Browse Source

[3086] Initial implementation of NameChangeTransaction class

Interim commit for 3086 which includes the preliminary implementation
of the base class, NameChangeTransaction.  b10-dhcp-ddns module will use
derivations of this class to carry out NameChangeRequests.
Thomas Markwalder 11 years ago
parent
commit
f3c1bcd7be

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

@@ -59,6 +59,7 @@ b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 EXTRA_DIST += d2_messages.mes

+ 6 - 0
src/bin/d2/d2_messages.mes

@@ -252,3 +252,9 @@ in event loop.
 % DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
 % DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
 This is informational message issued when the application has been instructed
 This is informational message issued when the application has been instructed
 to shut down by the controller.
 to shut down by the controller.
+
+% DHCP_DDNS_TRANS_PROCESS_EROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
+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. 

+ 6 - 0
src/bin/d2/d2_update_mgr.cc

@@ -108,6 +108,12 @@ void D2UpdateMgr::pickNextJob() {
         if (!hasTransaction(found_ncr->getDhcid())) {
         if (!hasTransaction(found_ncr->getDhcid())) {
             queue_mgr_->dequeueAt(index);
             queue_mgr_->dequeueAt(index);
             makeTransaction(found_ncr);
             makeTransaction(found_ncr);
+
+#if 0
+            // this will run it up to its first IO
+            trans->startTransaction();
+#endif
+
             return;
             return;
         }
         }
     }
     }

+ 1 - 49
src/bin/d2/d2_update_mgr.h

@@ -22,6 +22,7 @@
 #include <d2/d2_log.h>
 #include <d2/d2_log.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_queue_mgr.h>
 #include <d2/d2_cfg_mgr.h>
 #include <d2/d2_cfg_mgr.h>
+#include <d2/nc_trans.h>
 
 
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -37,58 +38,9 @@ public:
         isc::Exception(file, line, what) { };
         isc::Exception(file, line, what) { };
 };
 };
 
 
-//@{
-/// @todo  This is a stub implementation of NameChangeTransaction that is here
-/// strictly to facilitate development of D2UpdateMgr. It will move to its own
-/// source file(s) once NameChangeTransaction class development begins.
-
-/// @brief Defines the key for transactions.
-typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
-
-class NameChangeTransaction {
-public:
-    NameChangeTransaction(isc::asiolink::IOService& 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) {
-    }
-
-    ~NameChangeTransaction(){
-    }
-
-    const dhcp_ddns::NameChangeRequestPtr& getNcr() const {
-        return (ncr_);
-    }
-
-    const TransactionKey& getTransactionKey() const {
-        return (ncr_->getDhcid());
-    }
-
-    dhcp_ddns::NameChangeStatus getNcrStatus() const {
-        return (ncr_->getStatus());
-    }
-
-private:
-    isc::asiolink::IOService& io_service_;
-
-    dhcp_ddns::NameChangeRequestPtr ncr_;
-
-    DdnsDomainPtr forward_domain_;
-
-    DdnsDomainPtr reverse_domain_;
-};
-
-/// @brief Defines a pointer to a NameChangeTransaction.
-typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
-
-//@}
-
 /// @brief Defines a list of transactions.
 /// @brief Defines a list of transactions.
 typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
 typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
 
 
-
 /// @brief D2UpdateMgr creates and manages update transactions.
 /// @brief D2UpdateMgr creates and manages update transactions.
 ///
 ///
 /// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising
 /// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising

+ 244 - 0
src/bin/d2/nc_trans.cc

@@ -0,0 +1,244 @@
+// 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_trans.h>
+
+namespace isc {
+namespace d2 {
+
+// Common transaction states
+const int NameChangeTransaction::NEW_ST;
+const int NameChangeTransaction::READY_ST;
+const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
+const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
+const int NameChangeTransaction::DONE_ST;
+
+const int NameChangeTransaction::DERIVED_STATES;
+
+// Common transaction events
+const int NameChangeTransaction::NOP_EVT;
+const int NameChangeTransaction::START_TRANSACTION_EVT;
+const int NameChangeTransaction::SELECT_SERVER_EVT;
+const int NameChangeTransaction::SERVER_SELECTED_EVT;
+const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
+const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
+const int NameChangeTransaction::IO_COMPLETED_EVT;
+const int NameChangeTransaction::UPDATE_OK_EVT;
+const int NameChangeTransaction::UPDATE_FAILED_EVT;
+const int NameChangeTransaction::CANCEL_TRANSACTION_EVT;
+const int NameChangeTransaction::ALL_DONE_EVT;
+
+const int NameChangeTransaction::DERIVED_EVENTS;
+
+NameChangeTransaction::
+NameChangeTransaction(isc::asiolink::IOService& io_service,
+                      dhcp_ddns::NameChangeRequestPtr& ncr,
+                      DdnsDomainPtr forward_domain,
+                      DdnsDomainPtr reverse_domain)
+    : state_handlers_(), io_service_(io_service), ncr_(ncr),
+     forward_domain_(forward_domain), reverse_domain_(reverse_domain),
+     dns_client_(), state_(NEW_ST), next_event_(NOP_EVT),
+     dns_update_status_(DNSClient::OTHER), dns_update_response_(),
+     forward_change_completed_(false), reverse_change_completed_(false) {
+    if (!ncr_) {
+        isc_throw(NameChangeTransactionError, "NameChangeRequest cannot null");
+    }
+
+    if (ncr_->isForwardChange() && !(forward_domain_)) {
+        isc_throw(NameChangeTransactionError,
+                 "Forward change must have a forward domain");
+    }
+
+    if (ncr_->isReverseChange() && !(reverse_domain_)) {
+        isc_throw(NameChangeTransactionError,
+                 "Reverse change must have a reverse domain");
+    }
+    
+    // Use setters here so we get proper values for previous state, last event.
+    setState(state_);
+    setNextEvent(NOP_EVT);
+}
+
+NameChangeTransaction::~NameChangeTransaction(){
+}
+
+void
+NameChangeTransaction::startTransaction() {
+    // Initialize the state handler map first.
+    initStateHandlerMap();
+
+    // Set the current state to READY and enter the run loop.
+    setState(READY_ST);
+    runStateModel(START_TRANSACTION_EVT);
+}
+
+void
+NameChangeTransaction::cancelTransaction() {
+    //@todo It is up to the deriving state model to handle this event.
+    runStateModel(CANCEL_TRANSACTION_EVT);
+}
+
+void
+NameChangeTransaction::operator()(DNSClient::Status status) {
+    // Stow the completion status and re-enter the run loop with the event
+    // set to indicate IO completed.
+    // runStateModel is exception safe so we are good to call it here.  
+    // It won't exit until we hit the next IO wait or the state model ends.
+    setDnsUpdateStatus(status);
+    runStateModel(IO_COMPLETED_EVT);
+}
+
+void
+NameChangeTransaction::runStateModel(unsigned int run_event) {
+    try {
+        // Seed the loop with the given event as the next to process.
+        setNextEvent(run_event);
+        do {
+            // Invoke the current state's handler.  It should consume the 
+            // next event, then determine what happens next by setting
+            // current state and/or the next event.  
+            (getStateHandler(state_))();
+
+            // Keep going until a handler sets next event to a NOP_EVT. 
+        } while (getNextEvent() != NOP_EVT);
+    }
+    catch (const std::exception& ex) {
+        // Transaction has suffered an unexpected exception.  This indicates
+        // a programmatic shortcoming.  Log it and set status to ST_FAILED.
+        // In theory, the model should account for all error scenarios and
+        // deal with them accordingly.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_TRANS_PROCESS_EROR).arg(ex.what());
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+    }
+}
+
+
+StateHandler
+NameChangeTransaction::getStateHandler(unsigned int state) {
+    StateHandlerMap::iterator it = state_handlers_.find(state);
+    if (it == state_handlers_.end()) {
+        isc_throw(NameChangeTransactionError, "Invalid state: " << state);
+    }
+
+    return ((*it).second);
+}
+
+void
+NameChangeTransaction::addToMap(unsigned int state, StateHandler handler) {
+    StateHandlerMap::iterator it = state_handlers_.find(state);
+    if (it != state_handlers_.end()) {
+        isc_throw(NameChangeTransactionError,
+                  "Attempted duplicate entry in state handler mape, state: "
+                   << state);
+    }
+
+    state_handlers_[state] = handler;
+}
+
+void
+NameChangeTransaction::setState(unsigned int state) {
+    prev_state_ = state_;
+    state_ = state;
+}
+
+void
+NameChangeTransaction::setNextEvent(unsigned int event) {
+    last_event_ = next_event_;
+    next_event_ = event;
+}
+
+void
+NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
+    dns_update_status_ = status;
+}
+
+void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+    dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::setForwardChangeCompleted(const bool value) {
+    forward_change_completed_ = value;
+}
+
+void
+NameChangeTransaction::setReverseChangeCompleted(const bool value) {
+    reverse_change_completed_ = value;
+}
+
+const dhcp_ddns::NameChangeRequestPtr& 
+NameChangeTransaction::getNcr() const {
+    return (ncr_);
+}
+
+const TransactionKey& 
+NameChangeTransaction::getTransactionKey() const {
+    return (ncr_->getDhcid());
+}
+
+dhcp_ddns::NameChangeStatus
+NameChangeTransaction::getNcrStatus() const {
+    return (ncr_->getStatus());
+}
+
+void
+NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
+    return (ncr_->setStatus(status));
+}
+
+unsigned int
+NameChangeTransaction::getState() const {
+    return (state_);
+}
+
+unsigned int
+NameChangeTransaction::getPrevState() const {
+    return (prev_state_);
+}
+
+unsigned int
+NameChangeTransaction::getLastEvent() const {
+    return (last_event_);
+}
+
+unsigned int
+NameChangeTransaction::getNextEvent() const {
+    return (next_event_);
+}
+
+DNSClient::Status
+NameChangeTransaction::getDnsUpdateStatus() const {
+    return (dns_update_status_);
+}
+
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateResponse() const {
+    return (dns_update_response_);
+}
+
+bool
+NameChangeTransaction::getForwardChangeCompleted() const {
+    return (forward_change_completed_);
+}
+
+bool
+NameChangeTransaction::getReverseChangeCompleted() const {
+    return (reverse_change_completed_);
+}
+
+
+} // namespace isc::d2
+} // namespace isc

+ 478 - 0
src/bin/d2/nc_trans.h

@@ -0,0 +1,478 @@
+// 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_TRANS_H
+#define NC_TRANS_H
+
+/// @file nc_trans.h This file defines the class NameChangeTransaction.
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <d2/d2_config.h>
+#include <d2/dns_client.h>
+#include <dhcp_ddns/ncr_msg.h>
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the update manager encounters a general error.
+class NameChangeTransactionError : public isc::Exception {
+public:
+    NameChangeTransactionError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the type used as the unique key for transactions.
+typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
+
+/// @brief Defines a function pointer for the handler method for a state.
+typedef boost::function<void()> StateHandler;
+
+/// @brief Defines a map of states to their handler methods.
+typedef std::map<unsigned int, StateHandler> StateHandlerMap;
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS update.
+///
+/// NameChangeTransaction is the base class that provides the common state
+/// model mechanics and services performing the DNS updates needed to carry out
+/// a DHCP_DDNS request as described by a NameChangeRequest.
+///
+/// Upon construction, each transaction has all of the information and
+/// resources required to carry out its assigned request, including the list(s)
+/// of DNS server(s) needed. It is responsible for knowing what conversations
+/// it must have with which servers and in the order necessary to fulfill the
+/// request. Upon fulfillment of the request, the transaction's work is complete
+/// it is destroyed.
+///
+/// Fulfillment of the request is carried out through the performance of the
+/// transaction's state model.  Using a state driven implementation accounts
+/// for the conditional processing flow necessary to meet the DDNS RFCs as well
+/// as the asynchronous nature of IO with DNS servers.
+///
+/// Derivations of the class are responsible for defining the state model and
+/// conversations necessary to carry out the specific of request.
+///
+/// Conversations with DNS servers are done through the use of the DNSClient
+/// class.  The DNSClient provides a IOService-based means a service which
+/// performs a single, packet exchange with a given DNS server.  It sends a
+/// single update to the server and returns the response, asynchronously,
+/// through a callback.  At each point in a transaction's state model, where
+/// an update is to be sent, the model "suspends" until notified by the
+/// DNSClient via the callback.
+///
+/// The state model implementation used is a very basic approach. States
+/// and events are simple integer constants. Each state must have a state
+/// handler. State handlers are void methods which accept an event as their
+/// only parameter.  Each transaction instance contains a map of states to
+/// to bound method pointers to their respective state handlers.
+///
+/// When invoked, the handler determines what it should do based upon the event,
+/// including what the next state and event should be. In other words the state
+/// transition knowledge is distributed among the state handlers rather than
+/// encapsulated in some form of state transition table.  Events set from within
+/// the state handlers are "internally" triggered events.  Events set from
+/// outside the state model, such as through the DNSClient completion callback
+/// are "externally" triggered.
+///
+/// Executing the model consists of iteratively invoking the state handler
+/// indicated by the current state and passing it the current event.  As the
+/// handlers update the state and event, the machine is traversed. The loop
+/// "stops" whenever the model cannot continue without an externally triggered
+/// event or when it has reached its final state.  In the case of the former,
+/// the loop is re-entered upon arrival of the external event.
+///
+/// This loop is implemented in the runStateModel method.  This method accepts
+/// an event as argument.  This event is treated as the "next event" to process
+/// and is fed to the current state's handler.  The runStateModel does not exit
+/// until a handler sets the next event to a special value, NOP_EVT,
+/// indicating that either it is now waiting for IO to complete of the state
+/// model has reached its conclusion.
+///
+/// Re-entering the "loop" when a DNS update completes is done by a call to
+/// runStateModel() from within the DNSClient callback, with an event value
+/// of IO_COMPLETED_EVT.  As above, runStateModel() will loop until either the
+/// next IO is issued or the state model has reached its conclusion.
+///
+/// This class defines a set of events and states that are a common to all
+/// transactions. Each derivation may add define additional states and events
+/// as needed, but it must support the common set.  NameChangeTransaction
+/// does not supply any state handlers.  These are the sole responsibility of
+/// derivations.
+class NameChangeTransaction : public DNSClient::Callback {
+public:
+
+    //@{ States common to all transactions.
+    /// @brief State a transaction is in immediately after construction.
+    static const int NEW_ST = 0;
+    /// @brief State from which a transaction is started.
+    static const int READY_ST = 1;
+    /// @brief State in which forward DNS server  selection is done.
+    static const int SELECTING_FWD_SERVER_ST = 2;
+    /// @brief State in which reverse DNS server  selection is done.
+    static const int SELECTING_REV_SERVER_ST = 3;
+    /// @brief Final state, all work has been performed.
+    static const int DONE_ST = 4;
+
+    /// @define Value at which custom states in a derived class should begin.
+    static const int DERIVED_STATES = 100;
+    //@}
+
+
+    //@{ Events common to all transactions.
+    /// @brief Signifies that no event has occurred.
+    /// This is event used to interrupt the event loop to allow waiting for
+    /// an IO event or when there is no more work to be done.
+    static const int NOP_EVT = 0;
+    /// @brief Event used to start the transaction.
+    static const int START_TRANSACTION_EVT = 1;
+    /// @brief Issued when a server needs to be selected.
+    static const int SELECT_SERVER_EVT = 2;
+    /// @brief Issued when a server  has been selected.
+    static const int SERVER_SELECTED_EVT = 3;
+    /// @brief Issued when an update fails due to an IO error.
+    static const int SERVER_IO_ERROR_EVT = 4;
+    /// @brief Issued when there are no more servers from which to select.
+    /// This occurs when none of the servers in the list can be reached to
+    /// perform the update.
+    static const int NO_MORE_SERVERS_EVT = 5;
+    /// @brief Issued when a DNS update packet exchange has completed.
+    /// This occurs whenever the DNSClient callback is invoked whether the
+    /// exchange was successful or not.
+    static const int IO_COMPLETED_EVT = 6;
+    /// @brief Issued when the attempted update successfully completed.
+    /// This occurs when an DNS update packet was successfully processed
+    /// by the server.
+    static const int UPDATE_OK_EVT = 7;
+    /// @brief Issued when the attempted update fails to complete.
+    /// This occurs when an DNS update packet fails to process. The nature of
+    /// the failure is given by the DNSClient return status and the response
+    /// packet (if one was received).
+    static const int UPDATE_FAILED_EVT = 8;
+    /// @brief Issued when the transaction should be cancelled.
+    /// @todo - still on the fence about this one.
+    static const int CANCEL_TRANSACTION_EVT = 9;
+    /// @brief Issued when the state model has no more work left to do.
+    static const int ALL_DONE_EVT = 10;
+
+    /// @define Value at which custom events in a derived class should begin.
+    static const int DERIVED_EVENTS = 100;
+    //@}
+
+    /// @brief Constructor
+    ///
+    /// Instantiates a 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 NameChangeTransaction error 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,
+                          dhcp_ddns::NameChangeRequestPtr& ncr,
+                          DdnsDomainPtr forward_domain,
+                          DdnsDomainPtr reverse_domain);
+
+    virtual ~NameChangeTransaction();
+
+    /// @brief Begins execution of the transaction.
+    ///
+    /// This method invokes initHandlersMap() to initialize the map of state
+    /// handlers. It then starts the transaction's state model by setting the
+    /// current state to READY_ST and invoking runStateModel() with an event
+    /// parameter of START_TRANSACTION_EVT.
+    void startTransaction();
+
+    /// @todo - Not sure about this yet.
+    void cancelTransaction();
+
+    /// @brief Serves as the DNSClient IO completion event handler.
+    ///
+    /// This is the implementation of the method inherited by our derivation
+    /// from DNSClient::Callback.  When the DNSClient completes an update it
+    /// invokes this method as the completion handler.  This method stores
+    /// the given status and invokes runStateModel() with an event value of
+    /// IO_COMPLETED_EVT.
+    ///
+    /// @param status is the outcome of the DNS update packet exchange.
+    /// This method is exception safe.
+    virtual void operator()(DNSClient::Status status);
+
+protected:
+    /// @brief Populates the map of state handlers.
+    ///
+    /// This method is used by derivations to construct a map of states to
+    /// their appropriate state handlers (bound method pointers).  It is
+    /// invoked at the beginning of startTransaction().
+    ///
+    /// Implementations should use the addToMap() method add entries to
+    /// the map.
+    /// @todo This method should be pure virtual but until there are
+    /// derivations for the update manager to use we will provide an
+    /// temporary empty, implementation.  If we make it pure virtual now
+    /// D2UpdateManager will not compile.
+    virtual void initStateHandlerMap() {};
+
+    /// @brief Adds an entry to the state handler map.
+    ///
+    /// This method attempts to add an entry to the handler map which maps
+    /// the given handler to the given state.  The state handler must be
+    /// a bound member pointer to a handler method of the transaction instance.
+    /// The following code snippet shows an example derivation and call to
+    /// addToMap() within its initStateHandlerMap() method.
+    ///
+    /// @code
+    /// class ExampleTrans : public NameChangeTransaction {
+    /// public:
+    /// :
+    /// void readyHandler() {
+    /// }
+    ///
+    /// void initStateHandlerMap() {
+    ///     addToMap(READY_ST,
+    ///        boost::bind(&ExampleTrans::readyHandler, this));
+    ///     :
+    ///
+    /// @endcode
+    ///
+    /// @param state the value of the state to which to map
+    /// @param handler the bound method pointer to the handler for the state
+    ///
+    /// @throw NameChangeTransactionError if the map already contains an entry
+    /// for the given state.
+    void addToMap(unsigned int idx, StateHandler handler);
+
+    /// @brief Processes events through the state model
+    ///
+    /// This method implements the state model "execution loop".  It uses
+    /// the given event as the next event to process and begins looping by
+    /// passing it the state handler for the current state.   As described
+    /// above, the invoked state handler determines the current state and the
+    /// next event required to implement the business logic. The method
+    /// continues to loop until next event is set to NOP_EVT, at which point
+    /// the method exits.
+    ///
+    /// Any exception thrown during the loop is caught, logged, and the
+    /// transaction is immediately set to failed status.  The state model is
+    /// expected to account for any possible errors so any that escape are
+    /// treated as unrecoverable in terms of the current transaction.
+    ///
+    /// @param event is the next event to process
+    ///
+    /// This is guaranteed not to throw.
+    void runStateModel(unsigned int event);
+
+    /// @brief Return the state handler for a given state.
+    ///
+    /// This method looks up the state handler for the given state from within
+    /// the state handler map.
+    ///
+    /// @param state is the state constant of the desired handler.
+    ///
+    /// @return A StateHandler (bound method pointer) for the method that
+    /// handles the given state for this transaction.
+    ///
+    /// @throw NameChangeTransactionError
+    StateHandler getStateHandler(unsigned int state);
+
+    /// @brief Sets the current state to the given state value.
+    ///
+    /// This updates the transaction's notion of the current state and is the
+    /// state whose handler will be executed on the next iteration of the run
+    /// loop.
+    ///
+    /// @param state the new value to assign to the current state.
+    void setState(unsigned int state);
+
+    /// @brief Sets the next event to the given event value.
+    ///
+    /// This updates the transaction's notion of the next event and is the
+    /// event that will be passed into the current state's handler on the next
+    /// iteration of the run loop.
+    ///
+    /// @param state the new value to assign to the current state.
+    void setNextEvent(unsigned int event);
+
+    /// @brief Sets the update status to the given status value.
+    ///
+    /// @param status is the new value for the update status.
+    void setDnsUpdateStatus(const DNSClient::Status& status);
+
+    /// @brief Sets the update response packet to the given packet.
+    ///
+    /// @param response is the new response packet to assign.
+    void setDnsUpdateResponse(D2UpdateMessagePtr& response);
+
+    /// @brief Sets the forward change completion flag to the given value.
+    ///
+    /// @param value is the new value to assign to the flag.
+    void setForwardChangeCompleted(const bool value);
+
+    /// @brief Sets the reverse change completion flag to the given value.
+    ///
+    /// @param value is the new value to assign to the flag.
+    void setReverseChangeCompleted(const bool value);
+
+public:
+    /// @brief Fetches the NameChangeRequest for this transaction.
+    ///
+    /// @return A const pointer reference to the NameChangeRequest.
+    const dhcp_ddns::NameChangeRequestPtr& getNcr() const;
+
+    /// @brief Fetches the unique key that identifies this transaction.
+    ///
+    /// Transactions are uniquely identified by a TransactionKey. Currently
+    /// this is wrapper around a D2Dhcid.
+    ///
+    /// @return A const reference to the TransactionKey.
+    const TransactionKey& getTransactionKey() const;
+
+    /// @brief Fetches the NameChangeRequest status of the transaction.
+    ///
+    /// This is the current status of the NameChangeRequest, not to
+    /// be confused with the state of the transaction.  Once the transaction
+    /// is reached it's conclusion, the request will end up with a final
+    /// status.
+    ///
+    /// @return A dhcp_ddns::NameChangeStatus representing the current
+    /// status of the transaction.
+    dhcp_ddns::NameChangeStatus getNcrStatus() const;
+
+    /// @brief Sets the status of the transaction's NameChangeRequest
+    ///
+    /// @param status is the new value to assign to the NCR status.
+    void setNcrStatus(const dhcp_ddns::NameChangeStatus& status);
+
+    /// @brief Fetches the transaction's current state.
+    ///
+    /// This returns the transaction's notion of the current state. It is the
+    /// state whose handler will be executed on the next iteration of the run
+    /// loop.
+    ///
+    /// @return An unsigned int representing the current state.
+    unsigned int getState() const;
+
+    /// @brief Fetches the transaction's previous state.
+    ///
+    /// @return An unsigned int representing the previous state.
+    unsigned int getPrevState() const;
+
+    /// @brief Fetches the transaction's last event.
+    ///
+    /// @return An unsigned int representing the last event.
+    unsigned int getLastEvent() const;
+
+    /// @brief Fetches the transaction's next event.
+    ///
+    /// This returns the transaction's notion of the next event. It is the
+    /// event that will be passed into the current state's handler on the next
+    /// iteration of the run loop.
+    ///
+    /// @return An unsigned int representing the next event.
+    unsigned int getNextEvent() const;
+
+    /// @brief Fetches the most recent DNS update status.
+    ///
+    /// @return A DNSClient::Status indicating the result of the most recent
+    /// DNS update to complete.
+    DNSClient::Status getDnsUpdateStatus() const;
+
+    /// @brief Fetches the most recent DNS update response packet.
+    ///
+    /// @return A const pointer reference to the D2UpdateMessage most recently
+    /// received.
+    const D2UpdateMessagePtr& getDnsUpdateResponse() const;
+
+    /// @brief Returns whether the forward change has completed or not.
+    ///
+    /// The value returned is only meaningful if the NameChangeRequest calls
+    /// for a forward change to be done. The value returned indicates if
+    /// forward change has been completed successfully.
+    ///
+    /// @return True if the forward change has been completed, false otherwise.
+    bool getForwardChangeCompleted() const;
+
+    /// @brief Returns whether the reverse change has completed or not.
+    ///
+    /// The value returned is only meaningful if the NameChangeRequest calls
+    /// for a reverse change to be done. The value returned indicates if
+    /// reverse change has been completed successfully.
+    ///
+    /// @return True if the reverse change has been completed, false otherwise.
+    bool getReverseChangeCompleted() const;
+
+private:
+    /// @brief Contains a map of states to their state handlers.
+    StateHandlerMap state_handlers_;
+
+    /// @brief The IOService which should be used to for IO processing.
+    isc::asiolink::IOService& io_service_;
+
+    /// @brief The NameChangeRequest that the transaction is to fulfill.
+    dhcp_ddns::NameChangeRequestPtr ncr_;
+
+    /// @brief The forward domain that matches the request.
+    ///
+    /// The forward "domain" is DdnsDomain which contains all of the information
+    /// necessary, including the list of DNS servers to be used for a forward
+    /// change.
+    DdnsDomainPtr forward_domain_;
+
+    /// @brief The reverse domain that matches the request.
+    ///
+    /// The reverse "domain" is DdnsDomain which contains all of the information
+    /// necessary, including the list of DNS servers to be used for a reverse
+    /// change.
+    DdnsDomainPtr reverse_domain_;
+
+    /// @brief The DNSClient instance that will carry out DNS packet exchanges.
+    DNSClientPtr dns_client_;
+
+    /// @brief The current state within the transaction's state model.
+    unsigned int state_;
+
+    /// @brief The previous state within the transaction's state model.
+    unsigned int prev_state_;
+
+    /// @brief The event last processed by the transaction.
+    unsigned int last_event_;
+
+    /// @brief The event the transaction should process next.
+    unsigned int next_event_;
+
+    /// @brief The outcome of the most recently completed DNS packet exchange.
+    DNSClient::Status dns_update_status_;
+
+    /// @brief The DNS update response packet most recently received.
+    D2UpdateMessagePtr dns_update_response_;
+
+    /// @brief Indicator for whether or not the forward change completed ok.
+    bool forward_change_completed_;
+
+    /// @brief Indicator for whether or not the reverse change completed ok.
+    bool reverse_change_completed_;
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif

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

@@ -64,6 +64,7 @@ d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -76,6 +77,7 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
+d2_unittests_SOURCES += nc_trans_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 336 - 0
src/bin/d2/tests/nc_trans_unittests.cc

@@ -0,0 +1,336 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/nc_trans.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Test derivation of NameChangeTransaction for exercising state
+/// model mechanics.
+///
+/// This class faciliates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines.  It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class NameChangeStub : public NameChangeTransaction {
+public:
+
+    // NameChangeStub states
+    static const int DO_WORK_ST = DERIVED_STATES + 1;
+
+    // NameChangeStub events
+    static const int START_WORK_EVT = DERIVED_EVENTS + 1;
+
+    /// @brief Constructor
+    ///
+    /// Parameters match those needed by NameChangeTransaction.
+    NameChangeStub(isc::asiolink::IOService& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr forward_domain,
+                   DdnsDomainPtr reverse_domain)
+        : NameChangeTransaction(io_service, ncr, forward_domain,
+                                reverse_domain) {
+    }
+
+    /// @brief Destructor
+    virtual ~NameChangeStub() {
+    }
+
+    /// @brief State handler for the READY_ST.
+    /// 
+    /// Serves as the starting state handler, it consumes the
+    /// START_TRANSACTION_EVT "transitioing" to the state, DO_WORK_ST and
+    /// sets the next event to START_WORK_EVT.
+    void readyHandler() {
+        switch(getNextEvent()) {
+        case START_TRANSACTION_EVT:
+            setState(DO_WORK_ST);
+            setNextEvent(START_WORK_EVT);
+            break;
+        default:
+            // its bogus
+            isc_throw(NameChangeTransactionError, "invalid event: "
+                      << getNextEvent() << " for state: " << getState());
+        }
+    }
+
+    /// @brief State handler for the DO_WORK_ST.
+    /// 
+    /// Simulates a state that starts some form of asynchronous work.
+    /// When next event is START_WROK_EVT it sets the status to pending
+    /// and signals the state model must "wait" for an event by setting
+    /// next event to NOP_EVT. 
+    ///
+    /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+    /// DONE_ST, and sets the next event to ALL_DONE_EVT.
+    void doWorkHandler() {
+        switch(getNextEvent()) {
+        case START_WORK_EVT:
+            setNcrStatus(dhcp_ddns::ST_PENDING);
+            setNextEvent(NOP_EVT);
+            break;
+        //case WORK_DONE_EVT:
+        case IO_COMPLETED_EVT:
+            setState(DONE_ST);
+            setNextEvent(ALL_DONE_EVT);
+            break;
+        default:
+            // its bogus
+            isc_throw(NameChangeTransactionError, "invalid event: "
+                      << getNextEvent() << " for state: " << getState());
+        }
+    }
+
+    /// @brief State handler for the DONE_ST.
+    ///
+    /// This is the last state in the model.  Note that it sets the
+    /// status to completed and next event to NOP_EVT. 
+    void doneHandler() {
+        switch(getNextEvent()) {
+        case ALL_DONE_EVT:
+            setNcrStatus(dhcp_ddns::ST_COMPLETED);
+            setNextEvent(NOP_EVT);
+            break;
+        default:
+            // its bogus
+            isc_throw(NameChangeTransactionError, "invalid event: "
+                      << getNextEvent() << " for state: " << getState());
+        }
+    }
+
+    /// @brief Initializes the state handler map.
+    void initStateHandlerMap() {
+        addToMap(READY_ST,
+            boost::bind(&NameChangeStub::readyHandler, this));
+
+         addToMap(DO_WORK_ST,
+            boost::bind(&NameChangeStub::doWorkHandler, this));
+
+         addToMap(DONE_ST,
+            boost::bind(&NameChangeStub::doneHandler, this));
+    }
+
+    // Expose the protected methods to be tested.
+    using NameChangeTransaction::addToMap;
+    using NameChangeTransaction::getStateHandler;
+    using NameChangeTransaction::initStateHandlerMap;
+    using NameChangeTransaction::runStateModel;
+    using NameChangeTransaction::setState;
+    using NameChangeTransaction::setNextEvent;
+};
+
+const int NameChangeStub::DO_WORK_ST;
+const int NameChangeStub::START_WORK_EVT;
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
+
+/// @brief Test fixture for testing NameChangeTransaction
+///
+/// Note this class uses NameChangeStub class to exercise non-public
+/// aspects of NameChangeTransaction.
+class NameChangeTransactionTest : public ::testing::Test {
+public:
+    isc::asiolink::IOService io_service_;
+
+    virtual ~NameChangeTransactionTest() {
+    }
+
+    /// @brief Instantiates a NameChangeStub built around a canned 
+    /// NameChangeRequest.
+    NameChangeStubPtr makeCannedTransaction() {
+        const char* msg_str =
+            "{"
+            " \"change_type\" : 0 , "
+            " \"forward_change\" : true , "
+            " \"reverse_change\" : true , "
+            " \"fqdn\" : \"walah.walah.org.\" , "
+            " \"ip_address\" : \"192.168.2.1\" , "
+            " \"dhcid\" : \"0102030405060708\" , "
+            " \"lease_expires_on\" : \"20130121132405\" , "
+            " \"lease_length\" : 1300 "
+            "}";
+
+        dhcp_ddns::NameChangeRequestPtr ncr;
+        DnsServerInfoStoragePtr servers;
+        DdnsDomainPtr forward_domain;
+        DdnsDomainPtr reverse_domain;
+
+        ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
+        forward_domain.reset(new DdnsDomain("*", "", servers));
+        reverse_domain.reset(new DdnsDomain("*", "", servers));
+        return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr,
+                                  forward_domain, reverse_domain)));
+
+    }
+
+};
+
+/// @brief Tests NameChangeTransaction construction.
+/// This test verifies that:
+/// 1. Construction with null NameChangeRequest
+/// 2. Construction with null forward domain is not allowed when the request
+/// requires forward change.
+/// 3. Construction with null reverse domain is not allowed when the request
+/// requires reverse change.
+/// 4. Valid construction functions properly
+TEST(NameChangeTransaction, construction) {
+    isc::asiolink::IOService io_service;
+
+    const char* msg_str =
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : true , "
+        " \"fqdn\" : \"walah.walah.org.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \"0102030405060708\" , "
+        " \"lease_expires_on\" : \"20130121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    dhcp_ddns::NameChangeRequestPtr ncr;
+
+    dhcp_ddns::NameChangeRequestPtr empty_ncr;
+    DnsServerInfoStoragePtr servers;
+    DdnsDomainPtr forward_domain;
+    DdnsDomainPtr reverse_domain;
+    DdnsDomainPtr empty_domain;
+
+    ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+    ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers)));
+    ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers)));
+
+    // Verify that construction with an empty NameChangeRequest throws. 
+    EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
+                                       forward_domain, reverse_domain),
+                                        NameChangeTransactionError);
+
+    // Verify that construction with an empty forward domain when the
+    // NameChangeRequest calls for a forward change throws. 
+    EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+                                       empty_domain, reverse_domain),
+                                       NameChangeTransactionError);
+
+    // Verify that construction with an empty reverse domain when the
+    // NameChangeRequest calls for a reverse change throws. 
+    EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+                                       forward_domain, empty_domain),
+                                       NameChangeTransactionError);
+
+    // Verify that a valid construction attempt works.
+    EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+                                          forward_domain, reverse_domain));
+
+    // Verify that an empty forward domain is allowed when the requests does
+    // include a forward change. 
+    ncr->setForwardChange(false);
+    EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+                                          empty_domain, reverse_domain));
+
+    // Verify that an empty reverse domain is allowed when the requests does
+    // include a reverse change. 
+    ncr->setReverseChange(false);
+    EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+                                          empty_domain, empty_domain));
+}
+
+/// @brief Test the basic mechanics of state model execution.
+/// It first verifies basic state handle map fucntionality, and then 
+/// runs the NameChangeStub state model through from start to finish.
+TEST_F(NameChangeTransactionTest, stateModelTest) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Verify that getStateHandler will throw when, handler map is empty.
+    EXPECT_THROW(name_change->getStateHandler(NameChangeTransaction::READY_ST),
+                 NameChangeTransactionError);
+
+    // Verify that we can add a handler to the map.
+    ASSERT_NO_THROW(name_change->addToMap(NameChangeTransaction::READY_ST,
+                           boost::bind(&NameChangeStub::readyHandler,
+                                       name_change.get())));
+
+    // Verify that we can find the handler by its state.
+    EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction::
+                                                 READY_ST));
+
+    // Verify that we cannot add a duplicate.
+    EXPECT_THROW(name_change->addToMap(NameChangeTransaction::READY_ST,
+                           boost::bind(&NameChangeStub::readyHandler,
+                                       name_change.get())),
+                 NameChangeTransactionError);
+
+    // Verify that we can still find the handler by its state.
+    EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction::
+                                                 READY_ST));
+
+
+    // Get a fresh transaction.
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Manually call checkHandlerMap to ensure our test map populates.
+    // This is the method startTranscation invokes.
+    ASSERT_NO_THROW(name_change->initStateHandlerMap());
+
+    // Verify that we can find all the handlers by their state.
+    EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction::
+                                                 READY_ST));
+    EXPECT_NO_THROW(name_change->getStateHandler(NameChangeStub::DO_WORK_ST));
+    EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction::
+                                                 DONE_ST));
+
+
+    // Default value for state is NEW_ST.  Attempting to run the model
+    // with an invalid state will result in status of ST_FAILED.
+    ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState());
+    EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction::
+                                               START_TRANSACTION_EVT));
+
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+
+    // Get a fresh transaction.
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Launch the transaction properly by calling startTranscation.
+    // Verify that this transitions through to state of DO_WORK_ST,
+    // last event is START_WORK_EVT, next event is NOP_EVT, and
+    // NCR status is ST_PENDING.
+    ASSERT_NO_THROW(name_change->startTransaction());
+
+    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
+    EXPECT_EQ(NameChangeStub::START_WORK_EVT, name_change->getLastEvent());
+    EXPECT_EQ(NameChangeTransaction::NOP_EVT, name_change->getNextEvent());
+    EXPECT_EQ(dhcp_ddns::ST_PENDING, name_change->getNcrStatus());
+
+    // Simulate completion of DNSClient exchange by invoking the callback.
+    EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS));
+
+    // Verify that the state model has progressed through to completion:
+    // it is in the DONE_ST, the status is ST_COMPLETED, and the next event
+    // is NOP_EVT.
+    EXPECT_EQ(NameChangeTransaction::DONE_ST, name_change->getState());
+    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus());
+    EXPECT_EQ(NameChangeTransaction::NOP_EVT, name_change->getNextEvent());
+}
+
+}