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 += dns_client.cc dns_client.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
 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
 to shut down by the controller.
 
-% DHCP_DDNS_TRANS_PROCESS_EROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
-This is error message issued when the application fails to process a 
+% 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
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be
-reported. 
+reported.

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

@@ -19,17 +19,15 @@ namespace isc {
 namespace d2 {
 
 // Common transaction states
-const int NameChangeTransaction::NEW_ST;
 const int NameChangeTransaction::READY_ST;
 const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
 const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
-const int NameChangeTransaction::DONE_ST;
+const int NameChangeTransaction::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
-const int NameChangeTransaction::NOP_EVT;
-const int NameChangeTransaction::START_TRANSACTION_EVT;
 const int NameChangeTransaction::SELECT_SERVER_EVT;
 const int NameChangeTransaction::SERVER_SELECTED_EVT;
 const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
@@ -37,18 +35,16 @@ const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
 const int NameChangeTransaction::IO_COMPLETED_EVT;
 const int NameChangeTransaction::UPDATE_OK_EVT;
 const int NameChangeTransaction::UPDATE_FAILED_EVT;
-const int NameChangeTransaction::ALL_DONE_EVT;
 
-const int NameChangeTransaction::DERIVED_EVENTS;
+const int NameChangeTransaction::NCT_EVENT_MAX;
 
 NameChangeTransaction::
 NameChangeTransaction(isc::asiolink::IOService& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_domain)
-    : state_handlers_(), io_service_(io_service), ncr_(ncr),
-     forward_domain_(forward_domain), reverse_domain_(reverse_domain),
-     dns_client_(), state_(NEW_ST), next_event_(NOP_EVT),
+    : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
+     reverse_domain_(reverse_domain), dns_client_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      forward_change_completed_(false), reverse_change_completed_(false),
      current_server_list_(), current_server_(), next_server_pos_(0) {
@@ -65,10 +61,6 @@ NameChangeTransaction(isc::asiolink::IOService& io_service,
         isc_throw(NameChangeTransactionError,
                  "Reverse change must have a reverse domain");
     }
-
-    // Use setters here so we get proper values for previous state, last event.
-    setState(state_);
-    setNextEvent(NOP_EVT);
 }
 
 NameChangeTransaction::~NameChangeTransaction(){
@@ -76,85 +68,32 @@ NameChangeTransaction::~NameChangeTransaction(){
 
 void
 NameChangeTransaction::startTransaction() {
-    // 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 READY and enter the run loop.
-    setState(READY_ST);
-    runStateModel(START_TRANSACTION_EVT);
+    startModel(READY_ST);
 }
 
 void
 NameChangeTransaction::operator()(DNSClient::Status status) {
     // Stow the completion status and re-enter the run loop with the event
     // set to indicate IO completed.
-    // runStateModel is exception safe so we are good to call it here.
+    // 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.
     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
-NameChangeTransaction::setState(unsigned int state) {
-    prev_state_ = state_;
-    state_ = state;
+NameChangeTransaction::verifyStateHandlerMap() {
+    getStateHandler(READY_ST);
+    getStateHandler(SELECTING_FWD_SERVER_ST);
+    getStateHandler(SELECTING_REV_SERVER_ST);
+    getStateHandler(PROCESS_TRANS_OK_ST);
+    getStateHandler(PROCESS_TRANS_FAILED_ST);
 }
 
 void
-NameChangeTransaction::setNextEvent(unsigned int event) {
-    last_event_ = next_event_;
-    next_event_ = event;
+NameChangeTransaction::onModelFailure() {
+    setNcrStatus(dhcp_ddns::ST_FAILED);
 }
 
 void
@@ -244,26 +183,6 @@ NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(status));
 }
 
-unsigned int
-NameChangeTransaction::getState() const {
-    return (state_);
-}
-
-unsigned int
-NameChangeTransaction::getPrevState() const {
-    return (prev_state_);
-}
-
-unsigned int
-NameChangeTransaction::getLastEvent() const {
-    return (last_event_);
-}
-
-unsigned int
-NameChangeTransaction::getNextEvent() const {
-    return (next_event_);
-}
-
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
@@ -284,6 +203,66 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     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

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

@@ -21,6 +21,7 @@
 #include <exceptions/exceptions.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
+#include <d2/state_model.h>
 #include <dhcp_ddns/ncr_msg.h>
 
 #include <boost/shared_ptr.hpp>
@@ -29,7 +30,7 @@
 namespace isc {
 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 {
 public:
     NameChangeTransactionError(const char* file, size_t line,
@@ -40,17 +41,12 @@ public:
 /// @brief Defines the type used as the unique key for transactions.
 typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
 
-/// @brief Defines a function pointer for the handler method for a state.
-typedef boost::function<void()> StateHandler;
-
-/// @brief Defines a map of states to their handler methods.
-typedef std::map<unsigned int, StateHandler> StateHandlerMap;
-
 /// @brief Embodies the "life-cycle" required to carry out a DDNS update.
 ///
 /// NameChangeTransaction is the base class that provides the common state
 /// model mechanics and services performing the DNS updates needed to carry out
-/// a DHCP_DDNS request as described by a NameChangeRequest.
+/// 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
 /// 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,
 /// through a callback.  At each point in a transaction's state model, where
 /// an update is to be sent, the model "suspends" until notified by the
-/// DNSClient via the callback.
-///
-/// The state model implementation used is a very basic approach. States
-/// and events are simple integer constants. Each state must have a state
-/// handler. State handlers are void methods which accept an event as their
-/// only parameter.  Each transaction instance contains a map of states to
-/// to bound method pointers to their respective state handlers.
-///
-/// When invoked, the handler determines what it should do based upon the event,
-/// including what the next state and event should be. In other words the state
-/// transition knowledge is distributed among the state handlers rather than
-/// encapsulated in some form of state transition table.  Events set from within
-/// the state handlers are "internally" triggered events.  Events set from
-/// outside the state model, such as through the DNSClient completion callback
-/// are "externally" triggered.
-///
-/// Executing the model consists of iteratively invoking the state handler
-/// indicated by the current state and passing it the current event.  As the
-/// handlers update the state and event, the machine is traversed. The loop
-/// "stops" whenever the model cannot continue without an externally triggered
-/// event or when it has reached its final state.  In the case of the former,
-/// the loop is re-entered upon arrival of the external event.
+/// DNSClient via the callbacka.  Suspension is done by posting a
+/// StateModel::NOP_EVT as the next event, stopping the state model execution.
 ///
-/// This loop is implemented in the runStateModel method.  This method accepts
-/// an event as argument.  This event is treated as the "next event" to process
-/// and is fed to the current state's handler.  The runStateModel does not exit
-/// until a handler sets the next event to a special value, NOP_EVT,
-/// indicating that either it is now waiting for IO to complete of the state
-/// model has reached its conclusion.
-///
-/// Re-entering the "loop" when a DNS update completes is done by a call to
-/// runStateModel() from within the DNSClient callback, with an event value
-/// of IO_COMPLETED_EVT.  As above, runStateModel() will loop until either the
-/// next IO is issued or the state model has reached its conclusion.
+/// Resuming state model execution when a DNS update completes is done by a
+/// call to StateModel::runStateModel() from within the DNSClient callback,
+/// with an event value of IO_COMPLETED_EVT (described below).
 ///
 /// This class defines a set of events and states that are a common to all
 /// transactions. Each derivation may add define additional states and events
 /// as needed, but it must support the common set.  NameChangeTransaction
 /// does not supply any state handlers.  These are the sole responsibility of
 /// derivations.
-class NameChangeTransaction : public DNSClient::Callback {
+class NameChangeTransaction : public DNSClient::Callback, public StateModel {
 public:
 
     //@{ States common to all transactions.
-    /// @brief State a transaction is in immediately after construction.
-    static const int NEW_ST = 0;
+
     /// @brief State from which a transaction is started.
-    static const int READY_ST = 1;
+    static const int READY_ST = SM_STATE_MAX + 1;
+
     /// @brief State in which forward DNS server selection is done.
     ///
     /// Within this state, the actual selection of the next forward 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
     /// 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.
     ///
@@ -135,51 +103,43 @@ public:
     /// 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
     /// 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 DONE_ST = 4;
+    static const int PROCESS_TRANS_FAILED_ST = SM_STATE_MAX + 5;
 
     /// @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.
-    /// @brief Signifies that no event has occurred.
-    /// This is event used to interrupt the event loop to allow waiting for
-    /// an IO event or when there is no more work to be done.
-    static const int NOP_EVT = 0;
-    /// @brief Event used to start the transaction.
-    static const int START_TRANSACTION_EVT = 1;
     /// @brief Issued when a server needs to be selected.
-    static const int SELECT_SERVER_EVT = 2;
+    static const int SELECT_SERVER_EVT = SM_STATE_MAX + 1;
     /// @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.
-    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.
     /// This occurs when none of the servers in the list can be reached to
     /// perform the update.
-    static const int NO_MORE_SERVERS_EVT = 5;
+    static const int NO_MORE_SERVERS_EVT =SM_EVENT_MAX +  4;
     /// @brief Issued when a DNS update packet exchange has completed.
     /// This occurs whenever the DNSClient callback is invoked whether the
     /// exchange was successful or not.
-    static const int IO_COMPLETED_EVT = 6;
+    static const int IO_COMPLETED_EVT = SM_EVENT_MAX + 5;
     /// @brief Issued when the attempted update successfully completed.
     /// This occurs when an DNS update packet was successfully processed
     /// by the server.
-    static const int UPDATE_OK_EVT = 7;
+    static const int UPDATE_OK_EVT = SM_EVENT_MAX + 6;
     /// @brief Issued when the attempted update fails to complete.
     /// This occurs when an DNS update packet fails to process. The nature of
     /// the failure is given by the DNSClient return status and the response
     /// packet (if one was received).
-    static const int UPDATE_FAILED_EVT = 8;
-    /// @brief Issued when the state model has no more work left to do.
-    static const int ALL_DONE_EVT = 9;
+    static const int UPDATE_FAILED_EVT = SM_EVENT_MAX + 7;
 
     /// @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
@@ -204,10 +164,9 @@ public:
 
     /// @brief Begins execution of the transaction.
     ///
-    /// This method invokes initHandlersMap() to initialize the map of state
-    /// handlers. It then starts the transaction's state model by setting the
-    /// current state to READY_ST and invoking runStateModel() with an event
-    /// parameter of START_TRANSACTION_EVT.
+    /// This method invokes StateModel::startModel() with a value of READY_ST.
+    /// This causes transaction's state model to attempt to begin execution
+    /// with the state handler for READY_ST.
     void startTransaction();
 
     /// @brief Serves as the DNSClient IO completion event handler.
@@ -237,113 +196,49 @@ protected:
     /// D2UpdateManager will not compile.
     virtual void initStateHandlerMap() {};
 
-
     /// @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 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:
+    /// verifies that the state map includes handlers for all of the states
+    /// defined by NameChangeTransaction.  If the map is determined to be
+    /// invalid this method will throw a NameChangeTransactionError.
     ///
-    /// @code
-    ///    void verifyStateHandlerMap() {
-    ///        getStateHandler(READY_ST);
-    ///        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.
+    /// Derivations should ALSO provide an implementation of this method. That
+    /// implementation should invoke this method, as well as verifying that all
+    /// of the derivation's states have handlers.
     ///
-    /// This method attempts to add an entry to the handler map which maps
-    /// the given handler to the given state.  The state handler must be
-    /// a bound member pointer to a handler method of the transaction instance.
-    /// The following code snippet shows an example derivation and call to
-    /// addToMap() within its initStateHandlerMap() method.
+    /// A derivation's implementation of this function might look as follows:
     ///
     /// @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
-    /// the given event as the next event to process and begins looping by
-    /// passing it the state handler for the current state.   As described
-    /// above, the invoked state handler determines the current state and the
-    /// next event required to implement the business logic. The method
-    /// continues to loop until next event is set to NOP_EVT, at which point
-    /// the method exits.
+    ///     class DerivedTrans : public NameChangeTransaction {
+    ///         :
+    ///         void verifyStateHandlerMap() {
+    ///             // Verify derivations' states:
+    ///             getStateHandler(SOME_DERIVED_STATE_1);
+    ///             getStateHandler(SOME_DERIVED_STATE_2);
+    ///             :
+    ///             getStateHandler(SOME_DERIVED_STATE_N);
     ///
-    /// Any exception thrown during the loop is caught, logged, and the
-    /// transaction is immediately set to failed status.  The state model is
-    /// expected to account for any possible errors so any that escape are
-    /// treated as unrecoverable in terms of the current transaction.
+    ///             // Verify handlers for NameChangeTransaction states:
+    ///             NameChangeTransaction::verifyStateHandlerMap();
+    ///         }
     ///
-    /// @param event is the next event to process
-    ///
-    /// This is guaranteed not to throw.
-    void runStateModel(unsigned int event);
-
-    /// @brief Return the state handler for a given state.
-    ///
-    /// This method looks up the state handler for the given state from within
-    /// the state handler map.
-    ///
-    /// @param state is the state constant of the desired handler.
-    ///
-    /// @return A StateHandler (bound method pointer) for the method that
-    /// handles the given state for this transaction.
-    ///
-    /// @throw NameChangeTransactionError
-    StateHandler getStateHandler(unsigned int state);
-
-    /// @brief Sets the current state to the given state value.
-    ///
-    /// This updates the transaction's notion of the current state and is the
-    /// state whose handler will be executed on the next iteration of the run
-    /// loop.
+    /// @endcode
     ///
-    /// @param state the new value to assign to the current state.
-    void setState(unsigned int state);
+    /// @throw NameChangeTransactionError if the map is invalid.
+    virtual void verifyStateHandlerMap();
 
-    /// @brief Sets the next event to the given event value.
-    ///
-    /// This updates the transaction's notion of the next event and is the
-    /// event that will be passed into the current state's handler on the next
-    /// iteration of the run loop.
+    /// @brief Handler for fatal model execution errors.
     ///
-    /// @param state the new value to assign to the current state.
-    void setNextEvent(unsigned int event);
+    /// This handler is called by the StateModel implementation when the model
+    /// 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.
     ///
@@ -441,34 +336,6 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     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.
     ///
     /// @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.
     bool getReverseChangeCompleted() const;
 
-private:
-    /// @brief Contains a map of states to their state handlers.
-    StateHandlerMap state_handlers_;
+    /// @brief Converts a state value into a text label.
+    ///
+    /// 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.
     isc::asiolink::IOService& io_service_;
 
@@ -526,18 +465,6 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     DNSClientPtr dns_client_;
 
-    /// @brief The current state within the transaction's state model.
-    unsigned int state_;
-
-    /// @brief The previous state within the transaction's state model.
-    unsigned int prev_state_;
-
-    /// @brief The event last processed by the transaction.
-    unsigned int last_event_;
-
-    /// @brief The event the transaction should process next.
-    unsigned int next_event_;
-
     /// @brief The outcome of the most recently completed DNS packet exchange.
     DNSClient::Status dns_update_status_;
 

+ 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 += ../dns_client.cc ../dns_client.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 += d2_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 += dns_client_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
 
 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:
 
     // NameChangeStub states
-    static const int DUMMY_ST = DERIVED_STATES + 1;
-
-    static const int DO_WORK_ST = DERIVED_STATES + 2;
-
+    static const int DOING_UPDATE_ST = NCT_STATE_MAX + 1;
 
     // NameChangeStub events
-    static const int START_WORK_EVT = DERIVED_EVENTS + 1;
+    static const int SEND_UPDATE_EVT = NCT_EVENT_MAX + 2;
 
     /// @brief Constructor
     ///
@@ -51,84 +48,86 @@ public:
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr reverse_domain)
         : NameChangeTransaction(io_service, ncr, forward_domain,
-                                reverse_domain), dummy_called_(false) {
+                                reverse_domain) {
     }
 
     /// @brief Destructor
     virtual ~NameChangeStub() {
     }
 
-    bool getDummyCalled() {
-        return (dummy_called_);
-    }
-
-    void clearDummyCalled() {
-        dummy_called_ = false;
-    }
-
+    /// @brief Empty handler used to statisfy map verification.
     void dummyHandler() {
-       dummy_called_ = true;
+        isc_throw(NameChangeTransactionError,
+                  "dummyHandler - invalid event: " << getContextStr());
     }
 
     /// @brief State handler for the READY_ST.
     ///
     /// Serves as the starting state handler, it consumes the
-    /// START_TRANSACTION_EVT "transitioning" to the state, DO_WORK_ST and
-    /// sets the next event to START_WORK_EVT.
+    /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and
+    /// sets the next event to SEND_UPDATE_EVT.
     void readyHandler() {
         switch(getNextEvent()) {
-        case START_TRANSACTION_EVT:
-            setState(DO_WORK_ST);
-            setNextEvent(START_WORK_EVT);
+        case START_EVT:
+            transition(DOING_UPDATE_ST, SEND_UPDATE_EVT);
             break;
         default:
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
-                      << getNextEvent() << " for state: " << getState());
+            isc_throw(NameChangeTransactionError,
+                      "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.
-    /// 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
     /// next event to NOP_EVT.
     ///
     /// When next event is IO_COMPLETED_EVT, it transitions to the state,
-    /// DONE_ST, and sets the next event to ALL_DONE_EVT.
-    void doWorkHandler() {
+    /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT.
+    void doingUpdateHandler() {
         switch(getNextEvent()) {
-        case START_WORK_EVT:
+        case SEND_UPDATE_EVT:
             setNcrStatus(dhcp_ddns::ST_PENDING);
-            setNextEvent(NOP_EVT);
+            postNextEvent(NOP_EVT);
             break;
-        //case WORK_DONE_EVT:
         case IO_COMPLETED_EVT:
-            setState(DONE_ST);
-            setNextEvent(ALL_DONE_EVT);
+            if (getDnsUpdateStatus() == DNSClient::SUCCESS) {
+                setForwardChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
             break;
         default:
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
-                      << getNextEvent() << " for state: " << getState());
+            isc_throw(NameChangeTransactionError,
+                      "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
     /// status to completed and next event to NOP_EVT.
-    void doneHandler() {
+    void processTransDoneHandler() {
         switch(getNextEvent()) {
-        case ALL_DONE_EVT:
+        case UPDATE_OK_EVT:
             setNcrStatus(dhcp_ddns::ST_COMPLETED);
-            setNextEvent(NOP_EVT);
+            endModel();
+            break;
+        case UPDATE_FAILED_EVT:
+            setNcrStatus(dhcp_ddns::ST_FAILED);
+            endModel();
             break;
         default:
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
-                      << getNextEvent() << " for state: " << getState());
+            isc_throw(NameChangeTransactionError,
+                      "processTransDoneHandler - invalid event: "
+                      << getContextStr());
         }
     }
 
@@ -137,26 +136,63 @@ public:
         addToMap(READY_ST,
             boost::bind(&NameChangeStub::readyHandler, this));
 
-        addToMap(DO_WORK_ST,
-            boost::bind(&NameChangeStub::doWorkHandler, this));
+        addToMap(SELECTING_FWD_SERVER_ST,
+            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,
-            boost::bind(&NameChangeStub::doneHandler, this));
+        addToMap(PROCESS_TRANS_OK_ST,
+            boost::bind(&NameChangeStub::processTransDoneHandler, this));
+
+        addToMap(PROCESS_TRANS_FAILED_ST,
+            boost::bind(&NameChangeStub::processTransDoneHandler, this));
     }
 
     void verifyStateHandlerMap() {
         getStateHandler(READY_ST);
-        getStateHandler(DO_WORK_ST);
-        getStateHandler(DONE_ST);
+        getStateHandler(DOING_UPDATE_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.
-    using NameChangeTransaction::addToMap;
-    using NameChangeTransaction::getStateHandler;
-    using NameChangeTransaction::initStateHandlerMap;
-    using NameChangeTransaction::runStateModel;
-    using NameChangeTransaction::setState;
-    using NameChangeTransaction::setNextEvent;
+    using StateModel::runModel;
+    using StateModel::getStateHandler;
+
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
@@ -168,14 +204,9 @@ public:
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
-
-    bool dummy_called_;
 };
 
-const int NameChangeStub::DO_WORK_ST;
-const int NameChangeStub::START_WORK_EVT;
-
-/// @brief Defines a pointer to a D2UpdateMgr instance.
+/// @brief Defines a pointer to a NameChangeStubPtr instance.
 typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 
 /// @brief Test fixture for testing NameChangeTransaction
@@ -333,26 +364,11 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_FALSE(name_change->getDNSClient());
     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.
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     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());
 
     // 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());
 }
 
-/// @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.
 /// 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) {
     NameChangeStubPtr name_change;
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
 
     // Verify that the map validation throws prior to the map being
     // initialized.
-    ASSERT_THROW(name_change->verifyStateHandlerMap(),
-                 NameChangeTransactionError);
+    ASSERT_THROW(name_change->verifyStateHandlerMap(), StateModelError);
 
     // Call initStateHandlerMap to initialize the state map.
     ASSERT_NO_THROW(name_change->initStateHandlerMap());
@@ -423,85 +398,6 @@ TEST_F(NameChangeTransactionTest, stubStateMapInit) {
     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.
 /// 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
@@ -618,4 +514,178 @@ TEST_F(NameChangeTransactionTest, serverSelectionTest) {
     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());
+}
+
+}