Browse Source

[3156] Extracted state model logic from NameChangeTransaction into new class

b10-dhcp-ddns Finite state machine logic was refactored into its own class,
StateModel.
Thomas Markwalder 11 years ago
parent
commit
ea2cd98c25

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

@@ -60,6 +60,7 @@ b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
+b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 
 
 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

+ 3 - 3
src/bin/d2/d2_messages.mes

@@ -253,8 +253,8 @@ in event loop.
 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
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1 , %2
-This is error message issued when the application fails to process a 
+This is error message issued when the application fails to process a
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be
 of this update did not succeed. This is a programmatic error and should be
-reported. 
+reported.

+ 77 - 98
src/bin/d2/nc_trans.cc

@@ -19,17 +19,15 @@ namespace isc {
 namespace d2 {
 namespace d2 {
 
 
 // Common transaction states
 // Common transaction states
-const int NameChangeTransaction::NEW_ST;
 const int NameChangeTransaction::READY_ST;
 const int NameChangeTransaction::READY_ST;
 const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
 const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
 const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
 const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
-const int NameChangeTransaction::DONE_ST;
+const int NameChangeTransaction::PROCESS_TRANS_OK_ST;
+const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST;
 
 
-const int NameChangeTransaction::DERIVED_STATES;
+const int NameChangeTransaction::NCT_STATE_MAX;
 
 
 // Common transaction events
 // Common transaction events
-const int NameChangeTransaction::NOP_EVT;
-const int NameChangeTransaction::START_TRANSACTION_EVT;
 const int NameChangeTransaction::SELECT_SERVER_EVT;
 const int NameChangeTransaction::SELECT_SERVER_EVT;
 const int NameChangeTransaction::SERVER_SELECTED_EVT;
 const int NameChangeTransaction::SERVER_SELECTED_EVT;
 const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
 const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
@@ -37,18 +35,16 @@ const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
 const int NameChangeTransaction::IO_COMPLETED_EVT;
 const int NameChangeTransaction::IO_COMPLETED_EVT;
 const int NameChangeTransaction::UPDATE_OK_EVT;
 const int NameChangeTransaction::UPDATE_OK_EVT;
 const int NameChangeTransaction::UPDATE_FAILED_EVT;
 const int NameChangeTransaction::UPDATE_FAILED_EVT;
-const int NameChangeTransaction::ALL_DONE_EVT;
 
 
-const int NameChangeTransaction::DERIVED_EVENTS;
+const int NameChangeTransaction::NCT_EVENT_MAX;
 
 
 NameChangeTransaction::
 NameChangeTransaction::
 NameChangeTransaction(isc::asiolink::IOService& io_service,
 NameChangeTransaction(isc::asiolink::IOService& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_domain)
                       DdnsDomainPtr& reverse_domain)
-    : state_handlers_(), io_service_(io_service), ncr_(ncr),
+    : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
-     forward_domain_(forward_domain), reverse_domain_(reverse_domain),
+     reverse_domain_(reverse_domain), dns_client_(),
-     dns_client_(), state_(NEW_ST), next_event_(NOP_EVT),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      forward_change_completed_(false), reverse_change_completed_(false),
      forward_change_completed_(false), reverse_change_completed_(false),
      current_server_list_(), current_server_(), next_server_pos_(0) {
      current_server_list_(), current_server_(), next_server_pos_(0) {
@@ -65,10 +61,6 @@ NameChangeTransaction(isc::asiolink::IOService& io_service,
         isc_throw(NameChangeTransactionError,
         isc_throw(NameChangeTransactionError,
                  "Reverse change must have a reverse domain");
                  "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(){
 NameChangeTransaction::~NameChangeTransaction(){
@@ -76,85 +68,32 @@ NameChangeTransaction::~NameChangeTransaction(){
 
 
 void
 void
 NameChangeTransaction::startTransaction() {
 NameChangeTransaction::startTransaction() {
-    // Initialize the state handler map first.
+    startModel(READY_ST);
-    initStateHandlerMap();
-
-    // Test validity of the handler map. This provides an opportunity to
-    // sanity check the map prior to attempting to execute the model.
-    verifyStateHandlerMap();
-
-    // Set the current state to READY and enter the run loop.
-    setState(READY_ST);
-    runStateModel(START_TRANSACTION_EVT);
 }
 }
 
 
 void
 void
 NameChangeTransaction::operator()(DNSClient::Status status) {
 NameChangeTransaction::operator()(DNSClient::Status status) {
     // Stow the completion status and re-enter the run loop with the event
     // Stow the completion status and re-enter the run loop with the event
     // set to indicate IO completed.
     // set to indicate IO completed.
-    // runStateModel is exception safe so we are good to call it here.
+    // runModel 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.
     // It won't exit until we hit the next IO wait or the state model ends.
     setDnsUpdateStatus(status);
     setDnsUpdateStatus(status);
-    runStateModel(IO_COMPLETED_EVT);
+    runModel(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
 void
-NameChangeTransaction::setState(unsigned int state) {
+NameChangeTransaction::verifyStateHandlerMap() {
-    prev_state_ = state_;
+    getStateHandler(READY_ST);
-    state_ = state;
+    getStateHandler(SELECTING_FWD_SERVER_ST);
+    getStateHandler(SELECTING_REV_SERVER_ST);
+    getStateHandler(PROCESS_TRANS_OK_ST);
+    getStateHandler(PROCESS_TRANS_FAILED_ST);
 }
 }
 
 
 void
 void
-NameChangeTransaction::setNextEvent(unsigned int event) {
+NameChangeTransaction::onModelFailure() {
-    last_event_ = next_event_;
+    setNcrStatus(dhcp_ddns::ST_FAILED);
-    next_event_ = event;
 }
 }
 
 
 void
 void
@@ -244,26 +183,6 @@ NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(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
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
     return (dns_update_status_);
@@ -284,6 +203,66 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
     return (reverse_change_completed_);
 }
 }
 
 
+const char*
+NameChangeTransaction::getStateLabel(const int state) const {
+    const char* str = "Unknown";
+    switch(state) {
+    case READY_ST:
+        str = "NameChangeTransaction::READY_ST";
+        break;
+    case SELECTING_FWD_SERVER_ST:
+        str = "NameChangeTransaction::SELECTING_FWD_SERVER_ST";
+        break;
+    case SELECTING_REV_SERVER_ST:
+        str = "NameChangeTransaction::SELECTING_REV_SERVER_ST";
+        break;
+    case PROCESS_TRANS_OK_ST:
+        str = "NameChangeTransaction::PROCESS_TRANS_OK_ST";
+        break;
+    case PROCESS_TRANS_FAILED_ST:
+        str = "NameChangeTransaction::PROCESS_TRANS_FAILED_ST";
+        break;
+    default:
+        str = StateModel::getStateLabel(state);
+        break;
+    }
+
+    return (str);
+}
+
+const char*
+NameChangeTransaction::getEventLabel(const int event) const {
+    const char* str = "Unknown";
+    switch(event) {
+    case SELECT_SERVER_EVT:
+        str = "NameChangeTransaction::SELECT_SERVER_EVT";
+        break;
+    case SERVER_SELECTED_EVT:
+        str = "NameChangeTransaction::SERVER_SELECTED_EVT";
+        break;
+    case SERVER_IO_ERROR_EVT:
+        str = "NameChangeTransaction::SERVER_IO_ERROR_EVT";
+        break;
+    case NO_MORE_SERVERS_EVT:
+        str = "NameChangeTransaction::NO_MORE_SERVERS_EVT";
+        break;
+    case IO_COMPLETED_EVT:
+        str = "NameChangeTransaction::IO_COMPLETED_EVT";
+        break;
+    case UPDATE_OK_EVT:
+        str = "NameChangeTransaction::UPDATE_OK_EVT";
+        break;
+    case UPDATE_FAILED_EVT:
+        str = "NameChangeTransaction::UPDATE_FAILED_EVT";
+        break;
+    default:
+        str = StateModel::getEventLabel(event);
+        break;
+    }
+
+    return (str);
+}
+
 
 
 } // namespace isc::d2
 } // namespace isc::d2
 } // namespace isc
 } // namespace isc

+ 135 - 208
src/bin/d2/nc_trans.h

@@ -21,6 +21,7 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <d2/d2_config.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
 #include <d2/dns_client.h>
+#include <d2/state_model.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -29,7 +30,7 @@
 namespace isc {
 namespace isc {
 namespace d2 {
 namespace d2 {
 
 
-/// @brief Thrown if the update manager encounters a general error.
+/// @brief Thrown if the transaction encounters a general error.
 class NameChangeTransactionError : public isc::Exception {
 class NameChangeTransactionError : public isc::Exception {
 public:
 public:
     NameChangeTransactionError(const char* file, size_t line,
     NameChangeTransactionError(const char* file, size_t line,
@@ -40,17 +41,12 @@ public:
 /// @brief Defines the type used as the unique key for transactions.
 /// @brief Defines the type used as the unique key for transactions.
 typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
 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.
 /// @brief Embodies the "life-cycle" required to carry out a DDNS update.
 ///
 ///
 /// NameChangeTransaction is the base class that provides the common state
 /// NameChangeTransaction is the base class that provides the common state
 /// model mechanics and services performing the DNS updates needed to carry out
 /// model mechanics and services performing the DNS updates needed to carry out
-/// a DHCP_DDNS request as described by a NameChangeRequest.
+/// a DHCP_DDNS request as described by a NameChangeRequest.  It is derived
+/// from StateModel which supplies a simple, general purpose FSM implementation.
 ///
 ///
 /// Upon construction, each transaction has all of the information and
 /// Upon construction, each transaction has all of the information and
 /// resources required to carry out its assigned request, including the list(s)
 /// resources required to carry out its assigned request, including the list(s)
@@ -73,61 +69,33 @@ typedef std::map<unsigned int, StateHandler> StateHandlerMap;
 /// single update to the server and returns the response, asynchronously,
 /// single update to the server and returns the response, asynchronously,
 /// through a callback.  At each point in a transaction's state model, where
 /// 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
 /// an update is to be sent, the model "suspends" until notified by the
-/// DNSClient via the callback.
+/// DNSClient via the callbacka.  Suspension is done by posting a
-///
+/// StateModel::NOP_EVT as the next event, stopping the state model execution.
-/// 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
+/// Resuming state model execution when a DNS update completes is done by a
-/// an event as argument.  This event is treated as the "next event" to process
+/// call to StateModel::runStateModel() from within the DNSClient callback,
-/// and is fed to the current state's handler.  The runStateModel does not exit
+/// with an event value of IO_COMPLETED_EVT (described below).
-/// 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
 /// 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
 /// transactions. Each derivation may add define additional states and events
 /// as needed, but it must support the common set.  NameChangeTransaction
 /// as needed, but it must support the common set.  NameChangeTransaction
 /// does not supply any state handlers.  These are the sole responsibility of
 /// does not supply any state handlers.  These are the sole responsibility of
 /// derivations.
 /// derivations.
-class NameChangeTransaction : public DNSClient::Callback {
+class NameChangeTransaction : public DNSClient::Callback, public StateModel {
 public:
 public:
 
 
     //@{ States common to all transactions.
     //@{ 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.
     /// @brief State from which a transaction is started.
-    static const int READY_ST = 1;
+    static const int READY_ST = SM_STATE_MAX + 1;
+
     /// @brief State in which forward DNS server selection is done.
     /// @brief State in which forward DNS server selection is done.
     ///
     ///
     /// Within this state, the actual selection of the next forward server
     /// Within this state, the actual selection of the next forward server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// event.
     /// event.
-    static const int SELECTING_FWD_SERVER_ST = 2;
+    static const int SELECTING_FWD_SERVER_ST = SM_STATE_MAX + 2;
 
 
     /// @brief State in which reverse DNS server  selection is done.
     /// @brief State in which reverse DNS server  selection is done.
     ///
     ///
@@ -135,51 +103,43 @@ public:
     /// to use is conducted.  Upon conclusion of this state the next server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// event.
     /// event.
-    static const int SELECTING_REV_SERVER_ST = 3;
+    static const int SELECTING_REV_SERVER_ST = SM_STATE_MAX + 3;
+
+    static const int PROCESS_TRANS_OK_ST = SM_STATE_MAX + 4;
 
 
-    /// @brief Final state, all work has been performed.
+    static const int PROCESS_TRANS_FAILED_ST = SM_STATE_MAX + 5;
-    static const int DONE_ST = 4;
 
 
     /// @define Value at which custom states in a derived class should begin.
     /// @define Value at which custom states in a derived class should begin.
-    static const int DERIVED_STATES = 100;
+    static const int NCT_STATE_MAX = SM_STATE_MAX + 100;
     //@}
     //@}
 
 
-
     //@{ Events common to all transactions.
     //@{ 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.
     /// @brief Issued when a server needs to be selected.
-    static const int SELECT_SERVER_EVT = 2;
+    static const int SELECT_SERVER_EVT = SM_STATE_MAX + 1;
     /// @brief Issued when a server  has been selected.
     /// @brief Issued when a server  has been selected.
-    static const int SERVER_SELECTED_EVT = 3;
+    static const int SERVER_SELECTED_EVT = SM_EVENT_MAX + 2;
     /// @brief Issued when an update fails due to an IO error.
     /// @brief Issued when an update fails due to an IO error.
-    static const int SERVER_IO_ERROR_EVT = 4;
+    static const int SERVER_IO_ERROR_EVT = SM_EVENT_MAX + 3;
     /// @brief Issued when there are no more servers from which to select.
     /// @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
     /// This occurs when none of the servers in the list can be reached to
     /// perform the update.
     /// perform the update.
-    static const int NO_MORE_SERVERS_EVT = 5;
+    static const int NO_MORE_SERVERS_EVT =SM_EVENT_MAX +  4;
     /// @brief Issued when a DNS update packet exchange has completed.
     /// @brief Issued when a DNS update packet exchange has completed.
     /// This occurs whenever the DNSClient callback is invoked whether the
     /// This occurs whenever the DNSClient callback is invoked whether the
     /// exchange was successful or not.
     /// exchange was successful or not.
-    static const int IO_COMPLETED_EVT = 6;
+    static const int IO_COMPLETED_EVT = SM_EVENT_MAX + 5;
     /// @brief Issued when the attempted update successfully completed.
     /// @brief Issued when the attempted update successfully completed.
     /// This occurs when an DNS update packet was successfully processed
     /// This occurs when an DNS update packet was successfully processed
     /// by the server.
     /// by the server.
-    static const int UPDATE_OK_EVT = 7;
+    static const int UPDATE_OK_EVT = SM_EVENT_MAX + 6;
     /// @brief Issued when the attempted update fails to complete.
     /// @brief Issued when the attempted update fails to complete.
     /// This occurs when an DNS update packet fails to process. The nature of
     /// 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
     /// the failure is given by the DNSClient return status and the response
     /// packet (if one was received).
     /// packet (if one was received).
-    static const int UPDATE_FAILED_EVT = 8;
+    static const int UPDATE_FAILED_EVT = SM_EVENT_MAX + 7;
-    /// @brief Issued when the state model has no more work left to do.
-    static const int ALL_DONE_EVT = 9;
 
 
     /// @define Value at which custom events in a derived class should begin.
     /// @define Value at which custom events in a derived class should begin.
-    static const int DERIVED_EVENTS = 100;
+    static const int NCT_EVENT_MAX = SM_EVENT_MAX + 100;
     //@}
     //@}
 
 
     /// @brief Constructor
     /// @brief Constructor
@@ -204,10 +164,9 @@ public:
 
 
     /// @brief Begins execution of the transaction.
     /// @brief Begins execution of the transaction.
     ///
     ///
-    /// This method invokes initHandlersMap() to initialize the map of state
+    /// This method invokes StateModel::startModel() with a value of READY_ST.
-    /// handlers. It then starts the transaction's state model by setting the
+    /// This causes transaction's state model to attempt to begin execution
-    /// current state to READY_ST and invoking runStateModel() with an event
+    /// with the state handler for READY_ST.
-    /// parameter of START_TRANSACTION_EVT.
     void startTransaction();
     void startTransaction();
 
 
     /// @brief Serves as the DNSClient IO completion event handler.
     /// @brief Serves as the DNSClient IO completion event handler.
@@ -237,113 +196,49 @@ protected:
     /// D2UpdateManager will not compile.
     /// D2UpdateManager will not compile.
     virtual void initStateHandlerMap() {};
     virtual void initStateHandlerMap() {};
 
 
-
     /// @brief Validates the contents of the state handler map.
     /// @brief Validates the contents of the state handler map.
     ///
     ///
     /// This method is invoked immediately after initStateHandlerMap and
     /// This method is invoked immediately after initStateHandlerMap and
-    /// provides an opportunity for derivations to verify that the map
+    /// verifies that the state map includes handlers for all of the states
-    /// is correct.  If the map is determined to be invalid this method
+    /// defined by NameChangeTransaction.  If the map is determined to be
-    /// should throw a NameChangeTransactionError.
+    /// invalid this method will throw a NameChangeTransactionError.
-    ///
-    /// The simplest implementation would include a call to getStateHandler,
-    /// for each state the derivation supports.  For example, a implementation
-    /// which included three states, READY_ST, DO_WORK_ST, and DONE_ST could
-    /// implement this function as follows:
     ///
     ///
-    /// @code
+    /// Derivations should ALSO provide an implementation of this method. That
-    ///    void verifyStateHandlerMap() {
+    /// implementation should invoke this method, as well as verifying that all
-    ///        getStateHandler(READY_ST);
+    /// of the derivation's states have handlers.
-    ///        getStateHandler(DO_WORK_ST);
-    ///        getStateHandler(DONE_ST);
-    ///    }
-    /// @endcode
-    ///
-    /// @todo This method should be pure virtual but until there are
-    /// derivations for the update manager to use, we will provide a
-    /// temporary empty, implementation.  If we make it pure virtual now
-    /// D2UpdateManager will not compile.
-    /// @throw NameChangeTransactionError if the map is invalid.
-    virtual void verifyStateHandlerMap() {};
-
-    /// @brief Adds an entry to the state handler map.
     ///
     ///
-    /// This method attempts to add an entry to the handler map which maps
+    /// A derivation's implementation of this function might look as follows:
-    /// 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
     /// @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 state, StateHandler handler);
-
-    /// @brief Processes events through the state model
     ///
     ///
-    /// This method implements the state model "execution loop".  It uses
+    ///     class DerivedTrans : public NameChangeTransaction {
-    /// the given event as the next event to process and begins looping by
+    ///         :
-    /// passing it the state handler for the current state.   As described
+    ///         void verifyStateHandlerMap() {
-    /// above, the invoked state handler determines the current state and the
+    ///             // Verify derivations' states:
-    /// next event required to implement the business logic. The method
+    ///             getStateHandler(SOME_DERIVED_STATE_1);
-    /// continues to loop until next event is set to NOP_EVT, at which point
+    ///             getStateHandler(SOME_DERIVED_STATE_2);
-    /// the method exits.
+    ///             :
+    ///             getStateHandler(SOME_DERIVED_STATE_N);
     ///
     ///
-    /// Any exception thrown during the loop is caught, logged, and the
+    ///             // Verify handlers for NameChangeTransaction states:
-    /// transaction is immediately set to failed status.  The state model is
+    ///             NameChangeTransaction::verifyStateHandlerMap();
-    /// 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
+    /// @endcode
-    ///
-    /// 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.
+    /// @throw NameChangeTransactionError if the map is invalid.
-    void setState(unsigned int state);
+    virtual void verifyStateHandlerMap();
 
 
-    /// @brief Sets the next event to the given event value.
+    /// @brief Handler for fatal model execution errors.
-    ///
-    /// 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.
+    /// This handler is called by the StateModel implementation when the model
-    void setNextEvent(unsigned int event);
+    /// execution encounters a model violation:  attempt to call an unmapped
+    /// state, an event not valid for the current state, or an uncaught
+    /// exception thrown during a state handler invocation.  When such an
+    /// error occurs the transaction is deemed inoperable, and futher model
+    /// execution cannot be performed.  It marks the transaction as failed by
+    /// setting the NCR status to dhcp_ddns::ST_FAILED
+    virtual void onModelFailure();
 
 
     /// @brief Sets the update status to the given status value.
     /// @brief Sets the update status to the given status value.
     ///
     ///
@@ -441,34 +336,6 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     /// the request does not include a reverse change, the pointer will empty.
     DdnsDomainPtr& getReverseDomain();
     DdnsDomainPtr& getReverseDomain();
 
 
-    /// @brief Fetches the 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.
     /// @brief Fetches the most recent DNS update status.
     ///
     ///
     /// @return A DNSClient::Status indicating the result of the most recent
     /// @return A DNSClient::Status indicating the result of the most recent
@@ -499,10 +366,82 @@ public:
     /// @return True if the reverse change has been completed, false otherwise.
     /// @return True if the reverse change has been completed, false otherwise.
     bool getReverseChangeCompleted() const;
     bool getReverseChangeCompleted() const;
 
 
-private:
+    /// @brief Converts a state value into a text label.
-    /// @brief Contains a map of states to their state handlers.
+    ///
-    StateHandlerMap state_handlers_;
+    /// This method supplies labels for NameChangeTransaction's predefined
+    /// states. It is declared virtual to allow derivations to embed a call to
+    /// this method within their own implementation which would define labels
+    /// for its states.  An example implementation might look like the
+    /// following:
+    /// @code
+    ///
+    /// class DerivedTrans : public NameChangeTransaction {
+    ///     :
+    ///     static const int EXAMPLE_1_ST = NCT_STATE_MAX + 1;
+    ///     :
+    ///     const char* getStateLabel(const int state) const {
+    ///         const char* str = "Unknown";
+    ///         switch(state) {
+    ///         case EXAMPLE_1_ST:
+    ///             str = "DerivedTrans::EXAMPLE_1_ST";
+    ///             break;
+    ///         :
+    ///         default:
+    ///             // Not a derived state, pass it to NameChangeTransaction's
+    ///             // method.
+    ///             str = NameChangeTransaction::getStateLabel(state);
+    ///             break;
+    ///         }
+    ///
+    ///         return (str);
+    ///      }
+    ///
+    /// @endcode
+    ///
+    /// @param state is the state for which a label is desired.
+    ///
+    /// @return Returns a const char* containing the state label or
+    /// "Unknown" if the value cannot be mapped.
+    virtual const char* getStateLabel(const int state) const;
+
+    /// @brief Converts a event value into a text label.
+    ///
+    /// This method supplies labels for NameChangeTransactions's predefined
+    /// events. It is declared virtual to allow derivations to embed a call to
+    /// this method within their own implementation which would define labels
+    /// for their events.  An example implementation might look like the
+    /// following:
+    /// @code
+    ///
+    /// class DerivedTrans : public NameChangeTransaction {
+    ///     :
+    ///     static const int EXAMPLE_1_EVT = NCT_EVENT_MAX + 1;
+    ///     :
+    ///     const char* getEventLabel(const int event) const {
+    ///         const char* str = "Unknown";
+    ///         switch(event) {
+    ///         case EXAMPLE_1_EVT:
+    ///             str = "DerivedTrans::EXAMPLE_1_EVT";
+    ///             break;
+    ///         :
+    ///         default:
+    ///             // Not a derived event, pass it to NameChangeTransaction's
+    ///             // method.
+    ///             str = StateModel::getEventLabel(event);
+    ///             break;
+    ///         }
+    ///
+    ///         return (str);
+    ///      }
+    ///
+    /// @endcode
+    /// @param event is the event for which a label is desired.
+    ///
+    /// @return Returns a const char* containing the event label or
+    /// "Unknown" if the value cannot be mapped.
+    virtual const char* getEventLabel(const int state) const;
 
 
+private:
     /// @brief The IOService which should be used to for IO processing.
     /// @brief The IOService which should be used to for IO processing.
     isc::asiolink::IOService& io_service_;
     isc::asiolink::IOService& io_service_;
 
 
@@ -526,18 +465,6 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     DNSClientPtr dns_client_;
     DNSClientPtr dns_client_;
 
 
-    /// @brief The 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.
     /// @brief The outcome of the most recently completed DNS packet exchange.
     DNSClient::Status dns_update_status_;
     DNSClient::Status dns_update_status_;
 
 

+ 277 - 0
src/bin/d2/state_model.cc

@@ -0,0 +1,277 @@
+// 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/state_model.h>
+
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+// Common state model states
+const int StateModel::NEW_ST;
+const int StateModel::END_ST;
+
+const int StateModel::SM_STATE_MAX;
+
+// Common state model events
+const int StateModel::NOP_EVT;
+const int StateModel::START_EVT;
+const int StateModel::END_EVT;
+const int StateModel::FAIL_EVT;
+
+const int StateModel::SM_EVENT_MAX;
+
+StateModel::StateModel() : state_handlers_(), state_(NEW_ST),
+                          prev_state_(NEW_ST), last_event_(NOP_EVT),
+                          next_event_(NOP_EVT), on_entry_flag_(false),
+                          on_exit_flag_(false) {
+}
+
+StateModel::~StateModel(){
+}
+
+void
+StateModel::startModel(const int start_state) {
+    // Initialize the state handler map first.
+    initStateHandlerMap();
+
+    // Test validity of the handler map. This provides an opportunity to
+    // sanity check the map prior to attempting to execute the model.
+    verifyStateHandlerMap();
+
+    // Set the current state to startng state and enter the run loop.
+    setState(start_state);
+    runModel(START_EVT);
+}
+
+void
+StateModel::runModel(unsigned int run_event) {
+    try {
+        // Seed the loop with the given event as the next to process.
+        postNextEvent(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 (!isModelDone() && getNextEvent() != NOP_EVT);
+    }
+    catch (const std::exception& ex) {
+        // The model has suffered an unexpected exception. This constitutes a
+        // a model violation and indicates a programmatic shortcoming.
+        // In theory, the model should account for all error scenarios and
+        // deal with them accordingly.  Log it and transition to END_ST with
+        // FAILED_EVT via abortModel.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
+                  .arg(ex.what()).arg(getContextStr());
+        abortModel();
+    }
+}
+
+StateHandler
+StateModel::getStateHandler(unsigned int state) {
+    StateHandlerMap::iterator it = state_handlers_.find(state);
+    // It is wrong to try to find a state that isn't mapped.
+    if (it == state_handlers_.end()) {
+        isc_throw(StateModelError, "No handler for state: "
+                  << state << " - " << getStateLabel(state));
+    }
+
+    return ((*it).second);
+}
+
+void
+StateModel::addToMap(unsigned int state, StateHandler handler) {
+    StateHandlerMap::iterator it = state_handlers_.find(state);
+    // Check for a duplicate attempt.  State's can't have more than one.
+    if (it != state_handlers_.end()) {
+        isc_throw(StateModelError,
+                  "Attempted duplicate entry in state handler map, state: "
+                   << state << " - " << getStateLabel(state));
+    }
+
+    // Do not allow handlers for special states fo NEW_ST and END_ST.
+    if (state == NEW_ST || state == END_ST) {
+        isc_throw(StateModelError, "A handler is not supported for state: "
+                  << state << " - " << getStateLabel(state));
+    }
+
+    state_handlers_[state] = handler;
+}
+
+void
+StateModel::transition(unsigned int state, unsigned int event) {
+    setState(state);
+    postNextEvent(event);
+}
+
+void
+StateModel::endModel() {
+    transition(END_ST, END_EVT);
+}
+
+void
+StateModel::abortModel() {
+    transition(END_ST, FAIL_EVT);
+    onModelFailure();
+}
+
+void
+StateModel::setState(unsigned int state) {
+    // If the new state isn't NEW_ST or END_ST, make sure it has a handler.
+    if ((state && state != NEW_ST && state != END_ST)
+        && (state_handlers_.end() == state_handlers_.find(state))) {
+        isc_throw(StateModelError, "Attempt to set state to invalid stat :"
+                  << state << "=" << getStateLabel(state));
+    }
+
+    prev_state_ = state_;
+    state_ = state;
+
+    // Set the "do" flags if we are transitioning.
+    on_entry_flag_ = ((state != END_ST) && (prev_state_ != state_));
+    // At this time they are calculated the same way.
+    on_exit_flag_ = on_entry_flag_;
+}
+
+void
+StateModel::postNextEvent(unsigned int event) {
+    last_event_ = next_event_;
+    next_event_ = event;
+}
+
+bool
+StateModel::doOnEntry() {
+    bool ret = on_entry_flag_;
+    on_entry_flag_ = false;
+    return (ret);
+}
+
+bool
+StateModel::doOnExit() {
+    bool ret = on_exit_flag_;
+    on_exit_flag_ = false;
+    return (ret);
+}
+
+unsigned int
+StateModel::getState() const {
+    return (state_);
+}
+
+unsigned int
+StateModel::getPrevState() const {
+    return (prev_state_);
+}
+
+unsigned int
+StateModel::getLastEvent() const {
+    return (last_event_);
+}
+
+unsigned int
+StateModel::getNextEvent() const {
+    return (next_event_);
+}
+bool
+StateModel::isModelNew() const {
+    return (state_ == NEW_ST);
+}
+
+bool
+StateModel::isModelRunning() const {
+    return ((state_ != NEW_ST) && (state_ != END_ST));
+}
+
+bool
+StateModel::isModelWaiting() const {
+    return (isModelRunning() && (next_event_ == NOP_EVT));
+}
+
+bool
+StateModel::isModelDone() const {
+    return (state_ == END_ST);
+}
+
+bool
+StateModel::didModelFail() const {
+    return (isModelDone() && (next_event_ == FAIL_EVT));
+}
+
+const char*
+StateModel::getStateLabel(const int state) const {
+    const char* str = "Unknown";
+    switch(state) {
+    case NEW_ST:
+        str = "StateModel::NEW_ST";
+        break;
+    case END_ST:
+        str = "StateModel::END_ST";
+        break;
+    default:
+        break;
+    }
+
+    return (str);
+}
+
+std::string
+StateModel::getContextStr() const {
+    std::ostringstream stream;
+    stream << "current state: [ "
+            << state_ << " " << getStateLabel(state_)
+            << " ] next event: [ "
+            << next_event_ << " " << getEventLabel(next_event_) << " ]";
+    return(stream.str());
+}
+
+std::string
+StateModel::getPrevContextStr() const {
+    std::ostringstream stream;
+    stream << "previous state: [ "
+           << prev_state_ << " " << getStateLabel(prev_state_)
+           << " ] last event: [ "
+           << next_event_ << " " << getEventLabel(last_event_) << " ]";
+    return(stream.str());
+}
+
+const char*
+StateModel::getEventLabel(const int event) const {
+    const char* str = "Unknown";
+    switch(event) {
+    case NOP_EVT:
+        str = "StateModel::NOP_EVT";
+        break;
+    case START_EVT:
+        str = "StateModel::START_EVT";
+        break;
+    case END_EVT:
+        str = "StateModel::END_EVT";
+        break;
+    case FAIL_EVT:
+        str = "StateModel::FAIL_EVT";
+        break;
+    default:
+        break;
+    }
+
+    return (str);
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 555 - 0
src/bin/d2/state_model.h

@@ -0,0 +1,555 @@
+// 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 STATE_MODEL_H
+#define STATE_MODEL_H
+
+/// @file nc_trans.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>
+#include <dhcp_ddns/ncr_msg.h>
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the state machine encounters a general error.
+class StateModelError : public isc::Exception {
+public:
+    StateModelError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a pointer to an instance method for handling 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 Implements a finite state machine.
+///
+/// StateModel is an abstract class that provides the structure and mechanics
+/// of a basic finite state machine.
+///
+/// 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 require no parameters.
+/// Each model instance contains a map of states to instance method pointers
+/// to their respective state handlers.  The model tracks the following
+/// context values:
+///
+/// * state - The current state of the model
+/// * previous state -  The state the model was in prior to the current state
+/// * next event - The next event to be consumed
+/// * last event - The event most recently consumed
+///
+/// When invoked, a state handler determines what it should do based upon the
+/// next 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 "posted" from within the state handlers are "internally" triggered
+/// events.  Events "posted" from outside the state model, such as through
+/// the invocation of a callback are "externally" triggered.
+///
+/// StateModel defines two states:
+///
+/// * NEW_ST - State that a model is in following instantiation. It remains in
+/// this state until model execution has begun.
+/// * END_ST - State that a model is in once it has reached its conclusion.
+///
+/// and the following events:
+///
+/// * START_EVT - Event used to start model execution.
+/// * NOP_EVT - Event used to signify that the model stop and wait for an
+/// external event, such as the completion of an asynchronous IO operation.
+/// * END_EVT - Event used to trigger a normal conclusion of the model. This
+/// means only that the model was traversed from start to finish, without any
+/// model violations (i.e. invalid state, event, or transition) or uncaught
+/// exceptions.
+/// * FAIL_EVT - Event to trigger an abnormal conclusion of the model. This
+/// event is posted internally when model execution fails due to a model
+/// violation or uncaught exception.  It signifies that the model has reached
+/// an inoperable condition.
+///
+/// Derivations add their own states and events appropriate for their state
+/// model.  Note that NEW_ST and END_ST do not support handlers.  No work can
+/// done (events consumed) prior to starting the model nor can work be done
+/// once the model has ended.
+///
+/// Model execution consists of iteratively invoking the state handler
+/// indicated by the current state which should consume the next event. As the
+/// handlers post events and/or change the state, the model 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 may be re-entered upon arrival of the external event.
+///
+/// This loop is implemented in the runModel method.  This method accepts an
+/// event as argument which is "posts" as the next event.  It then retrieves the
+/// handler for the current state from the handler map and invokes it. runModel
+/// repeats this process until it either NOP_EVT or END_EVT events are posted.
+/// In other words each invocation of runModel causes the model to be traversed
+/// from the current state until it must wait or ends .
+///
+/// Re-entering the "loop" upon the occurrence of an external event is done by
+/// invoking runModel with the appropriate event.  As before, runModel will
+/// loop until either the NOP_EVT occurs or until the model reaches its end.
+///
+/// A StateModel (derivation) is in the NEW_ST when constructed and remains
+/// there until it has been "started".  Starting the model is done by invoking
+/// the startModel method which accepts a state as a parameter.  This parameter
+/// specifies the "start" state and it becomes the current state.
+
+/// The first task undertaken by startModel is to initialize and verify the
+/// state handler map.  Two virtual methods, initializeStateHandlerMap and
+/// verifyStateHandlerMap, are provided for the express purpose of allowing
+/// derivations to populate the state handler map and then verify that map is
+/// contents are correct.
+///
+/// Once the handler map has been properly initialized, the startModel method
+/// invokes runModel with an event of START_EVT.  From this point forward and
+/// until the model reaches the END_ST or fails, it is considered to be
+/// "running".  If the model encounters a NOP_EVT then it is "running" and
+/// "waiting".   If the model reaches END_ST with an END_EVT it is considered
+/// "done".  If the  model fails (END_ST with a FAILED_EVT) it is considered
+/// "done" and "failed".  There are several boolean status methods which may
+/// be used to check these conditions.
+///
+/// To progress from from one state to the another, state handlers invoke use
+/// the method, transition.  This method accepts a state and an a event as
+/// parameters.  These values become the current state and the next event
+/// respectively.  This has the effect of entering the given state having posted
+/// the given event.  The postEvent method is may be used to post a new event
+/// to the current state.
+///
+/// Bringing the model to a normal end is done by invoking the endModel method
+/// which transitions the model to END_ST with END_EVT.  Bringing the model to
+/// an abnormal end is done via the abortModel method, which transitions the
+/// model to END_ST with FAILED_EVT.
+class StateModel {
+public:
+
+    //@{ States common to all models.
+    /// @brief State that a state model is in immediately after construction.
+    static const int NEW_ST = 0;
+
+    /// @brief Final state, all the state model has reached its conclusion.
+    static const int END_ST = 1;
+
+    /// @define Value at which custom states in a derived class should begin.
+    static const int SM_STATE_MAX = 10;
+    //@}
+
+    //@{ Events common to all state models.
+    /// @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 issued to start the model execution.
+    static const int START_EVT = 1;
+
+    /// @brief Event issued to end the model execution.
+    static const int END_EVT = 2;
+
+    /// @brief Event issued to abort the model execution.
+    static const int FAIL_EVT = 3;
+
+    /// @define Value at which custom events in a derived class should begin.
+    static const int SM_EVENT_MAX = 10;
+    //@}
+
+    /// @brief Constructor
+    StateModel();
+
+    /// @brief Destructor
+    virtual ~StateModel();
+
+    /// @brief Begins execution of the model.
+    ///
+    /// This method invokes initStateHandlerMap() to initialize the map of state
+    /// handlers, followed by verifyStateHandlerMap which validates the map
+    /// contents. It 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.
+    /// @throw StateModelError or others indirectly, as this method calls
+    /// initializeStateHandlerMap and verifyStateHandlerMap.
+    void startModel(const int start_state);
+
+    /// @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 invoking the
+    /// the state handler for the current state.   As described above, the
+    /// invoked state handler consumes the next event and then determines the
+    /// next event and the current state as required to implement the business
+    /// logic. The method continues to loop until the next event posted is
+    /// NOP_EVT or the model ends.
+    ///
+    /// Any exception thrown during the loop is caught, logged, and the
+    /// model is aborted with a FAIL_EVT.  The derivation's state
+    /// model is expected to account for any possible errors so any that
+    /// escape are treated as unrecoverable.
+    ///
+    /// @param event is the next event to process
+    ///
+    /// This method is guaranteed not to throw.
+    void runModel(unsigned int event);
+
+    /// @brief Conducts a normal transition to the end of the model.
+    ///
+    /// This method posts an END_EVT and sets the current state to END_ST.
+    /// It is should be called by any state handler in the model from which
+    /// an exit leads to the model end.  In other words, if the transition
+    /// out of a particular state is to the end of the model, that state's
+    /// handler should call endModel.
+    void endModel();
+
+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 startModel().
+    ///
+    /// Implementations should use the addToMap() method add entries to
+    /// the map.
+    virtual void initStateHandlerMap() = 0;
+
+    /// @brief Validates the contents of the state handler map.
+    ///
+    /// This method is invoked immediately after initStateHandlerMap and
+    /// provides an opportunity for derivations to verify that the map
+    /// is correct.  If the map is determined to be invalid this method
+    /// should throw a StateModelError.
+    ///
+    /// The simplest implementation would include a call to getStateHandler,
+    /// for each state the derivation supports.  For example, a implementation
+    /// which included three states, READY_ST, DO_WORK_ST, and DONE_ST could
+    /// implement this function as follows:
+    ///
+    /// @code
+    ///    void verifyStateHandlerMap() {
+    ///        getStateHandler(READY_ST);
+    ///        getStateHandler(DO_WORK_ST);
+    ///        getStateHandler(DONE_ST);
+    ///    }
+    /// @endcode
+    ///
+    virtual void verifyStateHandlerMap() = 0;
+
+    /// @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 derivation instance.
+    /// The following code snippet shows an example derivation and call to
+    /// addToMap() within its initStateHandlerMap() method.
+    ///
+    /// @code
+    /// class ExampleModel : public StateModel {
+    /// public:
+    /// :
+    /// void readyHandler() {
+    /// }
+    ///
+    /// void initStateHandlerMap() {
+    ///     addToMap(READY_ST,
+    ///        boost::bind(&ExampleModel::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 StateModelError if the map already contains an entry
+    /// for the given state.
+    void addToMap(unsigned int state, StateHandler handler);
+
+    /// @brief Handler for fatal model execution errors.
+    ///
+    /// This method is called when an unexpected error renders during
+    /// model execution, such as a state handler throwing an exception.
+    /// It provides derivations an opportunity to act accordingly by setting
+    /// the appropriate status or taking other remedial action.   This allows
+    /// the model execution loop to remain exception safe.
+    virtual void onModelFailure() = 0;
+
+    /// @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 member pointer) for the method that
+    /// handles the given state for this model.
+    ///
+    /// @throw StateModelError
+    StateHandler getStateHandler(unsigned int state);
+
+    /// @brief Sets up the model to transition into given state with a given
+    /// event.
+    ///
+    /// This updates the model's notion of the current state and the next
+    /// event to process.  State handlers use this method to move from one state
+    /// to the next.
+    ///
+    /// @param state the new value to assign to the current state.
+    /// @param event the new value to assign to the next event.
+    ///
+    /// @throw StateModelError if the state is invalid.
+    void transition(unsigned int state, unsigned int event);
+
+    /// @brief Aborts model execution.
+    ///
+    /// This method posts a FAILED_EVT and sets the current state to END_ST.
+    /// It is called internally when a model violation occurs. Violations are
+    /// any sort of inconsistency such as attempting to reference an invalid
+    /// state, or if the next event is not valid for the current state, or a
+    /// state handler throws an uncaught exception.
+    void abortModel();
+
+    /// @brief Sets the current state to the given state value.
+    ///
+    /// This updates the model's notion of the current state and is the
+    /// state whose handler will be executed on the next iteration of the run
+    /// loop.  This is intended primarily for internal use and testing. It is
+    /// unlikely that transitioning to a new state without a new event is of
+    /// much use.
+    ///
+    /// @param state the new value to assign to the current state.
+    ///
+    /// @throw StateModelError if the state is invalid.
+    void setState(unsigned int state);
+
+    /// @brief Sets the next event to the given event value.
+    ///
+    /// This updates the model'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 event the new value to assign to the next event.
+    ///
+    /// @todo Currently there is no mechanism for validating events.
+    void postNextEvent(unsigned int event);
+
+    /// @brief Checks if on entry flag is true.
+    ///
+    /// This method acts as a one-shot test of whether or not  the model is
+    /// transitioning into a new state.  It returns true if the on-entry flag
+    /// is true upon entering this method and will set the flag false prior
+    /// to exit.  It may be used within state handlers to perform steps that
+    /// should only occur upon entry into the state.
+    ///
+    /// @return true if the on entry flag is true, false otherwise.
+    bool doOnEntry();
+
+    /// @brief Checks if on exit flag is true.
+    ///
+    /// This method acts as a one-shot test of whether or not the model is
+    /// transitioning out of the current state.  It returns true if the
+    /// on-exit flag is true upon entering this method and will set the flag
+    /// false prior to exiting.  It may be used within state handlers to perform
+    /// steps that should only occur upon exiting out of the current state.
+    ///
+    /// @return true if the on entry flag is true, false otherwise.
+    bool doOnExit();
+
+public:
+    /// @brief Fetches the model's current state.
+    ///
+    /// This returns the model'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 model's previous state.
+    ///
+    /// @return An unsigned int representing the previous state.
+    unsigned int getPrevState() const;
+
+    /// @brief Fetches the model's last event.
+    ///
+    /// @return An unsigned int representing the last event.
+    unsigned int getLastEvent() const;
+
+    /// @brief Fetches the model's next event.
+    ///
+    /// This returns the model'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 Returns whether or not the model is new.
+    ///
+    /// @return Boolean true if the model has not been started.
+    bool isModelNew() const;
+
+    /// @brief Returns whether or not the model is running.
+    ///
+    /// @return Boolean true if the model has been started but has not yet
+    /// ended.
+    bool isModelRunning() const;
+
+    /// @brief Returns whether or not the model is waiting.
+    ///
+    /// @return Boolean true if the model is running but is waiting for an
+    /// external event for resumption.
+    bool isModelWaiting() const;
+
+    /// @brief Returns whether or not the model has finished execution.
+    ///
+    /// @return Boolean true if the model has reached the END_ST.
+    bool isModelDone() const;
+
+    /// @brief Returns whether or not the model failed.
+    ///
+    /// @return Boolean true if the model has reached the END_ST and the last
+    /// event indicates a model violation, FAILED_EVT.
+    bool didModelFail() const;
+
+    /// @brief Converts a state value into a text label.
+    ///
+    /// This method supplies labels for StateModel's predefined states. It is
+    /// declared virtual to allow derivations to embed a call to this method
+    /// within their own implementation which would define labels for its
+    /// states.  An example implementation might look like the following:
+    /// @code
+    ///
+    /// class DerivedModel : public StateModel {
+    ///     :
+    ///     static const int EXAMPLE_1_ST = SM_STATE_MAX + 1;
+    ///     :
+    ///     const char* getStateLabel(const int state) const {
+    ///         const char* str = "Unknown";
+    ///         switch(state) {
+    ///         case EXAMPLE_1_ST:
+    ///             str = "DerivedModel::EXAMPLE_1_ST";
+    ///             break;
+    ///         :
+    ///         default:
+    ///             // Not a derived state, pass it down to StateModel's method.
+    ///             str = StateModel::getStateLabel(state);
+    ///             break;
+    ///         }
+    ///
+    ///         return (str);
+    ///      }
+    ///
+    /// @endcode
+    ///
+    /// @param state is the state for which a label is desired.
+    ///
+    /// @return Returns a const char* containing the state label or
+    /// "Unknown" if the value cannot be mapped.
+    virtual const char* getStateLabel(const int state) const;
+
+    /// @brief Converts a event value into a text label.
+    ///
+    /// This method supplies labels for StateModel's predefined events. It is
+    /// declared virtual to allow derivations to embed a call to this method
+    /// within their own implementation which would define labels for its
+    /// events.  An example implementation might look like the following:
+    /// @code
+    ///
+    /// class DerivedModel : public StateModel {
+    ///     :
+    ///     static const int EXAMPLE_1_EVT = SM_EVENT_MAX + 1;
+    ///     :
+    ///     const char* getEventLabel(const int event) const {
+    ///         const char* str = "Unknown";
+    ///         switch(event) {
+    ///         case EXAMPLE_1_EVT:
+    ///             str = "DerivedModel::EXAMPLE_1_EVT";
+    ///             break;
+    ///         :
+    ///         default:
+    ///             // Not a derived event, pass it down to StateModel's method.
+    ///             str = StateModel::getEventLabel(event);
+    ///             break;
+    ///         }
+    ///
+    ///         return (str);
+    ///      }
+    ///
+    /// @endcode
+    /// @param event is the event for which a label is desired.
+    ///
+    /// @return Returns a const char* containing the event label or
+    /// "Unknown" if the value cannot be mapped.
+    virtual const char* getEventLabel(const int state) const;
+
+    /// @brief Convenience method which returns a string rendition of the
+    /// current state and next event.
+    ///
+    /// The string will be of the form:
+    ///
+    ///   current state: [ {state} {label} ] next event: [ {event} {label} ]
+    ///
+    /// @return Returns a std::string of the format described above.
+    std::string getContextStr() const;
+
+    /// @brief Convenience method which returns a string rendition of the
+    /// previous state and last event.
+    ///
+    /// The string will be of the form:
+    ///
+    ///   previous state: [ {state} {label} ] last event: [ {event} {label} ]
+    ///
+    /// @return Returns a std::string of the format described above.
+    std::string getPrevContextStr() const;
+
+private:
+    /// @brief Contains a map of states to their state handlers.
+    StateHandlerMap state_handlers_;
+
+    /// @brief The current state within the model's state model.
+    unsigned int state_;
+
+    /// @brief The previous state within the model's state model.
+    unsigned int prev_state_;
+
+    /// @brief The event last processed by the model.
+    unsigned int last_event_;
+
+    /// @brief The event the model should process next.
+    unsigned int next_event_;
+
+    /// @brief Indicates if state entry logic should be executed.
+    bool on_entry_flag_;
+
+    /// @brief Indicates if state exit logic should be executed.
+    bool on_exit_flag_;
+};
+
+/// @brief Defines a pointer to a StateModel.
+typedef boost::shared_ptr<StateModel> StateModelPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif

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

@@ -65,6 +65,7 @@ d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
+d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += 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
@@ -78,6 +79,7 @@ d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += nc_trans_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
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 265 - 195
src/bin/d2/tests/nc_trans_unittests.cc

@@ -35,13 +35,10 @@ class NameChangeStub : public NameChangeTransaction {
 public:
 public:
 
 
     // NameChangeStub states
     // NameChangeStub states
-    static const int DUMMY_ST = DERIVED_STATES + 1;
+    static const int DOING_UPDATE_ST = NCT_STATE_MAX + 1;
-
-    static const int DO_WORK_ST = DERIVED_STATES + 2;
-
 
 
     // NameChangeStub events
     // NameChangeStub events
-    static const int START_WORK_EVT = DERIVED_EVENTS + 1;
+    static const int SEND_UPDATE_EVT = NCT_EVENT_MAX + 2;
 
 
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
@@ -51,84 +48,86 @@ public:
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr reverse_domain)
                    DdnsDomainPtr reverse_domain)
         : NameChangeTransaction(io_service, ncr, forward_domain,
         : NameChangeTransaction(io_service, ncr, forward_domain,
-                                reverse_domain), dummy_called_(false) {
+                                reverse_domain) {
     }
     }
 
 
     /// @brief Destructor
     /// @brief Destructor
     virtual ~NameChangeStub() {
     virtual ~NameChangeStub() {
     }
     }
 
 
-    bool getDummyCalled() {
+    /// @brief Empty handler used to statisfy map verification.
-        return (dummy_called_);
-    }
-
-    void clearDummyCalled() {
-        dummy_called_ = false;
-    }
-
     void dummyHandler() {
     void dummyHandler() {
-       dummy_called_ = true;
+        isc_throw(NameChangeTransactionError,
+                  "dummyHandler - invalid event: " << getContextStr());
     }
     }
 
 
     /// @brief State handler for the READY_ST.
     /// @brief State handler for the READY_ST.
     ///
     ///
     /// Serves as the starting state handler, it consumes the
     /// Serves as the starting state handler, it consumes the
-    /// START_TRANSACTION_EVT "transitioning" to the state, DO_WORK_ST and
+    /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and
-    /// sets the next event to START_WORK_EVT.
+    /// sets the next event to SEND_UPDATE_EVT.
     void readyHandler() {
     void readyHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case START_TRANSACTION_EVT:
+        case START_EVT:
-            setState(DO_WORK_ST);
+            transition(DOING_UPDATE_ST, SEND_UPDATE_EVT);
-            setNextEvent(START_WORK_EVT);
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "readyHandler - invalid event: " << getContextStr());
         }
         }
     }
     }
 
 
-    /// @brief State handler for the DO_WORK_ST.
+    /// @brief State handler for the DOING_UPDATE_ST.
     ///
     ///
     /// Simulates a state that starts some form of asynchronous work.
     /// Simulates a state that starts some form of asynchronous work.
-    /// When next event is START_WORK_EVT it sets the status to pending
+    /// When next event is SEND_UPDATE_EVT it sets the status to pending
     /// and signals the state model must "wait" for an event by setting
     /// and signals the state model must "wait" for an event by setting
     /// next event to NOP_EVT.
     /// next event to NOP_EVT.
     ///
     ///
     /// When next event is IO_COMPLETED_EVT, it transitions to the state,
     /// When next event is IO_COMPLETED_EVT, it transitions to the state,
-    /// DONE_ST, and sets the next event to ALL_DONE_EVT.
+    /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT.
-    void doWorkHandler() {
+    void doingUpdateHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case START_WORK_EVT:
+        case SEND_UPDATE_EVT:
             setNcrStatus(dhcp_ddns::ST_PENDING);
             setNcrStatus(dhcp_ddns::ST_PENDING);
-            setNextEvent(NOP_EVT);
+            postNextEvent(NOP_EVT);
             break;
             break;
-        //case WORK_DONE_EVT:
         case IO_COMPLETED_EVT:
         case IO_COMPLETED_EVT:
-            setState(DONE_ST);
+            if (getDnsUpdateStatus() == DNSClient::SUCCESS) {
-            setNextEvent(ALL_DONE_EVT);
+                setForwardChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "doingUpdateHandler - invalid event: "
+                      << getContextStr());
         }
         }
     }
     }
 
 
-    /// @brief State handler for the DONE_ST.
+    /// @brief State handler for the PROCESS_TRANS_OK_ST.
     ///
     ///
     /// This is the last state in the model.  Note that it sets the
     /// This is the last state in the model.  Note that it sets the
     /// status to completed and next event to NOP_EVT.
     /// status to completed and next event to NOP_EVT.
-    void doneHandler() {
+    void processTransDoneHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case ALL_DONE_EVT:
+        case UPDATE_OK_EVT:
             setNcrStatus(dhcp_ddns::ST_COMPLETED);
             setNcrStatus(dhcp_ddns::ST_COMPLETED);
-            setNextEvent(NOP_EVT);
+            endModel();
+            break;
+        case UPDATE_FAILED_EVT:
+            setNcrStatus(dhcp_ddns::ST_FAILED);
+            endModel();
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "processTransDoneHandler - invalid event: "
+                      << getContextStr());
         }
         }
     }
     }
 
 
@@ -137,26 +136,63 @@ public:
         addToMap(READY_ST,
         addToMap(READY_ST,
             boost::bind(&NameChangeStub::readyHandler, this));
             boost::bind(&NameChangeStub::readyHandler, this));
 
 
-        addToMap(DO_WORK_ST,
+        addToMap(SELECTING_FWD_SERVER_ST,
-            boost::bind(&NameChangeStub::doWorkHandler, this));
+            boost::bind(&NameChangeStub::dummyHandler, this));
+
+        addToMap(SELECTING_REV_SERVER_ST,
+            boost::bind(&NameChangeStub::dummyHandler, this));
+
+        addToMap(DOING_UPDATE_ST,
+            boost::bind(&NameChangeStub::doingUpdateHandler, this));
 
 
-        addToMap(DONE_ST,
+        addToMap(PROCESS_TRANS_OK_ST,
-            boost::bind(&NameChangeStub::doneHandler, this));
+            boost::bind(&NameChangeStub::processTransDoneHandler, this));
+
+        addToMap(PROCESS_TRANS_FAILED_ST,
+            boost::bind(&NameChangeStub::processTransDoneHandler, this));
     }
     }
 
 
     void verifyStateHandlerMap() {
     void verifyStateHandlerMap() {
         getStateHandler(READY_ST);
         getStateHandler(READY_ST);
-        getStateHandler(DO_WORK_ST);
+        getStateHandler(DOING_UPDATE_ST);
-        getStateHandler(DONE_ST);
+        // Call base class verification.
+        NameChangeTransaction::verifyStateHandlerMap();
+    }
+
+    const char* getStateLabel(const int state) const {
+        const char* str = "Unknown";
+        switch(state) {
+        case NameChangeStub::DOING_UPDATE_ST:
+            str = "NameChangeStub::DOING_UPDATE_ST";
+            break;
+        default:
+            str = NameChangeTransaction::getStateLabel(state);
+            break;
+        }
+
+        return (str);
     }
     }
 
 
+    const char* getEventLabel(const int event) const {
+        const char* str = "Unknown";
+        switch(event) {
+        case NameChangeStub::SEND_UPDATE_EVT:
+            str = "NameChangeStub::SEND_UPDATE_EVT";
+            break;
+        default:
+            str = NameChangeTransaction::getEventLabel(event);
+            break;
+        }
+
+        return (str);
+    }
+
+
+
     // Expose the protected methods to be tested.
     // Expose the protected methods to be tested.
-    using NameChangeTransaction::addToMap;
+    using StateModel::runModel;
-    using NameChangeTransaction::getStateHandler;
+    using StateModel::getStateHandler;
-    using NameChangeTransaction::initStateHandlerMap;
+
-    using NameChangeTransaction::runStateModel;
-    using NameChangeTransaction::setState;
-    using NameChangeTransaction::setNextEvent;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getCurrentServer;
@@ -168,14 +204,9 @@ public:
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
-
-    bool dummy_called_;
 };
 };
 
 
-const int NameChangeStub::DO_WORK_ST;
+/// @brief Defines a pointer to a NameChangeStubPtr instance.
-const int NameChangeStub::START_WORK_EVT;
-
-/// @brief Defines a pointer to a D2UpdateMgr instance.
 typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 
 
 /// @brief Test fixture for testing NameChangeTransaction
 /// @brief Test fixture for testing NameChangeTransaction
@@ -333,26 +364,11 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_FALSE(name_change->getDNSClient());
     EXPECT_FALSE(name_change->getDNSClient());
     EXPECT_FALSE(name_change->getCurrentServer());
     EXPECT_FALSE(name_change->getCurrentServer());
 
 
-    // Previous state should be set by setState.
-    EXPECT_NO_THROW(name_change->setState(NameChangeTransaction::READY_ST));
-    EXPECT_NO_THROW(name_change->setState(NameChangeStub::DO_WORK_ST));
-    EXPECT_EQ(NameChangeTransaction::READY_ST, name_change->getPrevState());
-    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
-
-    // Last event should be set by setNextEvent.
-    EXPECT_NO_THROW(name_change->setNextEvent(NameChangeStub::
-                                              START_WORK_EVT));
-    EXPECT_NO_THROW(name_change->setNextEvent(NameChangeTransaction::
-                                              IO_COMPLETED_EVT));
-    EXPECT_EQ(NameChangeStub::START_WORK_EVT, name_change->getLastEvent());
-    EXPECT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
-              name_change->getNextEvent());
-
     // Verify that DNS update status can be set and retrieved.
     // Verify that DNS update status can be set and retrieved.
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
 
 
-    // Verify that the DNS update response can be retrieved. 
+    // Verify that the DNS update response can be retrieved.
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
 
 
     // Verify that the forward change complete flag can be set and fetched.
     // Verify that the forward change complete flag can be set and fetched.
@@ -364,57 +380,16 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
 }
 }
 
 
-/// @brief Tests the fundamental methods used for state handler mapping.
-/// Verifies the ability to search for and add entries in the state handler map.
-TEST_F(NameChangeTransactionTest, basicStateMapping) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Verify that getStateHandler will throw when, state cannot be found.
-    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::
-                                                      dummyHandler,
-                                                      name_change.get())));
-
-    // Verify that we can find the handler by its state.
-    StateHandler retreived_handler;
-    EXPECT_NO_THROW(retreived_handler =
-                    name_change->getStateHandler(NameChangeTransaction::
-                                                 READY_ST));
-
-    // Verify that retrieved handler executes the correct method.
-    name_change->clearDummyCalled();
-
-    ASSERT_NO_THROW((retreived_handler)());
-    EXPECT_TRUE(name_change->getDummyCalled());
-
-    // 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));
-}
-
 /// @brief Tests state map initialization and validation.
 /// @brief Tests state map initialization and validation.
 /// This tests the basic concept of state map initialization and verification
 /// This tests the basic concept of state map initialization and verification
-/// by manually invoking the map methods normally called by startTransaction.
+/// by manually invoking the map methods normally by StateModel::startModel.
 TEST_F(NameChangeTransactionTest, stubStateMapInit) {
 TEST_F(NameChangeTransactionTest, stubStateMapInit) {
     NameChangeStubPtr name_change;
     NameChangeStubPtr name_change;
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
 
 
     // Verify that the map validation throws prior to the map being
     // Verify that the map validation throws prior to the map being
     // initialized.
     // initialized.
-    ASSERT_THROW(name_change->verifyStateHandlerMap(),
+    ASSERT_THROW(name_change->verifyStateHandlerMap(), StateModelError);
-                 NameChangeTransactionError);
 
 
     // Call initStateHandlerMap to initialize the state map.
     // Call initStateHandlerMap to initialize the state map.
     ASSERT_NO_THROW(name_change->initStateHandlerMap());
     ASSERT_NO_THROW(name_change->initStateHandlerMap());
@@ -423,85 +398,6 @@ TEST_F(NameChangeTransactionTest, stubStateMapInit) {
     ASSERT_NO_THROW(name_change->verifyStateHandlerMap());
     ASSERT_NO_THROW(name_change->verifyStateHandlerMap());
 }
 }
 
 
-/// @brief Tests that invalid states are handled gracefully.
-/// This test verifies that attempting to execute a state which has no handler
-/// results in a failed transaction.
-TEST_F(NameChangeTransactionTest, invalidState) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Verfiy that to running the model with a state that has no handler,
-    // will result in failed transaction (status of ST_FAILED).
-    // First, verify state is NEW_ST and that NEW_ST has no handler.
-    // that the transaction failed:
-    ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState());
-    ASSERT_THROW(name_change->getStateHandler(NameChangeTransaction::NEW_ST),
-                 NameChangeTransactionError);
-
-    // Now call runStateModel() which should not throw.
-    EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction::
-                                               START_TRANSACTION_EVT));
-
-    // Verify that the transaction has failed.
-    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
-}
-
-/// @brief Tests that invalid events are handled gracefully.
-/// This test verifies that submitting an invalid event to the state machine
-/// results in a failed transaction.
-TEST_F(NameChangeTransactionTest, invalidEvent) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // First, lets execute the state model to a known valid point, by
-    // calling startTransaction.
-    ASSERT_NO_THROW(name_change->startTransaction());
-
-    // Verify we are in the state of DO_WORK_ST.
-    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
-
-    // Verity that submitting an invalid event to a valid state, results
-    // in a failed transaction without a throw (Current state is DO_WORK_ST,
-    // during which START_TRANSACTION_EVT, is invalid).
-    EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction::
-                                               START_TRANSACTION_EVT));
-
-    // Verify that the transaction has failed.
-    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
-}
-
-/// @brief Test the basic mechanics of state model execution.
-/// This test exercises the ability to execute state model from state to
-/// finish, including the handling of a asynchronous IO operation.
-TEST_F(NameChangeTransactionTest, stateModelTest) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Launch the transaction by calling startTransaction.  The state model
-    // should run up until the "IO" operation is initiated in DO_WORK_ST.
-
-    ASSERT_NO_THROW(name_change->startTransaction());
-
-    // Verify that we are now in state of DO_WORK_ST, the last event was
-    // START_WORK_EVT, the next event is NOP_EVT, and NCR status is ST_PENDING.
-    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, as
-    // DNSClient would.  This should cause the state model to progress through
-    // completion.
-    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());
-}
-
 /// @brief Tests server selection methods.
 /// @brief Tests server selection methods.
 /// Each transaction has a list of one or more servers for each DNS direction
 /// Each transaction has a list of one or more servers for each DNS direction
 /// it is required to update.  The transaction must be able to start at the
 /// it is required to update.  The transaction must be able to start at the
@@ -618,4 +514,178 @@ TEST_F(NameChangeTransactionTest, serverSelectionTest) {
     EXPECT_EQ (passes, num_servers);
     EXPECT_EQ (passes, num_servers);
 }
 }
 
 
+/// @brief Tests the ability to decode state values into text labels.
+TEST_F(NameChangeTransactionTest, stateLabels) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Verify StateModel labels.
+    EXPECT_EQ("StateModel::NEW_ST",
+              name_change->getStateLabel(StateModel::NEW_ST));
+    EXPECT_EQ("StateModel::END_ST",
+              name_change->getStateLabel(StateModel::END_ST));
+
+    // Verify NameChangeTransaction labels
+    EXPECT_EQ("NameChangeTransaction::READY_ST",
+              name_change->getStateLabel(NameChangeTransaction::READY_ST));
+    EXPECT_EQ("NameChangeTransaction::SELECTING_FWD_SERVER_ST",
+              name_change->getStateLabel(NameChangeTransaction::
+                                        SELECTING_FWD_SERVER_ST));
+    EXPECT_EQ("NameChangeTransaction::SELECTING_REV_SERVER_ST",
+              name_change->getStateLabel(NameChangeTransaction::
+                                         SELECTING_REV_SERVER_ST));
+    EXPECT_EQ("NameChangeTransaction::PROCESS_TRANS_OK_ST",
+              name_change->getStateLabel(NameChangeTransaction::
+                                         PROCESS_TRANS_OK_ST));
+    EXPECT_EQ("NameChangeTransaction::PROCESS_TRANS_FAILED_ST",
+              name_change->getStateLabel(NameChangeTransaction::
+                                         PROCESS_TRANS_FAILED_ST));
+
+    // Verify Stub states
+    EXPECT_EQ("NameChangeStub::DOING_UPDATE_ST",
+              name_change->getStateLabel(NameChangeStub::DOING_UPDATE_ST));
+
+    // Verify unknown state.
+    EXPECT_EQ("Unknown", name_change->getStateLabel(-1));
+}
+
+/// @brief Tests the ability to decode event values into text labels.
+TEST_F(NameChangeTransactionTest, eventLabels) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Verify StateModel labels.
+    EXPECT_EQ("StateModel::NOP_EVT",
+              name_change->getEventLabel(StateModel::NOP_EVT));
+    EXPECT_EQ("StateModel::START_EVT",
+              name_change->getEventLabel(StateModel::START_EVT));
+    EXPECT_EQ("StateModel::END_EVT",
+              name_change->getEventLabel(StateModel::END_EVT));
+    EXPECT_EQ("StateModel::FAIL_EVT",
+              name_change->getEventLabel(StateModel::FAIL_EVT));
+
+    // Verify NameChangeTransactionLabels
+    EXPECT_EQ("NameChangeTransaction::SELECT_SERVER_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         SELECT_SERVER_EVT));
+    EXPECT_EQ("NameChangeTransaction::SERVER_SELECTED_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         SERVER_SELECTED_EVT));
+    EXPECT_EQ("NameChangeTransaction::SERVER_IO_ERROR_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         SERVER_IO_ERROR_EVT));
+    EXPECT_EQ("NameChangeTransaction::NO_MORE_SERVERS_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         NO_MORE_SERVERS_EVT));
+    EXPECT_EQ("NameChangeTransaction::IO_COMPLETED_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         IO_COMPLETED_EVT));
+    EXPECT_EQ("NameChangeTransaction::UPDATE_OK_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         UPDATE_OK_EVT));
+    EXPECT_EQ("NameChangeTransaction::UPDATE_FAILED_EVT",
+              name_change->getEventLabel(NameChangeTransaction::
+                                         UPDATE_FAILED_EVT));
+
+    // Verify stub class labels.
+    EXPECT_EQ("NameChangeStub::SEND_UPDATE_EVT",
+              name_change->getEventLabel(NameChangeStub::SEND_UPDATE_EVT));
+
+    // Verify unknown state.
+    EXPECT_EQ("Unknown", name_change->getEventLabel(-1));
+}
+
+/// @brief Tests that the transaction will be "failed" upon model errors.
+TEST_F(NameChangeTransactionTest, modelFailure) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // We will cause a model failure by attempting to submit an event to
+    // NEW_ST.  Let's make sure that state is NEW_ST and that NEW_ST has no
+    // handler.
+    ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState());
+    ASSERT_THROW(name_change->getStateHandler(NameChangeTransaction::NEW_ST),
+                 StateModelError);
+
+    // Now call runStateModel() which should not throw.
+    EXPECT_NO_THROW(name_change->runModel(NameChangeTransaction::START_EVT));
+
+    // Verify that the model reports are done but failed.
+    EXPECT_TRUE(name_change->isModelDone());
+    EXPECT_TRUE(name_change->didModelFail());
+
+    // Verify that the transaction has failed.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+}
+
+/// @brief Tests the ability to use startTransaction to initate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// the model with a update successful outcome.
+TEST_F(NameChangeTransactionTest, successfulUpdateTest) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    EXPECT_TRUE(name_change->isModelNew());
+    EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+    // Launch the transaction by calling startTransaction.  The state model
+    // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+    ASSERT_NO_THROW(name_change->startTransaction());
+
+    // Verify that the model is running but waiting, and that forward change
+    // completion is still false.
+    EXPECT_TRUE(name_change->isModelRunning());
+    EXPECT_TRUE(name_change->isModelWaiting());
+    EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+    // Simulate completion of DNSClient exchange by invoking the callback, as
+    // DNSClient would.  This should cause the state model to progress through
+    // completion.
+    EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS));
+
+    // The model should have worked through to completion.
+    // Verify that the model is done and not failed.
+    EXPECT_TRUE(name_change->isModelDone());
+    EXPECT_FALSE(name_change->didModelFail());
+
+    // Verify that NCR status is completed, and that the forward change
+    // was completed.
+    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus());
+    EXPECT_TRUE(name_change->getForwardChangeCompleted());
+}
+
+/// @brief Tests the ability to use startTransaction to initate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// the model with a update failure outcome.
+TEST_F(NameChangeTransactionTest, failedUpdateTest) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Launch the transaction by calling startTransaction.  The state model
+    // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+    ASSERT_NO_THROW(name_change->startTransaction());
+
+    // Vefity that the model is running but waiting, and that the forward
+    // change has not been completed.
+    EXPECT_TRUE(name_change->isModelRunning());
+    EXPECT_TRUE(name_change->isModelWaiting());
+    EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+    // Simulate completion of DNSClient exchange by invoking the callback, as
+    // DNSClient would.  This should cause the state model to progress through
+    // to completion.
+    EXPECT_NO_THROW((*name_change)(DNSClient::TIMEOUT));
+
+    // The model should have worked through to completion.
+    // Verify that the model is done and not failed.
+    EXPECT_TRUE(name_change->isModelDone());
+    EXPECT_FALSE(name_change->didModelFail());
+
+    // Verify that the NCR status is failed and that the forward change
+    // was not completed.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+    EXPECT_FALSE(name_change->getForwardChangeCompleted());
+}
+
+
 }
 }

+ 660 - 0
src/bin/d2/tests/state_model_unittests.cc

@@ -0,0 +1,660 @@
+// 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/state_model.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 StateModel for exercising state model mechanics.
+///
+/// This class facilitates 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 StateModelTest : public StateModel, public testing::Test {
+public:
+
+    ///@brief StateModelTest states
+    ///@brief Fake state used for handler mapping tests.
+    static const int DUMMY_ST = SM_STATE_MAX + 1;
+
+    ///@brief Starting state for the test state model.
+    static const int READY_ST = SM_STATE_MAX + 2;
+
+    ///@brief State which simulates doing asynchronous work.
+    static const int DO_WORK_ST = SM_STATE_MAX + 3;
+
+    ///@brief State which finishes off processing.
+    static const int DONE_ST = SM_STATE_MAX + 4;
+
+    // StateModelTest events
+    ///@brief Event used to trigger initiation of asynchronous work.
+    static const int WORK_START_EVT = SM_EVENT_MAX + 1;
+
+    ///@brief Event issued when the asynchronous work "completes".
+    static const int WORK_DONE_EVT = SM_EVENT_MAX + 2;
+
+    ///@brief Event issued when all the work is done.
+    static const int ALL_DONE_EVT = SM_EVENT_MAX + 3;
+
+    /// @brief Constructor
+    ///
+    /// Parameters match those needed by StateModel.
+    StateModelTest() : dummy_called_(false), model_failure_called_(false),
+                      work_completed_(false) {
+    }
+    /// @brief Destructor
+    virtual ~StateModelTest() {
+    }
+
+    /// @brief Fetches the value of the dummy called flag.
+    bool getDummyCalled() {
+        return (dummy_called_);
+    }
+
+    /// @brief StateHandler for fake state, DummyState.
+    ///
+    /// It simply sets the dummy called flag to indicate that this method
+    /// was invoked.
+    void dummyHandler() {
+        dummy_called_ = true;
+    }
+
+    /// @brief Returns indication of whether or not the model failed to execute.
+    ///
+    /// If true, this indicates that the test model suffered an error
+    /// during execution such as an invalid state, event, or exception thrown by
+    /// a state handler. The flag is only set true by onModelFailure() method.
+    bool getModelFailureCalled() {
+        return (model_failure_called_);
+    }
+
+    /// @brief Returns indication of whether or not the model succeeded.
+    ///
+    /// If true, this indicates that the test model executed correctly through
+    /// to completion.  The flag is only by the DONE_ST handler.
+    bool getWorkCompleted() {
+        return (work_completed_);
+    }
+
+    /// @brief State handler for the READY_ST.
+    ///
+    /// Serves as the starting state handler, it consumes the
+    /// START_EVT "transitioning" to the state, DO_WORK_ST and
+    /// sets the next event to WORK_START_EVT.
+    void readyHandler() {
+        switch(getNextEvent()) {
+        case START_EVT:
+            transition(DO_WORK_ST, WORK_START_EVT);
+            break;
+        default:
+            // its bogus
+            isc_throw(StateModelError, "readyHandler:invalid event: "
+                      << getContextStr());
+        }
+    }
+
+    /// @brief State handler for the DO_WORK_ST.
+    ///
+    /// Simulates a state that starts some form of asynchronous work.
+    /// When next event is WORK_START_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 WORK_DONE_EVT.
+    void doWorkHandler() {
+        switch(getNextEvent()) {
+        case WORK_START_EVT:
+            postNextEvent(NOP_EVT);
+            break;
+        case WORK_DONE_EVT:
+            work_completed_ = true;
+            transition(DONE_ST, ALL_DONE_EVT);
+            break;
+        default:
+            // its bogus
+            isc_throw(StateModelError, "doWorkHandler:invalid event: "
+                      <<  getContextStr());
+        }
+    }
+
+    /// @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 doneWorkHandler() {
+        switch(getNextEvent()) {
+        case ALL_DONE_EVT:
+            endModel();
+            break;
+        default:
+            // its bogus
+            isc_throw(StateModelError, "doneWorkHandler:invalid event: "
+                      << getContextStr());
+        }
+    }
+
+    /// @brief Initializes the state handler map.
+    virtual void initStateHandlerMap() {
+        addToMap(DUMMY_ST,
+            boost::bind(&StateModelTest::dummyHandler, this));
+
+        addToMap(READY_ST,
+            boost::bind(&StateModelTest::readyHandler, this));
+
+        addToMap(DO_WORK_ST,
+            boost::bind(&StateModelTest::doWorkHandler, this));
+
+        addToMap(DONE_ST,
+            boost::bind(&StateModelTest::doneWorkHandler, this));
+    }
+
+    /// @brief Validates the contents of the state handler map.
+    virtual void verifyStateHandlerMap() {
+        getStateHandler(DUMMY_ST);
+        getStateHandler(READY_ST);
+        getStateHandler(DO_WORK_ST);
+        getStateHandler(DONE_ST);
+    }
+
+    /// @brief  Handler called when the model suffers an execution error.
+    virtual void onModelFailure() {
+        model_failure_called_ = true;
+    }
+
+    const char* getStateLabel(const int state) const {
+        const char* str = "Unknown";
+        switch(state) {
+        case DUMMY_ST:
+            str = "StateModelTest::DUMMY_ST";
+            break;
+        case READY_ST:
+            str = "StateModelTest::READY_ST";
+            break;
+        case DO_WORK_ST:
+            str = "StateModelTest::DO_WORK_ST";
+            break;
+        case DONE_ST:
+            str = "StateModelTest::DONE_ST";
+            break;
+        default:
+            str = StateModel::getStateLabel(state);
+            break;
+        }
+
+        return (str);
+    }
+
+    const char* getEventLabel(const int event) const {
+        const char* str = "Unknown";
+        switch(event) {
+        case WORK_START_EVT:
+            str = "StateModelTest::WORK_START_EVT";
+            break;
+        case WORK_DONE_EVT:
+            str = "StateModelTest::WORK_DONE_EVT";
+            break;
+        case ALL_DONE_EVT :
+            str = "StateModelTest::ALL_DONE_EVT";
+            break;
+        default:
+            str = StateModel::getEventLabel(event);
+            break;
+        }
+
+        return (str);
+    }
+
+    /// @brief Indicator of whether or not the DUMMY_ST handler has been called.
+    bool dummy_called_;
+
+    /// @brief Indicator of whether or not onModelError has been called.
+    bool model_failure_called_;
+
+    /// @brief Indicator of whether or not DONE_ST handler has been called.
+    bool work_completed_;
+};
+
+/// @brief Tests the fundamental methods used for state handler mapping.
+/// Verifies the ability to search for and add entries in the state handler map.
+TEST_F(StateModelTest, basicStateMapping) {
+    // After construction, the state map should be empty. Verify that
+    // getStateHandler will throw when, state cannot be found.
+    EXPECT_THROW(getStateHandler(READY_ST), StateModelError);
+
+    // Verify that we can add a handler to the map.
+    ASSERT_NO_THROW(addToMap(READY_ST, boost::bind(&StateModelTest::
+                                                   dummyHandler, this)));
+
+    // Verify that we can find the handler by its state.
+    StateHandler retreived_handler;
+    EXPECT_NO_THROW(retreived_handler = getStateHandler(READY_ST));
+
+    // Now we will Verify that retrieved handler executes the correct method.
+    // Make sure the dummy called flag is false prior to invocation.
+    EXPECT_FALSE(getDummyCalled());
+
+    // Invoke the retreived handler.
+    ASSERT_NO_THROW((retreived_handler)());
+
+    // Verify the dummy called flag is now true.
+    EXPECT_TRUE(getDummyCalled());
+
+    // Verify that we cannot add a duplicate.
+    EXPECT_THROW(addToMap(READY_ST, boost::bind(&StateModelTest::readyHandler,
+                                                this)),
+                 StateModelError);
+
+    // Verify that we can still find the handler by its state.
+    EXPECT_NO_THROW(getStateHandler(READY_ST));
+
+
+    // Verify that we cannot add a handler for NEW_ST.
+    EXPECT_THROW(addToMap(NEW_ST, boost::bind(&StateModelTest::dummyHandler,
+                                              this)),
+                 StateModelError);
+
+    // Verify that we cannot add a handler for END_ST.
+    EXPECT_THROW(addToMap(END_ST, boost::bind(&StateModelTest::dummyHandler,
+                                              this)),
+                 StateModelError);
+}
+
+/// @brief Tests state map initialization and validation.
+/// This tests the basic concept of state map initialization and verification
+/// by manually invoking the map methods normally called by startModel.
+TEST_F(StateModelTest, stateMapInit) {
+    // Verify that the map validation throws prior to the map being
+    // initialized.
+    EXPECT_THROW(verifyStateHandlerMap(), StateModelError);
+
+    // Call initStateHandlerMap to initialize the state map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+
+    // Verify that the map validation succeeds now that the map is initialized.
+    EXPECT_NO_THROW(verifyStateHandlerMap());
+}
+
+/// @brief Tests the ability to decode state values into text labels.
+TEST_F(StateModelTest, stateLabels) {
+    // Verify base class labels.
+    EXPECT_EQ("StateModel::NEW_ST", getStateLabel(NEW_ST));
+    EXPECT_EQ("StateModel::END_ST", getStateLabel(END_ST));
+
+    // Verify stub class labels.
+    EXPECT_EQ("StateModelTest::DUMMY_ST", getStateLabel(DUMMY_ST));
+    EXPECT_EQ("StateModelTest::READY_ST", getStateLabel(READY_ST));
+    EXPECT_EQ("StateModelTest::DO_WORK_ST", getStateLabel(DO_WORK_ST));
+    EXPECT_EQ("StateModelTest::DONE_ST", getStateLabel(DONE_ST));
+
+    // Verify unknown state.
+    EXPECT_EQ("Unknown", getStateLabel(-1));
+}
+
+/// @brief Tests the ability to decode event values into text labels.
+TEST_F(StateModelTest, eventLabels) {
+    // Verify base class labels.
+    EXPECT_EQ("StateModel::NOP_EVT", getEventLabel(NOP_EVT));
+    EXPECT_EQ("StateModel::START_EVT", getEventLabel(START_EVT));
+    EXPECT_EQ("StateModel::END_EVT", getEventLabel(END_EVT));
+    EXPECT_EQ("StateModel::FAIL_EVT", getEventLabel(FAIL_EVT));
+
+    // Verify stub class labels.
+    EXPECT_EQ("StateModelTest::WORK_START_EVT", getEventLabel(WORK_START_EVT));
+    EXPECT_EQ("StateModelTest::WORK_DONE_EVT", getEventLabel(WORK_DONE_EVT));
+    EXPECT_EQ("StateModelTest::ALL_DONE_EVT", getEventLabel(ALL_DONE_EVT));
+
+    // Verify unknown state.
+    EXPECT_EQ("Unknown", getEventLabel(-1));
+}
+
+/// @brief General testing of member accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, stateAccessors) {
+    // setState will throw unless we initialize the handler map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // Verify post-construction state values.
+    EXPECT_EQ(NEW_ST, getState());
+    EXPECT_EQ(NEW_ST, getPrevState());
+
+    // Call setState which will update both state and previous state.
+    EXPECT_NO_THROW(setState(READY_ST));
+
+    // Verify the values are what we expect.
+    EXPECT_EQ(READY_ST, getState());
+    EXPECT_EQ(NEW_ST, getPrevState());
+
+    // Call setState again.
+    EXPECT_NO_THROW(setState(DO_WORK_ST));
+
+    // Verify the values are what we expect.
+    EXPECT_EQ(DO_WORK_ST, getState());
+    EXPECT_EQ(READY_ST, getPrevState());
+
+    // Verify that calling setState with an state that has no handler
+    // will throw.
+    EXPECT_THROW(setState(-1), StateModelError);
+
+    // Verify that calling setState with NEW_ST is ok.
+    EXPECT_NO_THROW(setState(NEW_ST));
+
+    // Verify that calling setState with END_ST is ok.
+    EXPECT_NO_THROW(setState(END_ST));
+}
+
+TEST_F(StateModelTest, eventAccessors) {
+    // Verify post-construction event values.
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+    EXPECT_EQ(NOP_EVT, getLastEvent());
+
+    // Call setEvent which will update both next event and last event.
+    EXPECT_NO_THROW(postNextEvent(START_EVT));
+
+    // Verify the values are what we expect.
+    EXPECT_EQ(START_EVT, getNextEvent());
+    EXPECT_EQ(NOP_EVT, getLastEvent());
+
+    // Call setEvent again.
+    EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
+    EXPECT_EQ(WORK_START_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+
+    // Verify the values are what we expect.
+}
+
+TEST_F(StateModelTest, transitionWithEnd) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    //  Verify that state and event members are as expected.
+    EXPECT_EQ(DUMMY_ST, getState());
+    EXPECT_EQ(NEW_ST, getPrevState());
+    EXPECT_EQ(START_EVT, getNextEvent());
+    EXPECT_EQ(NOP_EVT, getLastEvent());
+
+    // Call endModel to transition us to the end of the model.
+    EXPECT_NO_THROW(endModel());
+
+    // Verify state and event members are correctly set.
+    EXPECT_EQ(END_ST, getState());
+    EXPECT_EQ(DUMMY_ST, getPrevState());
+    EXPECT_EQ(END_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+TEST_F(StateModelTest, transitionWithAbort) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    //  Verify that state and event members are as expected.
+    EXPECT_EQ(DUMMY_ST, getState());
+    EXPECT_EQ(NEW_ST, getPrevState());
+    EXPECT_EQ(START_EVT, getNextEvent());
+    EXPECT_EQ(NOP_EVT, getLastEvent());
+
+    // Call endModel to transition us to the end of the model.
+    EXPECT_NO_THROW(abortModel());
+
+    // Verify state and event members are correctly set.
+    EXPECT_EQ(END_ST, getState());
+    EXPECT_EQ(DUMMY_ST, getPrevState());
+    EXPECT_EQ(FAIL_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+TEST_F(StateModelTest, doFlags) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // Verify that "do" flags are false.
+    EXPECT_FALSE(doOnEntry());
+    EXPECT_FALSE(doOnExit());
+
+    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    // We are transitioning states, so "do" flags should be true.
+    EXPECT_TRUE(doOnEntry());
+    EXPECT_TRUE(doOnExit());
+
+    // "do" flags are one-shots, so they should now both be false.
+    EXPECT_FALSE(doOnEntry());
+    EXPECT_FALSE(doOnExit());
+
+    // call transition to re-enter same state, "do" flags should be false.
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    // "do" flags should be false.
+    EXPECT_FALSE(doOnEntry());
+    EXPECT_FALSE(doOnExit());
+
+}
+
+TEST_F(StateModelTest, statusMethods) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // After construction, state model is "new", all others should be false.
+    EXPECT_TRUE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_FALSE(isModelDone());
+    EXPECT_FALSE(didModelFail());
+
+    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    // The state and event combos set above, should show the model as
+    // "running", all others should be false.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_TRUE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_FALSE(isModelDone());
+    EXPECT_FALSE(didModelFail());
+
+    // call transition to submit NOP_EVT to current state, DUMMY_ST.
+    EXPECT_NO_THROW(transition(DUMMY_ST, NOP_EVT));
+
+    // Verify the status methods are correct: with next event set to NOP_EVT,
+    // model should be "running" and "waiting".
+    EXPECT_FALSE(isModelNew());
+    EXPECT_TRUE(isModelRunning());
+    EXPECT_TRUE(isModelWaiting());
+    EXPECT_FALSE(isModelDone());
+    EXPECT_FALSE(didModelFail());
+
+    // Call endModel to transition us to the end of the model.
+    EXPECT_NO_THROW(endModel());
+
+    // With state set to END_ST, model should be done.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_TRUE(isModelDone());
+    EXPECT_FALSE(didModelFail());
+}
+
+TEST_F(StateModelTest, statusMethodsOnFailure) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+    // Call endModel to transition us to the end of the model.
+    EXPECT_NO_THROW(abortModel());
+
+    // With state set to END_ST, model should be done.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_TRUE(isModelDone());
+    EXPECT_TRUE(didModelFail());
+}
+
+TEST_F(StateModelTest, contextStrs) {
+    // Manually init the handlers map.
+    ASSERT_NO_THROW(initStateHandlerMap());
+    ASSERT_NO_THROW(verifyStateHandlerMap());
+
+    // transition uses setState and setEvent, testing it tests all three.
+    EXPECT_NO_THROW(transition(READY_ST, START_EVT));
+
+    std::string ctx_str = getContextStr();
+    EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(READY_ST)));
+    EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(START_EVT)));
+
+    ctx_str = getPrevContextStr();
+    EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(NEW_ST)));
+    EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(NOP_EVT)));
+}
+
+/// @brief Tests that invalid states are handled gracefully.
+/// This test verifies that attempting to execute a state which has no handler
+/// results in a failed model.
+TEST_F(StateModelTest, invalidState) {
+    // First, verify state is NEW_ST and that NEW_ST has no handler.
+    // This is the state that runModel will attempt to execute.
+    ASSERT_EQ(NEW_ST, getState());
+    ASSERT_THROW(getStateHandler(NEW_ST), StateModelError);
+
+    // Verify that the StateModelTest's outcome flags are both false.
+    EXPECT_FALSE(getModelFailureCalled());
+    EXPECT_FALSE(getWorkCompleted());
+
+    // Now call runModel() which should not throw, but should result
+    // in a failed model and a call to onModelFailure().
+    EXPECT_NO_THROW(runModel(START_EVT));
+
+    // Verify that status methods are correct: model is done but failed.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_TRUE(isModelDone());
+    EXPECT_TRUE(didModelFail());
+
+    // Verify that model failure flag is true.
+    EXPECT_TRUE(getModelFailureCalled());
+
+    // Verify that work completed flag is still false.
+    EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that invalid events are handled gracefully.
+/// This test verifies that submitting an invalid event to the state machine
+/// results in a failed transaction.
+TEST_F(StateModelTest, invalidEvent) {
+    // Verify that the StateModelTest's outcome flags are both false.
+    EXPECT_FALSE(getModelFailureCalled());
+    EXPECT_FALSE(getWorkCompleted());
+
+    // First, lets execute the state model to a known valid point, by
+    // calling startModel with a start state of READY_ST.
+    // This should run the model through to DO_WORK_ST.
+    ASSERT_NO_THROW(startModel(READY_ST));
+
+    // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+    EXPECT_EQ(DO_WORK_ST, getState());
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+
+    // Submitting an invalid event to a valid state, should cause runModel to
+    // return without throwing and yield a failed model. (Current state is
+    // DO_WORK_ST, during which START_EVT, is invalid).
+    EXPECT_NO_THROW(runModel(START_EVT));
+
+    // Verify that status methods are correct: model is done but failed.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_TRUE(isModelDone());
+    EXPECT_TRUE(didModelFail());
+
+    // Verify that model failure flag is true.
+    EXPECT_TRUE(getModelFailureCalled());
+
+    // Verify that work completed flag is still false.
+    EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Test the basic mechanics of state model execution.
+/// This test exercises the ability to execute state model from state to
+/// finish, including the handling of a asynchronous IO operation.
+TEST_F(StateModelTest, stateModelTest) {
+    // Verify that status methods are correct: model is new.
+    EXPECT_TRUE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_FALSE(isModelDone());
+
+    // Verify that the StateModelTest's outcome flags are both false.
+    EXPECT_FALSE(getModelFailureCalled());
+    EXPECT_FALSE(getWorkCompleted());
+
+    // Launch the transaction by calling startModel.  The state model
+    // should run up until the simulated async work operation is initiated
+    // in DO_WORK_ST.
+    ASSERT_NO_THROW(startModel(READY_ST));
+
+    // Verify that we are now in state of DO_WORK_ST, the last event was
+    // WORK_START_EVT, the next event is NOP_EVT.
+    EXPECT_EQ(DO_WORK_ST, getState());
+    EXPECT_EQ(WORK_START_EVT, getLastEvent());
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+
+    // Simulate completion of async work completion by resuming runModel with
+    // an event of WORK_DONE_EVT.
+    ASSERT_NO_THROW(runModel(WORK_DONE_EVT));
+
+    // 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(END_ST, getState());
+    EXPECT_EQ(END_EVT, getNextEvent());
+
+    // Verify that status methods are correct: model done.
+    EXPECT_FALSE(isModelNew());
+    EXPECT_FALSE(isModelRunning());
+    EXPECT_FALSE(isModelWaiting());
+    EXPECT_TRUE(isModelDone());
+
+    // Verify that model failure flag is false.
+    EXPECT_FALSE(getModelFailureCalled());
+
+    // Verify that work completed flag is true.
+    EXPECT_TRUE(getWorkCompleted());
+}
+
+}