Browse Source

[master] Merge branch 'trac3156'

Extracts the d2 finite state machine implementation from within
d2::NameChangeTransacation into its own class d2::StateModel.
Thomas Markwalder 11 years ago
parent
commit
6e9227b1b1

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

@@ -62,7 +62,9 @@ b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
+b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 EXTRA_DIST += d2_messages.mes

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

@@ -199,7 +199,7 @@ public:
     /// output:
     /// output:
     ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
     ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
     ///
     ///
-    /// @param address string containing a valid IPv6 address.
+    /// @param ioaddr string containing a valid IPv6 address.
     ///
     ///
     /// @return a std::string containing the reverse order address.
     /// @return a std::string containing the reverse order address.
     ///
     ///

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

@@ -253,8 +253,8 @@ in event loop.
 This is informational message issued when the application has been instructed
 This is informational message issued when the application has been instructed
 to shut down by the controller.
 to shut down by the controller.
 
 
-% DHCP_DDNS_TRANS_PROCESS_EROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
-This is error message issued when the application fails to process a 
+This is error message issued when the application fails to process a
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be
 of this update did not succeed. This is a programmatic error and should be
-reported. 
+reported.

+ 123 - 0
src/bin/d2/labeled_value.cc

@@ -0,0 +1,123 @@
+// 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/labeled_value.h>
+
+namespace isc {
+namespace d2 {
+
+/**************************** LabeledValue ****************************/
+
+LabeledValue::LabeledValue(const int value, const std::string& label)
+    : value_(value), label_(label) {
+    if (label.empty()) {
+        isc_throw(LabeledValueError, "labels cannot be empty");
+    }
+}
+
+LabeledValue::~LabeledValue(){
+}
+
+int
+LabeledValue::getValue() const {
+    return (value_);
+}
+
+std::string
+LabeledValue::getLabel() const {
+    return (label_);
+}
+
+bool
+LabeledValue::operator==(const LabeledValue& other) const {
+    return (this->value_ == other.value_);
+}
+
+bool
+LabeledValue::operator!=(const LabeledValue& other) const {
+    return (this->value_ != other.value_);
+}
+
+bool
+LabeledValue::operator<(const LabeledValue& other) const {
+    return (this->value_ < other.value_);
+}
+
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp) {
+    os << vlp.getLabel();
+    return (os);
+}
+
+/**************************** LabeledValueSet ****************************/
+
+const char* LabeledValueSet::UNDEFINED_LABEL = "UNDEFINED";
+
+LabeledValueSet::LabeledValueSet(){
+}
+
+LabeledValueSet::~LabeledValueSet() {
+}
+
+void
+LabeledValueSet::add(LabeledValuePtr entry) {
+    if (!entry) {
+        isc_throw(LabeledValueError, "cannot add an null entry to set");
+    }
+
+    const int value = entry->getValue();
+    if (isDefined(value)) {
+        isc_throw(LabeledValueError,
+                  "value: " << value << " is already defined as: "
+                  << getLabel(value));
+        }
+
+    map_[entry->getValue()]=entry;
+}
+
+void
+LabeledValueSet::add(const int value, const std::string& label) {
+    add (LabeledValuePtr(new LabeledValue(value,label)));
+}
+
+const LabeledValuePtr&
+LabeledValueSet::get(int value) {
+    static LabeledValuePtr undefined;
+    LabeledValueMap::iterator it = map_.find(value);
+    if (it != map_.end()) {
+        return ((*it).second);
+    }
+
+    // Return an empty pointer when not found.
+    return (undefined);
+}
+
+bool
+LabeledValueSet::isDefined(const int value) const {
+    LabeledValueMap::const_iterator it = map_.find(value);
+    return (it != map_.end());
+}
+
+std::string
+LabeledValueSet::getLabel(const int value) const {
+    LabeledValueMap::const_iterator it = map_.find(value);
+    if (it != map_.end()) {
+        const LabeledValuePtr& ptr = (*it).second;
+        return (ptr->getLabel());
+    }
+
+    return (std::string(UNDEFINED_LABEL));
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 184 - 0
src/bin/d2/labeled_value.h

@@ -0,0 +1,184 @@
+// 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 LABELED_VALUE_H
+#define LABELED_VALUE_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <ostream>
+#include <string>
+#include <map>
+
+/// @file labeled_value.h This file defines classes: LabeledValue and
+/// LabeledValueSet.
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if an error is encountered handling a LabeledValue.
+class LabeledValueError : public isc::Exception {
+public:
+    LabeledValueError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Implements the concept of a constant value with a text label.
+///
+/// This class implements an association between an constant integer value
+/// and a text label. It provides a single constructor, accessors for both
+/// the value and label, and boolean operators which treat the value as
+/// the "key" for comparisons.  This allows them to be assembled into
+/// dictionaries of unique values.  Note, that the labels are not required to
+/// be unique but in practice it makes little sense to for them to be
+/// otherwise.
+class LabeledValue {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param value the numeric constant value to be labeled.
+    /// @param label the text label to associate to this value.
+    ///
+    /// @throw LabeledValueError if label is empty.
+    LabeledValue(const int value, const std::string& label);
+
+    /// @brief Destructor.
+    ///
+    /// Destructor is virtual to permit derivations.
+    virtual ~LabeledValue();
+
+    /// @brief Gets the integer value of this instance.
+    ///
+    /// @return integer value of this instance.
+    int getValue() const;
+
+    /// @brief Gets the text label of this instance.
+    ///
+    /// @return The text label as string
+    std::string getLabel() const;
+
+    /// @brief  Equality operator
+    ///
+    /// @return True if a.value_ is equal to b.value_.
+    bool operator==(const LabeledValue& other) const;
+
+    /// @brief  Inequality operator
+    ///
+    /// @return True if a.value_ is not equal to b.value_.
+    bool operator!=(const LabeledValue& other) const;
+
+    /// @brief  Less-than operator
+    ///
+    /// @return True if a.value_ is less than b.value_.
+    bool operator<(const LabeledValue& other) const;
+
+private:
+    /// @brief The numeric value to label.
+    int value_;
+
+    /// @brief The text label for the value.
+    std::string label_;
+};
+
+/// @brief Dumps the label to ostream.
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp);
+
+/// @brief Defines a shared pointer to a LabeledValue instance.
+typedef boost::shared_ptr<LabeledValue> LabeledValuePtr;
+
+/// @brief Defines a map of pointers to LabeledValues keyed by value.
+typedef std::map<unsigned int, LabeledValuePtr> LabeledValueMap;
+
+
+/// @brief Implements a set of unique LabeledValues.
+///
+/// This class is intended to function as a dictionary of numeric values
+/// and the labels associated with them.  It is essentially a thin wrapper
+/// around a std::map of LabeledValues, keyed by their values.  This is handy
+/// for defining a set of "valid" constants while conveniently associating a
+/// text label with each value.
+///
+/// This class offers two variants of an add method for adding entries to the
+/// set, and accessors for finding an entry or an entry's label by value.
+/// Note that the add methods may throw but all accessors are exception safe.
+/// It is up to the caller to determine when and if an undefined value is
+/// exception-worthy.
+///
+/// More interestingly, a derivation of this class can be used to "define"
+/// valid instances of derivations of LabeledValue.
+class LabeledValueSet {
+public:
+    /// @brief Defines a text label returned by when value is not found.
+    static const char* UNDEFINED_LABEL;
+
+    /// @brief Constructor
+    ///
+    /// Constructs an empty set.
+    LabeledValueSet();
+
+    /// @brief Destructor
+    ///
+    /// Destructor is virtual to permit derivations.
+    virtual ~LabeledValueSet();
+
+    /// @brief Adds the given entry to the set
+    ///
+    /// @param entry is the entry to add.
+    ///
+    /// @throw LabeledValuePtr if the entry is null or the set already
+    /// contains an entry with the same value.
+    void add(LabeledValuePtr entry);
+
+    /// @brief Adds an entry to the set for the given value and label
+    ///
+    /// @param value the numeric constant value to be labeled.
+    /// @param label the text label to associate to this value.
+    ///
+    /// @throw LabeledValuePtr if the label is empty, or if the set
+    /// already contains an entry with the same value.
+    void add(const int value, const std::string& label);
+
+    /// @brief Fetches a pointer to the entry associated with value
+    ///
+    /// @param value is the value of the entry desired.
+    ///
+    /// @return A pointer to the entry if the entry was found otherwise the
+    /// pointer is empty.
+    const LabeledValuePtr& get(int value);
+
+    /// @brief Tests if the set contains an entry for the given value.
+    ///
+    /// @param value is the value of the entry to test.
+    ///
+    /// @return True if an entry for value exists in the set, false if not.
+    bool isDefined(const int value) const;
+
+    /// @brief Fetches the label for the given value
+    ///
+    /// @param value is the value for which the label is desired.
+    ///
+    /// @return the label of the value if defined, otherwise it returns
+    /// UNDEFINED_LABEL.
+    std::string getLabel(const int value) const;
+
+private:
+    /// @brief The map of labeled values.
+    LabeledValueMap map_;
+};
+
+} // namespace isc::d2
+} // namespace isc
+#endif

+ 52 - 91
src/bin/d2/nc_trans.cc

@@ -19,17 +19,15 @@ namespace isc {
 namespace d2 {
 namespace d2 {
 
 
 // Common transaction states
 // Common transaction states
-const int NameChangeTransaction::NEW_ST;
 const int NameChangeTransaction::READY_ST;
 const int NameChangeTransaction::READY_ST;
 const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
 const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
 const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
 const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
-const int NameChangeTransaction::DONE_ST;
+const int NameChangeTransaction::PROCESS_TRANS_OK_ST;
+const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST;
 
 
-const int NameChangeTransaction::DERIVED_STATES;
+const int NameChangeTransaction::NCT_DERIVED_STATE_MIN;
 
 
 // Common transaction events
 // Common transaction events
-const int NameChangeTransaction::NOP_EVT;
-const int NameChangeTransaction::START_TRANSACTION_EVT;
 const int NameChangeTransaction::SELECT_SERVER_EVT;
 const int NameChangeTransaction::SELECT_SERVER_EVT;
 const int NameChangeTransaction::SERVER_SELECTED_EVT;
 const int NameChangeTransaction::SERVER_SELECTED_EVT;
 const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
 const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
@@ -37,18 +35,16 @@ const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
 const int NameChangeTransaction::IO_COMPLETED_EVT;
 const int NameChangeTransaction::IO_COMPLETED_EVT;
 const int NameChangeTransaction::UPDATE_OK_EVT;
 const int NameChangeTransaction::UPDATE_OK_EVT;
 const int NameChangeTransaction::UPDATE_FAILED_EVT;
 const int NameChangeTransaction::UPDATE_FAILED_EVT;
-const int NameChangeTransaction::ALL_DONE_EVT;
 
 
-const int NameChangeTransaction::DERIVED_EVENTS;
+const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
 
 
 NameChangeTransaction::
 NameChangeTransaction::
 NameChangeTransaction(isc::asiolink::IOService& io_service,
 NameChangeTransaction(isc::asiolink::IOService& io_service,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       dhcp_ddns::NameChangeRequestPtr& ncr,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& forward_domain,
                       DdnsDomainPtr& reverse_domain)
                       DdnsDomainPtr& reverse_domain)
-    : state_handlers_(), io_service_(io_service), ncr_(ncr),
+    : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
-     forward_domain_(forward_domain), reverse_domain_(reverse_domain),
+     reverse_domain_(reverse_domain), dns_client_(),
-     dns_client_(), state_(NEW_ST), next_event_(NOP_EVT),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      dns_update_status_(DNSClient::OTHER), dns_update_response_(),
      forward_change_completed_(false), reverse_change_completed_(false),
      forward_change_completed_(false), reverse_change_completed_(false),
      current_server_list_(), current_server_(), next_server_pos_(0) {
      current_server_list_(), current_server_(), next_server_pos_(0) {
@@ -65,10 +61,6 @@ NameChangeTransaction(isc::asiolink::IOService& io_service,
         isc_throw(NameChangeTransactionError,
         isc_throw(NameChangeTransactionError,
                  "Reverse change must have a reverse domain");
                  "Reverse change must have a reverse domain");
     }
     }
-
-    // Use setters here so we get proper values for previous state, last event.
-    setState(state_);
-    setNextEvent(NOP_EVT);
 }
 }
 
 
 NameChangeTransaction::~NameChangeTransaction(){
 NameChangeTransaction::~NameChangeTransaction(){
@@ -76,85 +68,75 @@ NameChangeTransaction::~NameChangeTransaction(){
 
 
 void
 void
 NameChangeTransaction::startTransaction() {
 NameChangeTransaction::startTransaction() {
-    // Initialize the state handler map first.
+    startModel(READY_ST);
-    initStateHandlerMap();
-
-    // Test validity of the handler map. This provides an opportunity to
-    // sanity check the map prior to attempting to execute the model.
-    verifyStateHandlerMap();
-
-    // Set the current state to READY and enter the run loop.
-    setState(READY_ST);
-    runStateModel(START_TRANSACTION_EVT);
 }
 }
 
 
 void
 void
 NameChangeTransaction::operator()(DNSClient::Status status) {
 NameChangeTransaction::operator()(DNSClient::Status status) {
     // Stow the completion status and re-enter the run loop with the event
     // Stow the completion status and re-enter the run loop with the event
     // set to indicate IO completed.
     // set to indicate IO completed.
-    // runStateModel is exception safe so we are good to call it here.
+    // runModel is exception safe so we are good to call it here.
     // It won't exit until we hit the next IO wait or the state model ends.
     // It won't exit until we hit the next IO wait or the state model ends.
     setDnsUpdateStatus(status);
     setDnsUpdateStatus(status);
-    runStateModel(IO_COMPLETED_EVT);
+    runModel(IO_COMPLETED_EVT);
 }
 }
 
 
 void
 void
-NameChangeTransaction::runStateModel(unsigned int run_event) {
+NameChangeTransaction::defineEvents() {
-    try {
+    // Call superclass impl first.
-        // Seed the loop with the given event as the next to process.
+    StateModel::defineEvents();
-        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);
-    }
-}
 
 
+    // Define NCT events.
+    defineEvent(SELECT_SERVER_EVT, "SELECT_SERVER_EVT");
+    defineEvent(SERVER_SELECTED_EVT, "SERVER_SELECTED_EVT");
+    defineEvent(SERVER_IO_ERROR_EVT, "SERVER_IO_ERROR_EVT");
+    defineEvent(NO_MORE_SERVERS_EVT, "NO_MORE_SERVERS_EVT");
+    defineEvent(IO_COMPLETED_EVT, "IO_COMPLETED_EVT");
+    defineEvent(UPDATE_OK_EVT, "UPDATE_OK_EVT");
+    defineEvent(UPDATE_FAILED_EVT, "UPDATE_FAILED_EVT");
+}
 
 
-StateHandler
+void
-NameChangeTransaction::getStateHandler(unsigned int state) {
+NameChangeTransaction::verifyEvents() {
-    StateHandlerMap::iterator it = state_handlers_.find(state);
+    // Call superclass impl first.
-    if (it == state_handlers_.end()) {
+    StateModel::verifyEvents();
-        isc_throw(NameChangeTransactionError, "Invalid state: " << state);
-    }
 
 
-    return ((*it).second);
+    // Verify NCT events.
+    getEvent(SELECT_SERVER_EVT);
+    getEvent(SERVER_SELECTED_EVT);
+    getEvent(SERVER_IO_ERROR_EVT);
+    getEvent(NO_MORE_SERVERS_EVT);
+    getEvent(IO_COMPLETED_EVT);
+    getEvent(UPDATE_OK_EVT);
+    getEvent(UPDATE_FAILED_EVT);
 }
 }
 
 
 void
 void
-NameChangeTransaction::addToMap(unsigned int state, StateHandler handler) {
+NameChangeTransaction::defineStates() {
-    StateHandlerMap::iterator it = state_handlers_.find(state);
+    // Call superclass impl first.
-    if (it != state_handlers_.end()) {
+    StateModel::defineStates();
-        isc_throw(NameChangeTransactionError,
+    // This class is "abstract" in that it does not supply handlers for its
-                  "Attempted duplicate entry in state handler mape, state: "
+    // states, derivations must do that therefore they must define them.
-                   << state);
-    }
-
-    state_handlers_[state] = handler;
 }
 }
 
 
 void
 void
-NameChangeTransaction::setState(unsigned int state) {
+NameChangeTransaction::verifyStates() {
-    prev_state_ = state_;
+    // Call superclass impl first.
-    state_ = state;
+    StateModel::verifyStates();
+
+    // Verify NCT states. This ensures that derivations provide the handlers.
+    getState(READY_ST);
+    getState(SELECTING_FWD_SERVER_ST);
+    getState(SELECTING_REV_SERVER_ST);
+    getState(PROCESS_TRANS_OK_ST);
+    getState(PROCESS_TRANS_FAILED_ST);
 }
 }
 
 
 void
 void
-NameChangeTransaction::setNextEvent(unsigned int event) {
+NameChangeTransaction::onModelFailure(const std::string& explanation) {
-    last_event_ = next_event_;
+    setNcrStatus(dhcp_ddns::ST_FAILED);
-    next_event_ = event;
+    LOG_ERROR(dctl_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
+                  .arg(explanation);
 }
 }
 
 
 void
 void
@@ -244,26 +226,6 @@ NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
     return (ncr_->setStatus(status));
     return (ncr_->setStatus(status));
 }
 }
 
 
-unsigned int
-NameChangeTransaction::getState() const {
-    return (state_);
-}
-
-unsigned int
-NameChangeTransaction::getPrevState() const {
-    return (prev_state_);
-}
-
-unsigned int
-NameChangeTransaction::getLastEvent() const {
-    return (last_event_);
-}
-
-unsigned int
-NameChangeTransaction::getNextEvent() const {
-    return (next_event_);
-}
-
 DNSClient::Status
 DNSClient::Status
 NameChangeTransaction::getDnsUpdateStatus() const {
 NameChangeTransaction::getDnsUpdateStatus() const {
     return (dns_update_status_);
     return (dns_update_status_);
@@ -284,6 +246,5 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
     return (reverse_change_completed_);
 }
 }
 
 
-
 } // namespace isc::d2
 } // namespace isc::d2
 } // namespace isc
 } // namespace isc

+ 83 - 223
src/bin/d2/nc_trans.h

@@ -21,6 +21,7 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <d2/d2_config.h>
 #include <d2/d2_config.h>
 #include <d2/dns_client.h>
 #include <d2/dns_client.h>
+#include <d2/state_model.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -29,7 +30,7 @@
 namespace isc {
 namespace isc {
 namespace d2 {
 namespace d2 {
 
 
-/// @brief Thrown if the update manager encounters a general error.
+/// @brief Thrown if the transaction encounters a general error.
 class NameChangeTransactionError : public isc::Exception {
 class NameChangeTransactionError : public isc::Exception {
 public:
 public:
     NameChangeTransactionError(const char* file, size_t line,
     NameChangeTransactionError(const char* file, size_t line,
@@ -40,17 +41,12 @@ public:
 /// @brief Defines the type used as the unique key for transactions.
 /// @brief Defines the type used as the unique key for transactions.
 typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
 typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
 
 
-/// @brief Defines a function pointer for the handler method for a state.
-typedef boost::function<void()> StateHandler;
-
-/// @brief Defines a map of states to their handler methods.
-typedef std::map<unsigned int, StateHandler> StateHandlerMap;
-
 /// @brief Embodies the "life-cycle" required to carry out a DDNS update.
 /// @brief Embodies the "life-cycle" required to carry out a DDNS update.
 ///
 ///
 /// NameChangeTransaction is the base class that provides the common state
 /// NameChangeTransaction is the base class that provides the common state
 /// model mechanics and services performing the DNS updates needed to carry out
 /// model mechanics and services performing the DNS updates needed to carry out
-/// a DHCP_DDNS request as described by a NameChangeRequest.
+/// a DHCP_DDNS request as described by a NameChangeRequest.  It is derived
+/// from StateModel which supplies a simple, general purpose FSM implementation.
 ///
 ///
 /// Upon construction, each transaction has all of the information and
 /// Upon construction, each transaction has all of the information and
 /// resources required to carry out its assigned request, including the list(s)
 /// resources required to carry out its assigned request, including the list(s)
@@ -73,61 +69,33 @@ typedef std::map<unsigned int, StateHandler> StateHandlerMap;
 /// single update to the server and returns the response, asynchronously,
 /// single update to the server and returns the response, asynchronously,
 /// through a callback.  At each point in a transaction's state model, where
 /// through a callback.  At each point in a transaction's state model, where
 /// an update is to be sent, the model "suspends" until notified by the
 /// an update is to be sent, the model "suspends" until notified by the
-/// DNSClient via the callback.
+/// DNSClient via the callbacka.  Suspension is done by posting a
-///
+/// StateModel::NOP_EVT as the next event, stopping the state model execution.
-/// The state model implementation used is a very basic approach. States
-/// and events are simple integer constants. Each state must have a state
-/// handler. State handlers are void methods which accept an event as their
-/// only parameter.  Each transaction instance contains a map of states to
-/// to bound method pointers to their respective state handlers.
-///
-/// When invoked, the handler determines what it should do based upon the event,
-/// including what the next state and event should be. In other words the state
-/// transition knowledge is distributed among the state handlers rather than
-/// encapsulated in some form of state transition table.  Events set from within
-/// the state handlers are "internally" triggered events.  Events set from
-/// outside the state model, such as through the DNSClient completion callback
-/// are "externally" triggered.
 ///
 ///
-/// Executing the model consists of iteratively invoking the state handler
+/// Resuming state model execution when a DNS update completes is done by a
-/// indicated by the current state and passing it the current event.  As the
+/// call to StateModel::runStateModel() from within the DNSClient callback,
-/// handlers update the state and event, the machine is traversed. The loop
+/// with an event value of IO_COMPLETED_EVT (described below).
-/// "stops" whenever the model cannot continue without an externally triggered
-/// event or when it has reached its final state.  In the case of the former,
-/// the loop is re-entered upon arrival of the external event.
-///
-/// This loop is implemented in the runStateModel method.  This method accepts
-/// an event as argument.  This event is treated as the "next event" to process
-/// and is fed to the current state's handler.  The runStateModel does not exit
-/// until a handler sets the next event to a special value, NOP_EVT,
-/// indicating that either it is now waiting for IO to complete of the state
-/// model has reached its conclusion.
-///
-/// Re-entering the "loop" when a DNS update completes is done by a call to
-/// runStateModel() from within the DNSClient callback, with an event value
-/// of IO_COMPLETED_EVT.  As above, runStateModel() will loop until either the
-/// next IO is issued or the state model has reached its conclusion.
 ///
 ///
 /// This class defines a set of events and states that are a common to all
 /// This class defines a set of events and states that are a common to all
 /// transactions. Each derivation may add define additional states and events
 /// transactions. Each derivation may add define additional states and events
 /// as needed, but it must support the common set.  NameChangeTransaction
 /// as needed, but it must support the common set.  NameChangeTransaction
 /// does not supply any state handlers.  These are the sole responsibility of
 /// does not supply any state handlers.  These are the sole responsibility of
 /// derivations.
 /// derivations.
-class NameChangeTransaction : public DNSClient::Callback {
+class NameChangeTransaction : public DNSClient::Callback, public StateModel {
 public:
 public:
 
 
     //@{ States common to all transactions.
     //@{ States common to all transactions.
-    /// @brief State a transaction is in immediately after construction.
+
-    static const int NEW_ST = 0;
     /// @brief State from which a transaction is started.
     /// @brief State from which a transaction is started.
-    static const int READY_ST = 1;
+    static const int READY_ST = SM_DERIVED_STATE_MIN + 1;
+
     /// @brief State in which forward DNS server selection is done.
     /// @brief State in which forward DNS server selection is done.
     ///
     ///
     /// Within this state, the actual selection of the next forward server
     /// Within this state, the actual selection of the next forward server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// event.
     /// event.
-    static const int SELECTING_FWD_SERVER_ST = 2;
+    static const int SELECTING_FWD_SERVER_ST = SM_DERIVED_STATE_MIN + 2;
 
 
     /// @brief State in which reverse DNS server  selection is done.
     /// @brief State in which reverse DNS server  selection is done.
     ///
     ///
@@ -135,51 +103,52 @@ public:
     /// to use is conducted.  Upon conclusion of this state the next server
     /// to use is conducted.  Upon conclusion of this state the next server
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
     /// event.
     /// event.
-    static const int SELECTING_REV_SERVER_ST = 3;
+    static const int SELECTING_REV_SERVER_ST = SM_DERIVED_STATE_MIN + 3;
 
 
-    /// @brief Final state, all work has been performed.
+    /// @brief State which processes successful transaction conclusion.
-    static const int DONE_ST = 4;
+    static const int PROCESS_TRANS_OK_ST = SM_DERIVED_STATE_MIN + 4;
 
 
-    /// @define Value at which custom states in a derived class should begin.
+    /// @brief State which processes an unsuccessful transaction conclusion.
-    static const int DERIVED_STATES = 100;
+    static const int PROCESS_TRANS_FAILED_ST = SM_DERIVED_STATE_MIN + 5;
-    //@}
 
 
+    /// @brief Value at which custom states in a derived class should begin.
+    static const int NCT_DERIVED_STATE_MIN = SM_DERIVED_STATE_MIN + 101;
+    //@}
 
 
     //@{ Events common to all transactions.
     //@{ Events common to all transactions.
-    /// @brief Signifies that no event has occurred.
-    /// This is event used to interrupt the event loop to allow waiting for
-    /// an IO event or when there is no more work to be done.
-    static const int NOP_EVT = 0;
-    /// @brief Event used to start the transaction.
-    static const int START_TRANSACTION_EVT = 1;
     /// @brief Issued when a server needs to be selected.
     /// @brief Issued when a server needs to be selected.
-    static const int SELECT_SERVER_EVT = 2;
+    static const int SELECT_SERVER_EVT = SM_DERIVED_EVENT_MIN + 1;
+
     /// @brief Issued when a server  has been selected.
     /// @brief Issued when a server  has been selected.
-    static const int SERVER_SELECTED_EVT = 3;
+    static const int SERVER_SELECTED_EVT = SM_DERIVED_EVENT_MIN + 2;
+
     /// @brief Issued when an update fails due to an IO error.
     /// @brief Issued when an update fails due to an IO error.
-    static const int SERVER_IO_ERROR_EVT = 4;
+    static const int SERVER_IO_ERROR_EVT = SM_DERIVED_EVENT_MIN + 3;
+
     /// @brief Issued when there are no more servers from which to select.
     /// @brief Issued when there are no more servers from which to select.
     /// This occurs when none of the servers in the list can be reached to
     /// This occurs when none of the servers in the list can be reached to
     /// perform the update.
     /// perform the update.
-    static const int NO_MORE_SERVERS_EVT = 5;
+
+    static const int NO_MORE_SERVERS_EVT =SM_DERIVED_EVENT_MIN +  4;
     /// @brief Issued when a DNS update packet exchange has completed.
     /// @brief Issued when a DNS update packet exchange has completed.
     /// This occurs whenever the DNSClient callback is invoked whether the
     /// This occurs whenever the DNSClient callback is invoked whether the
     /// exchange was successful or not.
     /// exchange was successful or not.
-    static const int IO_COMPLETED_EVT = 6;
+
+    static const int IO_COMPLETED_EVT = SM_DERIVED_EVENT_MIN + 5;
     /// @brief Issued when the attempted update successfully completed.
     /// @brief Issued when the attempted update successfully completed.
     /// This occurs when an DNS update packet was successfully processed
     /// This occurs when an DNS update packet was successfully processed
     /// by the server.
     /// by the server.
-    static const int UPDATE_OK_EVT = 7;
+
+    static const int UPDATE_OK_EVT = SM_DERIVED_EVENT_MIN + 6;
+
     /// @brief Issued when the attempted update fails to complete.
     /// @brief Issued when the attempted update fails to complete.
     /// This occurs when an DNS update packet fails to process. The nature of
     /// This occurs when an DNS update packet fails to process. The nature of
     /// the failure is given by the DNSClient return status and the response
     /// the failure is given by the DNSClient return status and the response
     /// packet (if one was received).
     /// packet (if one was received).
-    static const int UPDATE_FAILED_EVT = 8;
+    static const int UPDATE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 7;
-    /// @brief Issued when the state model has no more work left to do.
-    static const int ALL_DONE_EVT = 9;
 
 
-    /// @define Value at which custom events in a derived class should begin.
+    /// @brief Value at which custom events in a derived class should begin.
-    static const int DERIVED_EVENTS = 100;
+    static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
     //@}
     //@}
 
 
     /// @brief Constructor
     /// @brief Constructor
@@ -204,10 +173,9 @@ public:
 
 
     /// @brief Begins execution of the transaction.
     /// @brief Begins execution of the transaction.
     ///
     ///
-    /// This method invokes initHandlersMap() to initialize the map of state
+    /// This method invokes StateModel::startModel() with a value of READY_ST.
-    /// handlers. It then starts the transaction's state model by setting the
+    /// This causes transaction's state model to attempt to begin execution
-    /// current state to READY_ST and invoking runStateModel() with an event
+    /// with the state handler for READY_ST.
-    /// parameter of START_TRANSACTION_EVT.
     void startTransaction();
     void startTransaction();
 
 
     /// @brief Serves as the DNSClient IO completion event handler.
     /// @brief Serves as the DNSClient IO completion event handler.
@@ -223,127 +191,62 @@ public:
     virtual void operator()(DNSClient::Status status);
     virtual void operator()(DNSClient::Status status);
 
 
 protected:
 protected:
-    /// @brief Populates the map of state handlers.
+    /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
     ///
-    /// This method is used by derivations to construct a map of states to
+    /// This method adds the events common to NCR transaction processing to
-    /// their appropriate state handlers (bound method pointers).  It is
+    /// the set of define events.  It invokes the superclass's implementation
-    /// invoked at the beginning of startTransaction().
+    /// first to maitain the hierarchical chain of event defintion.
+    /// Derivations of NameChangeTransaction must invoke its implementation
+    /// in like fashion.
     ///
     ///
-    /// Implementations should use the addToMap() method add entries to
+    /// @throw StateModelError if an event definition is invalid or a duplicate.
-    /// the map.
+    virtual void defineEvents();
-    /// @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.
-    virtual void initStateHandlerMap() {};
-
 
 
-    /// @brief Validates the contents of the state handler map.
+    /// @brief Validates the contents of the set of events.
-    ///
-    /// 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,
+    /// This method verifies that the events defined by both the superclass and
-    /// for each state the derivation supports.  For example, a implementation
+    /// this class are defined.  As with defineEvents, this method calls the
-    /// which included three states, READY_ST, DO_WORK_ST, and DONE_ST could
+    /// superclass's implementation first, to verify events defined by it and
-    /// implement this function as follows:
+    /// then this implementation to verify events defined by
+    /// NameChangeTransaction.
     ///
     ///
-    /// @code
+    /// @throw StateModelError if an event value is undefined.
-    ///    void verifyStateHandlerMap() {
+    virtual void verifyEvents();
-    ///        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.
+    /// @brief Adds states defined by NameChangeTransaction to the state set.
-    ///
-    /// This method attempts to add an entry to the handler map which maps
-    /// the given handler to the given state.  The state handler must be
-    /// a bound member pointer to a handler method of the transaction instance.
-    /// The following code snippet shows an example derivation and call to
-    /// addToMap() within its initStateHandlerMap() method.
-    ///
-    /// @code
-    /// class ExampleTrans : public NameChangeTransaction {
-    /// public:
-    /// :
-    /// void readyHandler() {
-    /// }
     ///
     ///
-    /// void initStateHandlerMap() {
+    /// This method adds the states common to NCR transaction processing to
-    ///     addToMap(READY_ST,
+    /// the dictionary of states.  It invokes the superclass's implementation
-    ///        boost::bind(&ExampleTrans::readyHandler, this));
+    /// first to maitain the hierarchical chain of state defintion.
-    ///     :
+    /// Derivations of NameChangeTransaction must invoke its implementation
+    /// in like fashion.
     ///
     ///
-    /// @endcode
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
-    ///
+    virtual void defineStates();
-    /// @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
+    /// @brief Validates the contents of the set of states.
-    ///
-    /// This method implements the state model "execution loop".  It uses
-    /// the given event as the next event to process and begins looping by
-    /// passing it the state handler for the current state.   As described
-    /// above, the invoked state handler determines the current state and the
-    /// next event required to implement the business logic. The method
-    /// continues to loop until next event is set to NOP_EVT, at which point
-    /// the method exits.
     ///
     ///
-    /// Any exception thrown during the loop is caught, logged, and the
+    /// This method verifies that the states defined by both the superclass and
-    /// transaction is immediately set to failed status.  The state model is
+    /// this class are defined.  As with defineStates, this method calls the
-    /// expected to account for any possible errors so any that escape are
+    /// superclass's implementation first, to verify states defined by it and
-    /// treated as unrecoverable in terms of the current transaction.
+    /// then this implementation to verify states defined by
+    /// NameChangeTransaction.
     ///
     ///
-    /// @param event is the next event to process
+    /// @throw StateModelError if an event value is undefined.
-    ///
+    virtual void verifyStates();
-    /// 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.
+    /// @brief Handler for fatal model execution errors.
     ///
     ///
-    /// This updates the transaction's notion of the current state and is the
+    /// This handler is called by the StateModel implementation when the model
-    /// state whose handler will be executed on the next iteration of the run
+    /// execution encounters a model violation:  attempt to call an unmapped
-    /// loop.
+    /// 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
     ///
     ///
-    /// @param state the new value to assign to the current state.
+    /// @param explanation is text detailing the error
-    void setState(unsigned int state);
+    virtual void onModelFailure(const std::string& explanation);
-
-    /// @brief Sets the next event to the given event value.
-    ///
-    /// This updates the transaction's notion of the next event and is the
-    /// event that will be passed into the current state's handler on the next
-    /// iteration of the run loop.
-    ///
-    /// @param state the new value to assign to the current state.
-    void setNextEvent(unsigned int event);
 
 
     /// @brief Sets the update status to the given status value.
     /// @brief Sets the update status to the given status value.
     ///
     ///
@@ -441,34 +344,6 @@ public:
     /// the request does not include a reverse change, the pointer will empty.
     /// the request does not include a reverse change, the pointer will empty.
     DdnsDomainPtr& getReverseDomain();
     DdnsDomainPtr& getReverseDomain();
 
 
-    /// @brief Fetches the transaction's current state.
-    ///
-    /// This returns the transaction's notion of the current state. It is the
-    /// state whose handler will be executed on the next iteration of the run
-    /// loop.
-    ///
-    /// @return An unsigned int representing the current state.
-    unsigned int getState() const;
-
-    /// @brief Fetches the transaction's previous state.
-    ///
-    /// @return An unsigned int representing the previous state.
-    unsigned int getPrevState() const;
-
-    /// @brief Fetches the transaction's last event.
-    ///
-    /// @return An unsigned int representing the last event.
-    unsigned int getLastEvent() const;
-
-    /// @brief Fetches the transaction's next event.
-    ///
-    /// This returns the transaction's notion of the next event. It is the
-    /// event that will be passed into the current state's handler on the next
-    /// iteration of the run loop.
-    ///
-    /// @return An unsigned int representing the next event.
-    unsigned int getNextEvent() const;
-
     /// @brief Fetches the most recent DNS update status.
     /// @brief Fetches the most recent DNS update status.
     ///
     ///
     /// @return A DNSClient::Status indicating the result of the most recent
     /// @return A DNSClient::Status indicating the result of the most recent
@@ -500,9 +375,6 @@ public:
     bool getReverseChangeCompleted() const;
     bool getReverseChangeCompleted() const;
 
 
 private:
 private:
-    /// @brief Contains a map of states to their state handlers.
-    StateHandlerMap state_handlers_;
-
     /// @brief The IOService which should be used to for IO processing.
     /// @brief The IOService which should be used to for IO processing.
     isc::asiolink::IOService& io_service_;
     isc::asiolink::IOService& io_service_;
 
 
@@ -526,18 +398,6 @@ private:
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     /// @brief The DNSClient instance that will carry out DNS packet exchanges.
     DNSClientPtr dns_client_;
     DNSClientPtr dns_client_;
 
 
-    /// @brief The current state within the transaction's state model.
-    unsigned int state_;
-
-    /// @brief The previous state within the transaction's state model.
-    unsigned int prev_state_;
-
-    /// @brief The event last processed by the transaction.
-    unsigned int last_event_;
-
-    /// @brief The event the transaction should process next.
-    unsigned int next_event_;
-
     /// @brief The outcome of the most recently completed DNS packet exchange.
     /// @brief The outcome of the most recently completed DNS packet exchange.
     DNSClient::Status dns_update_status_;
     DNSClient::Status dns_update_status_;
 
 

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

@@ -0,0 +1,380 @@
+// 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 {
+
+/********************************** State *******************************/
+
+State::State(const int value, const std::string& label, StateHandler handler)
+        : LabeledValue(value, label), handler_(handler) {
+}
+
+State::~State() {
+}
+
+void
+State::run() {
+        (handler_)();
+}
+
+/********************************** StateSet *******************************/
+
+StateSet::StateSet() {
+}
+
+StateSet::~StateSet() {
+}
+
+void
+StateSet::add(const int value, const std::string& label, StateHandler handler) {
+    try {
+        LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler)));
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what());
+    }
+
+}
+
+const StatePtr
+StateSet::getState(int value) {
+    if (!isDefined(value)) {
+        isc_throw(StateModelError," StateSet: state is undefined");
+    }
+
+    // Since we have to use dynamic casting, to get a state pointer
+    // we can't return a reference.
+    StatePtr state = boost::dynamic_pointer_cast<State>(get(value));
+    return (state);
+}
+
+/********************************** StateModel *******************************/
+
+
+// Common state model states
+const int StateModel::NEW_ST;
+const int StateModel::END_ST;
+
+const int StateModel::SM_DERIVED_STATE_MIN;
+
+// 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_DERIVED_EVENT_MIN;
+
+StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false),
+                          curr_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) {
+    // First let's build and verify the dictionary of events.
+    try {
+        defineEvents();
+        verifyEvents();
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "Event set is invalid: " << ex.what());
+    }
+
+    // Next let's build and verify the dictionary of states.
+    try {
+        defineStates();
+        verifyStates();
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "State set is invalid: " << ex.what());
+    }
+
+    // Record that we are good to go.
+    dictionaries_initted_ = true;
+
+    // Set the current state to starting state and enter the run loop.
+    setState(start_state);
+    runModel(START_EVT);
+}
+
+void
+StateModel::runModel(unsigned int run_event) {
+    /// If the dictionaries aren't built bail out.
+    if (!dictionaries_initted_) {
+       abortModel("runModel invoked before model has been initialized");
+    }
+
+    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.
+            getState(curr_state_)->run();
+
+            // 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 model violation and indicates a programmatic shortcoming.
+        // In theory, the model should account for all error scenarios and
+        // deal with them accordingly.  Transition to END_ST with FAILED_EVT
+        // via abortModel.
+        abortModel(ex.what());
+    }
+}
+
+void
+StateModel::nopStateHandler() {
+}
+
+
+void
+StateModel::defineEvent(unsigned int event_value, const std::string& label) {
+    if (!isModelNew()) {
+        // Don't allow for self-modifying models.
+        isc_throw(StateModelError, "Events may only be added to a new model."
+                   << event_value << " - " << label);
+    }
+
+    // Attempt to add the event to the set.
+    try {
+        events_.add(event_value, label);
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "Error adding event: " << ex.what());
+    }
+}
+
+const EventPtr&
+StateModel::getEvent(unsigned int event_value) {
+    if (!events_.isDefined(event_value)) {
+        isc_throw(StateModelError,
+                  "Event value is not defined:" << event_value);
+    }
+
+    return (events_.get(event_value));
+}
+
+void
+StateModel::defineState(unsigned int state_value, const std::string& label,
+    StateHandler handler) {
+    if (!isModelNew()) {
+        // Don't allow for self-modifying maps.
+        isc_throw(StateModelError, "States may only be added to a new model."
+                   << state_value << " - " << label);
+    }
+
+    // Attempt to add the state to the set.
+    try {
+        states_.add(state_value, label, handler);
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "Error adding state: " << ex.what());
+    }
+}
+
+const StatePtr
+StateModel::getState(unsigned int state_value) {
+    if (!states_.isDefined(state_value)) {
+        isc_throw(StateModelError,
+                  "State value is not defined:" << state_value);
+    }
+
+    return (states_.getState(state_value));
+}
+
+void
+StateModel::defineEvents() {
+    defineEvent(NOP_EVT, "NOP_EVT");
+    defineEvent(START_EVT, "START_EVT");
+    defineEvent(END_EVT, "END_EVT");
+    defineEvent(FAIL_EVT, "FAIL_EVT");
+}
+
+void
+StateModel::verifyEvents() {
+    getEvent(NOP_EVT);
+    getEvent(START_EVT);
+    getEvent(END_EVT);
+    getEvent(FAIL_EVT);
+}
+
+void
+StateModel::defineStates() {
+    defineState(NEW_ST, "NEW_ST",
+                boost::bind(&StateModel::nopStateHandler, this));
+    defineState(END_ST, "END_ST",
+                boost::bind(&StateModel::nopStateHandler, this));
+}
+
+void
+StateModel::verifyStates() {
+    getState(NEW_ST);
+    getState(END_ST);
+}
+
+void 
+StateModel::onModelFailure(const std::string&) {
+    // Empty implementation to make deriving classes simpler.
+}
+
+void
+StateModel::transition(unsigned int state, unsigned int event) {
+    setState(state);
+    postNextEvent(event);
+}
+
+void
+StateModel::endModel() {
+    transition(END_ST, END_EVT);
+}
+
+void
+StateModel::abortModel(const std::string& explanation) {
+    transition(END_ST, FAIL_EVT);
+
+    std::ostringstream stream ;
+    stream << explanation << " : " << getContextStr();
+    onModelFailure(stream.str());
+}
+
+void
+StateModel::setState(unsigned int state) {
+    if (state != END_ST && !states_.isDefined(state)) {
+        isc_throw(StateModelError,
+                  "Attempt to set state to an undefined value: " << state );
+    }
+
+    prev_state_ = curr_state_;
+    curr_state_ = state;
+
+    // Set the "do" flags if we are transitioning.
+    on_entry_flag_ = ((state != END_ST) && (prev_state_ != curr_state_));
+
+    // At this time they are calculated the same way.
+    on_exit_flag_ = on_entry_flag_;
+}
+
+void
+StateModel::postNextEvent(unsigned int event_value) {
+    // Check for FAIL_EVT as special case of model error before events are
+    // defined.
+    if (event_value != FAIL_EVT && !events_.isDefined(event_value)) {
+        isc_throw(StateModelError,
+                  "Attempt to post an undefined event, value: " << event_value);
+    }
+
+    last_event_ = next_event_;
+    next_event_ = event_value;
+}
+
+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::getCurrState() const {
+    return (curr_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 (curr_state_ == NEW_ST);
+}
+
+bool
+StateModel::isModelRunning() const {
+    return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST));
+}
+
+bool
+StateModel::isModelWaiting() const {
+    return (isModelRunning() && (next_event_ == NOP_EVT));
+}
+
+bool
+StateModel::isModelDone() const {
+    return (curr_state_ == END_ST);
+}
+
+bool
+StateModel::didModelFail() const {
+    return (isModelDone() && (next_event_ == FAIL_EVT));
+}
+
+std::string
+StateModel::getStateLabel(const int state) const {
+    return (states_.getLabel(state));
+}
+
+std::string
+StateModel::getEventLabel(const int event) const {
+    return (events_.getLabel(event));
+}
+
+std::string
+StateModel::getContextStr() const {
+    std::ostringstream stream;
+    stream << "current state: [ "
+            << curr_state_ << " " << getStateLabel(curr_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());
+}
+
+} // namespace isc::d2
+} // namespace isc

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

@@ -0,0 +1,672 @@
+// 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 state_model.h This file defines the class StateModel.
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <d2/d2_config.h>
+#include <d2/dns_client.h>
+#include <d2/labeled_value.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 Define an Event.
+typedef LabeledValue Event;
+
+/// @brief Define Event pointer.
+typedef LabeledValuePtr EventPtr;
+
+/// @brief Defines a pointer to an instance method for handling a state.
+typedef boost::function<void()> StateHandler;
+
+/// @brief Defines a State within the State Model.
+///
+/// This class provides the means to define a state within a set or dictionary
+/// of states, and assign the state an handler method to execute the state's
+/// actions.  It derives from LabeledValue which allows a set of states to be
+/// keyed by integer constants.
+class State : public LabeledValue {
+public:
+    /// @brief Constructor
+    ///
+    /// @param value is the numeric value of the state
+    /// @param label is the text label to assign to the state
+    /// @param handler is the bound instance method which handles the state's
+    /// action.
+    ///
+    /// A typical invocation might look this:
+    ///
+    /// @code
+    ///     State(SOME_INT_VAL, "SOME_INT_VAL",
+    ///            boost::bind(&StateModelDerivation::someHandler, this));
+    /// @endcode
+    ///
+    /// @throw StateModelError if label is null or blank.
+    State(const int value, const std::string& label, StateHandler handler);
+
+    /// @brief Destructor
+    virtual ~State();
+
+    /// @brief Invokes the State's handler.
+    void run();
+
+private:
+    /// @brief Bound instance method pointer to the state's handler method.
+    StateHandler handler_;
+};
+
+/// @brief Defines a shared pointer to a State.
+typedef boost::shared_ptr<State> StatePtr;
+
+/// @brief Implements a unique set or dictionary of states.
+///
+/// This class provides the means to construct and access a unique set of
+/// states.  This provide the ability to validate state values, look up their
+/// text labels, and their handlers.
+class StateSet : public LabeledValueSet {
+public:
+    /// @brief Constructor
+    StateSet();
+
+    /// @brief Destructor
+    virtual ~StateSet();
+
+    /// @brief Adds a state definition to the set of states.
+    ///
+    /// @param value is the numeric value of the state
+    /// @param label is the text label to assig to the state
+    /// @param handler is the bound instance method which handles the state's
+    ///
+    /// @throw StateModelError if the value is already defined in the set, or
+    /// if the label is null or blank.
+    void add(const int value, const std::string& label, StateHandler handler);
+
+    /// @brief Fetches a state for the given value.
+    ///
+    /// @param value the numeric value of the state desired
+    ///
+    /// @return A constant pointer the State found.
+    /// Note, this relies on dynamic cast and cannot return a pointer reference.
+    ///
+    /// @throw StateModelError if the value is undefined.
+    const StatePtr getState(int value);
+};
+
+/// @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. The model
+/// uses numeric constants to identify events and states, and maintains
+/// dictionaries of defined events and states.  Event and state definitions
+/// include a text label for logging purposes.  Additionally, each state
+/// definition includes a state handler. State handlers are methods which
+/// implement the actions that need to occur when the model is "in" a given
+/// state.  The implementation provides methods to add entries to and verify
+/// the contents of both dictionaries.
+///
+/// During model execution, the following context is tracked:
+///
+/// * current 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
+/// be 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 it "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 a NOP_EVT posts or the state changes
+/// to END_ST.  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
+/// the event and state dictionaries.  The following virtual methods are
+/// provided for this:
+///
+/// * defineEvents - define events
+/// * verifyEvents - verifies that the expected events are defined
+/// * defineStates - defines states
+/// * verifyStates - verifies that the expected states are defined
+///
+/// The concept behind the verify methods is to provide an initial sanity
+/// check of the dictionaries.  This should help avoid using undefined event
+/// or state values accidentally.
+///
+/// These methods are intended to be implemented by each "layer" in a StateModel
+/// derivation hierarchy.  This allows each layer to define additional events
+/// and states.
+///
+/// Once the dictionaries have 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 one state to the another, state handlers invoke use
+/// the method, transition.  This method accepts a state and an 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 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;
+
+    /// @brief Value at which custom states in a derived class should begin.
+    static const int SM_DERIVED_STATE_MIN = 11;
+    //@}
+
+    //@{ 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;
+
+    /// @brief Value at which custom events in a derived class should begin.
+    static const int SM_DERIVED_EVENT_MIN = 11;
+    //@}
+
+    /// @brief Constructor
+    StateModel();
+
+    /// @brief Destructor
+    virtual ~StateModel();
+
+    /// @brief Begins execution of the model.
+    ///
+    /// This method invokes the define and verify methods for both events and
+    /// states to initialize their respective dictionaries. It then starts
+    /// the model execution setting the current state to the given start state,
+    /// and the event to START_EVT.
+    ///
+    /// @param start_state is the state in which to begin execution.
+    ///
+    /// @throw StateModelError or others indirectly, as this method calls
+    /// dictionary define and verify methods.
+    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 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 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();
+
+    /// @brief An empty state handler.
+    ///
+    /// This method is primarily used to permit special states, NEW_ST and
+    /// END_ST to be included in the state dictionary.  Currently it is an
+    /// empty method.
+    void nopStateHandler();
+
+protected:
+    /// @brief Populates the set of events.
+    ///
+    /// This method is used to construct the set of valid events. Each class
+    /// within a StateModel derivation hierarchy uses this method to add any
+    /// events it defines to the set.  Each derivation's implementation must
+    /// also call it's superclass's implementation.  This allows each class
+    /// within the hierarchy to make contributions to the set of defined
+    /// events. Implementations use the method, defineEvent(), to add event
+    /// definitions.  An example of the derivation's implementation follows:
+    ///
+    /// @code
+    /// void StateModelDerivation::defineEvents() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::defineEvents();
+    ///
+    ///     // Add the events defined by the derivation.
+    ///     defineEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+    ///     defineEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+    ///     :
+    /// }
+    /// @endcode
+    virtual void defineEvents();
+
+    /// @brief Adds an event value and associated label to the set of events.
+    ///
+    /// @param value is the numeric value of the event
+    /// @param label is the text label of the event used in log messages and
+    /// exceptions.
+    ///
+    /// @throw StateModelError if the model has already been started, if
+    /// the value is already defined, or if the label is empty.
+    void defineEvent(unsigned int value, const std::string& label);
+
+    /// @brief Fetches the event referred to by value.
+    ///
+    /// @param value is the numeric value of the event desired.
+    ///
+    /// @return returns a constant pointer reference to the event if found
+    ///
+    /// @throw StateModelError if the event is not defined.
+    const EventPtr& getEvent(unsigned int value);
+
+    /// @brief Validates the contents of the set of events.
+    ///
+    /// This method is invoked immediately after the defineEvents method and
+    /// is used to verify that all the required events are defined.  If the
+    /// event set is determined to be invalid this method should throw a
+    /// StateModelError.  As with the defineEvents method, each class within
+    /// a StateModel derivation hierarchy must supply an implementation
+    /// which calls it's superclass's implementation as well as verifying any
+    /// events added by the derivation.  Validating an event is accomplished
+    /// by simply attempting to fetch an event by its value from the event set.
+    /// An example of the derivation's implementation follows:
+    ///
+    /// @code
+    /// void StateModelDerivation::verifyEvents() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::verifyEvents();
+    ///
+    ///     // Verify the events defined by the derivation.
+    ///     getEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+    ///     getEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+    ///     :
+    /// }
+    /// @endcode
+    virtual void verifyEvents();
+
+    /// @brief Populates the set of states.
+    ///
+    /// This method is used to construct the set of valid states. Each class
+    /// within a StateModel derivation hierarchy uses this method to add any
+    /// states it defines to the set.  Each derivation's implementation must
+    /// also call it's superclass's implementation.  This allows each class
+    /// within the hierarchy to make contributions to the set of defined
+    /// states. Implementations use the method, defineState(), to add state
+    /// definitions.  An example of the derivation's implementation follows:
+    ///
+    /// @code
+    /// void StateModelDerivation::defineStates() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::defineStates();
+    ///
+    ///     // Add the states defined by the derivation.
+    ///     defineState(SOME_ST, "SOME_ST",
+    ///                 boost::bind(&StateModelDerivation::someHandler, this));
+    ///     :
+    /// }
+    /// @endcode
+    virtual void defineStates();
+
+    /// @brief Adds an state value and associated label to the set of states.
+    ///
+    /// @param value is the numeric value of the state
+    /// @param label is the text label of the state used in log messages and
+    /// exceptions.
+    /// @param handler is the bound instance method which implements the state's
+    /// actions.
+    ///
+    /// @throw StateModelError if the model has already been started, if
+    /// the value is already defined, or if the label is empty.
+    void defineState(unsigned int value, const std::string& label,
+                     StateHandler handler);
+
+    /// @brief Fetches the state referred to by value.
+    ///
+    /// @param value is the numeric value of the state desired.
+    ///
+    /// @return returns a constant pointer to the state if found
+    ///
+    /// @throw StateModelError if the state is not defined.
+    const StatePtr getState(unsigned int value);
+
+    /// @brief Validates the contents of the set of states.
+    ///
+    /// This method is invoked immediately after the defineStates method and
+    /// is used to verify that all the required states are defined.  If the
+    /// state set is determined to be invalid this method should throw a
+    /// StateModelError.  As with the defineStates method, each class within
+    /// a StateModel derivation hierarchy must supply an implementation
+    /// which calls it's superclass's implementation as well as verifying any
+    /// states added by the derivation.  Validating an state is accomplished
+    /// by simply attempting to fetch the state by its value from the state set.
+    /// An example of the derivation's implementation follows:
+    ///
+    /// @code
+    /// void StateModelDerivation::verifyStates() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::verifyStates();
+    ///
+    ///     // Verify the states defined by the derivation.
+    ///     getState(SOME_CUSTOM_EVT_2);
+    ///     :
+    /// }
+    /// @endcode
+    virtual void verifyStates();
+
+    /// @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.  This default
+    /// implementation does nothing.
+    ///
+    /// @param explanation text detailing the error and state machine context
+    virtual void onModelFailure(const std::string& explanation);
+
+    /// @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.
+    ///
+    /// @param explanation is text detailing the reason for aborting.
+    void abortModel(const std::string& explanation);
+
+    /// @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 numeric event value to post as the next event.
+    ///
+    /// @throw StateModelError if the event is undefined
+    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 getCurrState() 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 Fetches the label associated with an event value.
+    ///
+    /// @param event is the numeric event value for which the label is desired.
+    ///
+    /// @return Returns a string containing the event label or
+    /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+    std::string getEventLabel(const int event) const;
+
+    /// @brief Fetches the label associated with an state value.
+    ///
+    /// @param state is the numeric state value for which the label is desired.
+    ///
+    /// @return Returns a const char* containing the state label or
+    /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+    std::string getStateLabel(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 The dictionary of valid events.
+    LabeledValueSet events_;
+
+    /// @brief The dictionary of valid states.
+    StateSet states_;
+
+    /// @brief Indicates if the event and state dictionaries have been initted.
+    bool dictionaries_initted_;
+
+    /// @brief The current state within the model's state model.
+    unsigned int curr_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

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

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

+ 107 - 0
src/bin/d2/tests/labeled_value_unittests.cc

@@ -0,0 +1,107 @@
+// 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/labeled_value.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Verifies basic construction and accessors for LabeledValue.
+TEST(LabeledValue, construction) {
+    /// Verify that an empty label is not allowed.
+    ASSERT_THROW(LabeledValue(1, ""), LabeledValueError);
+
+    /// Verify that a valid constructor works.
+    LabeledValuePtr lvp;
+    ASSERT_NO_THROW(lvp.reset(new LabeledValue(1, "NotBlank")));
+    ASSERT_TRUE(lvp);
+
+    // Verify that the value can be accessed.
+    EXPECT_EQ(1, lvp->getValue());
+
+    // Verify that the label can be accessed.
+    EXPECT_EQ("NotBlank", lvp->getLabel());
+}
+
+/// @brief Verifies the logical operators defined for LabeledValue.
+TEST(LabeledValue, operators) {
+    LabeledValuePtr lvp1;
+    LabeledValuePtr lvp1Also;
+    LabeledValuePtr lvp2;
+
+    // Create three instances, two of which have the same numeric value.
+    ASSERT_NO_THROW(lvp1.reset(new LabeledValue(1, "One")));
+    ASSERT_NO_THROW(lvp1Also.reset(new LabeledValue(1, "OneAlso")));
+    ASSERT_NO_THROW(lvp2.reset(new LabeledValue(2, "Two")));
+
+    // Verify each of the operators.
+    EXPECT_TRUE(*lvp1 == *lvp1Also);
+    EXPECT_TRUE(*lvp1 != *lvp2);
+    EXPECT_TRUE(*lvp1 < *lvp2);
+    EXPECT_FALSE(*lvp2 < *lvp1);
+}
+
+/// @brief Verifies the default constructor for LabeledValueSet.
+TEST(LabeledValueSet, construction) {
+    ASSERT_NO_THROW (LabeledValueSet());
+}
+
+/// @brief Verifies the basic operations of a LabeledValueSet.
+/// Essentially we verify that we can define a set of valid entries and
+/// look them up without issue.
+TEST(LabeledValueSet, basicOperation) {
+    const char* labels[] = {"Zero", "One", "Two", "Three" };
+    LabeledValueSet lvset;
+    LabeledValuePtr lvp;
+
+    // Verify the we cannot add an empty pointer to the set.
+    EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+    // Verify that we can add an entry to the set via pointer.
+    ASSERT_NO_THROW(lvp.reset(new LabeledValue(0, labels[0])));
+    EXPECT_NO_THROW(lvset.add(lvp));
+
+    // Verify that we cannot add a duplicate entry.
+    EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+    // Add the remaining entries using add(int,char*) variant.
+    for (int i = 1; i < 3; i++) {
+        EXPECT_NO_THROW(lvset.add(i, labels[i]));
+    }
+
+    // Verify that we can't add a duplicate entry this way either.
+    EXPECT_THROW ((lvset.add(0, labels[0])), LabeledValueError);
+
+    // Verify that we can look up all of the defined entries properly.
+    for (int i = 1; i < 3; i++) {
+        EXPECT_TRUE(lvset.isDefined(i));
+        EXPECT_NO_THROW(lvp = lvset.get(i));
+        EXPECT_EQ(lvp->getValue(), i);
+        EXPECT_EQ(lvp->getLabel(), labels[i]);
+        EXPECT_EQ(lvset.getLabel(i), labels[i]);
+    }
+
+    // Verify behavior for a value that is not defined.
+    EXPECT_FALSE(lvset.isDefined(4));
+    EXPECT_NO_THROW(lvp = lvset.get(4));
+    EXPECT_FALSE(lvp);
+    EXPECT_EQ(lvset.getLabel(4), LabeledValueSet::UNDEFINED_LABEL);
+}
+
+}

+ 188 - 210
src/bin/d2/tests/nc_trans_unittests.cc

@@ -35,13 +35,10 @@ class NameChangeStub : public NameChangeTransaction {
 public:
 public:
 
 
     // NameChangeStub states
     // NameChangeStub states
-    static const int DUMMY_ST = DERIVED_STATES + 1;
+    static const int DOING_UPDATE_ST = NCT_DERIVED_STATE_MIN + 1;
-
-    static const int DO_WORK_ST = DERIVED_STATES + 2;
-
 
 
     // NameChangeStub events
     // NameChangeStub events
-    static const int START_WORK_EVT = DERIVED_EVENTS + 1;
+    static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
 
 
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
@@ -51,112 +48,146 @@ public:
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr forward_domain,
                    DdnsDomainPtr reverse_domain)
                    DdnsDomainPtr reverse_domain)
         : NameChangeTransaction(io_service, ncr, forward_domain,
         : NameChangeTransaction(io_service, ncr, forward_domain,
-                                reverse_domain), dummy_called_(false) {
+                                reverse_domain) {
     }
     }
 
 
     /// @brief Destructor
     /// @brief Destructor
     virtual ~NameChangeStub() {
     virtual ~NameChangeStub() {
     }
     }
 
 
-    bool getDummyCalled() {
+    /// @brief Empty handler used to statisfy map verification.
-        return (dummy_called_);
-    }
-
-    void clearDummyCalled() {
-        dummy_called_ = false;
-    }
-
     void dummyHandler() {
     void dummyHandler() {
-       dummy_called_ = true;
+        isc_throw(NameChangeTransactionError,
+                  "dummyHandler - invalid event: " << getContextStr());
     }
     }
 
 
     /// @brief State handler for the READY_ST.
     /// @brief State handler for the READY_ST.
     ///
     ///
     /// Serves as the starting state handler, it consumes the
     /// Serves as the starting state handler, it consumes the
-    /// START_TRANSACTION_EVT "transitioning" to the state, DO_WORK_ST and
+    /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and
-    /// sets the next event to START_WORK_EVT.
+    /// sets the next event to SEND_UPDATE_EVT.
     void readyHandler() {
     void readyHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case START_TRANSACTION_EVT:
+        case START_EVT:
-            setState(DO_WORK_ST);
+            transition(DOING_UPDATE_ST, SEND_UPDATE_EVT);
-            setNextEvent(START_WORK_EVT);
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "readyHandler - invalid event: " << getContextStr());
         }
         }
     }
     }
 
 
-    /// @brief State handler for the DO_WORK_ST.
+    /// @brief State handler for the DOING_UPDATE_ST.
     ///
     ///
     /// Simulates a state that starts some form of asynchronous work.
     /// Simulates a state that starts some form of asynchronous work.
-    /// When next event is START_WORK_EVT it sets the status to pending
+    /// When next event is SEND_UPDATE_EVT it sets the status to pending
     /// and signals the state model must "wait" for an event by setting
     /// and signals the state model must "wait" for an event by setting
     /// next event to NOP_EVT.
     /// next event to NOP_EVT.
     ///
     ///
     /// When next event is IO_COMPLETED_EVT, it transitions to the state,
     /// When next event is IO_COMPLETED_EVT, it transitions to the state,
-    /// DONE_ST, and sets the next event to ALL_DONE_EVT.
+    /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT.
-    void doWorkHandler() {
+    void doingUpdateHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case START_WORK_EVT:
+        case SEND_UPDATE_EVT:
             setNcrStatus(dhcp_ddns::ST_PENDING);
             setNcrStatus(dhcp_ddns::ST_PENDING);
-            setNextEvent(NOP_EVT);
+            postNextEvent(NOP_EVT);
             break;
             break;
-        //case WORK_DONE_EVT:
         case IO_COMPLETED_EVT:
         case IO_COMPLETED_EVT:
-            setState(DONE_ST);
+            if (getDnsUpdateStatus() == DNSClient::SUCCESS) {
-            setNextEvent(ALL_DONE_EVT);
+                setForwardChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "doingUpdateHandler - invalid event: "
+                      << getContextStr());
         }
         }
     }
     }
 
 
-    /// @brief State handler for the DONE_ST.
+    /// @brief State handler for the PROCESS_TRANS_OK_ST.
     ///
     ///
     /// This is the last state in the model.  Note that it sets the
     /// This is the last state in the model.  Note that it sets the
     /// status to completed and next event to NOP_EVT.
     /// status to completed and next event to NOP_EVT.
-    void doneHandler() {
+    void processTransDoneHandler() {
         switch(getNextEvent()) {
         switch(getNextEvent()) {
-        case ALL_DONE_EVT:
+        case UPDATE_OK_EVT:
             setNcrStatus(dhcp_ddns::ST_COMPLETED);
             setNcrStatus(dhcp_ddns::ST_COMPLETED);
-            setNextEvent(NOP_EVT);
+            endModel();
+            break;
+        case UPDATE_FAILED_EVT:
+            setNcrStatus(dhcp_ddns::ST_FAILED);
+            endModel();
             break;
             break;
         default:
         default:
             // its bogus
             // its bogus
-            isc_throw(NameChangeTransactionError, "invalid event: "
+            isc_throw(NameChangeTransactionError,
-                      << getNextEvent() << " for state: " << getState());
+                      "processTransDoneHandler - invalid event: "
+                      << getContextStr());
         }
         }
     }
     }
 
 
-    /// @brief Initializes the state handler map.
+    /// @brief Construct the event dictionary.
-    void initStateHandlerMap() {
+    virtual void defineEvents() {
-        addToMap(READY_ST,
+        // Invoke the base call implementation first.
-            boost::bind(&NameChangeStub::readyHandler, this));
+        NameChangeTransaction::defineEvents();
+
+        // Define our events.
+        defineEvent(SEND_UPDATE_EVT, "SEND_UPDATE_EVT");
+    }
+
+    /// @brief Verify the event dictionary.
+    virtual void verifyEvents() {
+        // Invoke the base call implementation first.
+        NameChangeTransaction::verifyEvents();
+
+        // Define our events.
+        getEvent(SEND_UPDATE_EVT);
+    }
+
+    /// @brief Construct the state dictionary.
+    virtual void defineStates() {
+        // Invoke the base call implementation first.
+        NameChangeTransaction::defineStates();
 
 
-        addToMap(DO_WORK_ST,
+        // Define our states.
-            boost::bind(&NameChangeStub::doWorkHandler, this));
+        defineState(READY_ST, "READY_ST",
+                             boost::bind(&NameChangeStub::readyHandler, this));
 
 
-        addToMap(DONE_ST,
+        defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
-            boost::bind(&NameChangeStub::doneHandler, this));
+                             boost::bind(&NameChangeStub::dummyHandler, this));
+
+        defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+                             boost::bind(&NameChangeStub::dummyHandler, this));
+
+        defineState(DOING_UPDATE_ST, "DOING_UPDATE_ST",
+                             boost::bind(&NameChangeStub::doingUpdateHandler,
+                                         this));
+
+        defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+                             boost::bind(&NameChangeStub::
+                                         processTransDoneHandler, this));
+
+        defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+                             boost::bind(&NameChangeStub::
+                                         processTransDoneHandler, this));
     }
     }
 
 
-    void verifyStateHandlerMap() {
+    /// @brief Verify the event dictionary.
-        getStateHandler(READY_ST);
+    virtual void verifyStates() {
-        getStateHandler(DO_WORK_ST);
+        // Invoke the base call implementation first.
-        getStateHandler(DONE_ST);
+        NameChangeTransaction::verifyStates();
+
+        // Define our states.
+        getState(DOING_UPDATE_ST);
     }
     }
 
 
     // Expose the protected methods to be tested.
     // Expose the protected methods to be tested.
-    using NameChangeTransaction::addToMap;
+    using StateModel::runModel;
-    using NameChangeTransaction::getStateHandler;
-    using NameChangeTransaction::initStateHandlerMap;
-    using NameChangeTransaction::runStateModel;
-    using NameChangeTransaction::setState;
-    using NameChangeTransaction::setNextEvent;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
     using NameChangeTransaction::getCurrentServer;
@@ -168,14 +199,9 @@ public:
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::getReverseChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setForwardChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
     using NameChangeTransaction::setReverseChangeCompleted;
-
-    bool dummy_called_;
 };
 };
 
 
-const int NameChangeStub::DO_WORK_ST;
+/// @brief Defines a pointer to a NameChangeStubPtr instance.
-const int NameChangeStub::START_WORK_EVT;
-
-/// @brief Defines a pointer to a D2UpdateMgr instance.
 typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 
 
 /// @brief Test fixture for testing NameChangeTransaction
 /// @brief Test fixture for testing NameChangeTransaction
@@ -333,26 +359,11 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_FALSE(name_change->getDNSClient());
     EXPECT_FALSE(name_change->getDNSClient());
     EXPECT_FALSE(name_change->getCurrentServer());
     EXPECT_FALSE(name_change->getCurrentServer());
 
 
-    // Previous state should be set by setState.
-    EXPECT_NO_THROW(name_change->setState(NameChangeTransaction::READY_ST));
-    EXPECT_NO_THROW(name_change->setState(NameChangeStub::DO_WORK_ST));
-    EXPECT_EQ(NameChangeTransaction::READY_ST, name_change->getPrevState());
-    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
-
-    // Last event should be set by setNextEvent.
-    EXPECT_NO_THROW(name_change->setNextEvent(NameChangeStub::
-                                              START_WORK_EVT));
-    EXPECT_NO_THROW(name_change->setNextEvent(NameChangeTransaction::
-                                              IO_COMPLETED_EVT));
-    EXPECT_EQ(NameChangeStub::START_WORK_EVT, name_change->getLastEvent());
-    EXPECT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
-              name_change->getNextEvent());
-
     // Verify that DNS update status can be set and retrieved.
     // Verify that DNS update status can be set and retrieved.
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
     EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
 
 
-    // Verify that the DNS update response can be retrieved. 
+    // Verify that the DNS update response can be retrieved.
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
     EXPECT_FALSE(name_change->getDnsUpdateResponse());
 
 
     // Verify that the forward change complete flag can be set and fetched.
     // Verify that the forward change complete flag can be set and fetched.
@@ -364,142 +375,23 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
 }
 }
 
 
-/// @brief Tests the fundamental methods used for state handler mapping.
+/// @brief Tests event and state dictionary construction and verification.
-/// Verifies the ability to search for and add entries in the state handler map.
+TEST_F(NameChangeTransactionTest, dictionaryCheck) {
-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.
-TEST_F(NameChangeTransactionTest, stubStateMapInit) {
     NameChangeStubPtr name_change;
     NameChangeStubPtr name_change;
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
 
 
-    // Verify that the map validation throws prior to the map being
+    // Verify that the event and state dictionary validation fails prior
-    // initialized.
+    // dictionary construction.
-    ASSERT_THROW(name_change->verifyStateHandlerMap(),
+    ASSERT_THROW(name_change->verifyEvents(), StateModelError);
-                 NameChangeTransactionError);
+    ASSERT_THROW(name_change->verifyStates(), StateModelError);
 
 
-    // Call initStateHandlerMap to initialize the state map.
+    // Construct both dictionaries.
-    ASSERT_NO_THROW(name_change->initStateHandlerMap());
+    ASSERT_NO_THROW(name_change->defineEvents());
+    ASSERT_NO_THROW(name_change->defineStates());
 
 
-    // Verify that the map validation succeeds now that the map is initialized.
+    // Verify both event and state dictionaries now pass validation.
-    ASSERT_NO_THROW(name_change->verifyStateHandlerMap());
+    ASSERT_NO_THROW(name_change->verifyEvents());
-}
+    ASSERT_NO_THROW(name_change->verifyStates());
-
-/// @brief Tests that invalid states are handled gracefully.
-/// This test verifies that attempting to execute a state which has no handler
-/// results in a failed transaction.
-TEST_F(NameChangeTransactionTest, invalidState) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Verfiy that to running the model with a state that has no handler,
-    // will result in failed transaction (status of ST_FAILED).
-    // First, verify state is NEW_ST and that NEW_ST has no handler.
-    // that the transaction failed:
-    ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState());
-    ASSERT_THROW(name_change->getStateHandler(NameChangeTransaction::NEW_ST),
-                 NameChangeTransactionError);
-
-    // Now call runStateModel() which should not throw.
-    EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction::
-                                               START_TRANSACTION_EVT));
-
-    // Verify that the transaction has failed.
-    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
-}
-
-/// @brief Tests that invalid events are handled gracefully.
-/// This test verifies that submitting an invalid event to the state machine
-/// results in a failed transaction.
-TEST_F(NameChangeTransactionTest, invalidEvent) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // First, lets execute the state model to a known valid point, by
-    // calling startTransaction.
-    ASSERT_NO_THROW(name_change->startTransaction());
-
-    // Verify we are in the state of DO_WORK_ST.
-    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
-
-    // Verity that submitting an invalid event to a valid state, results
-    // in a failed transaction without a throw (Current state is DO_WORK_ST,
-    // during which START_TRANSACTION_EVT, is invalid).
-    EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction::
-                                               START_TRANSACTION_EVT));
-
-    // Verify that the transaction has failed.
-    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
-}
-
-/// @brief Test the basic mechanics of state model execution.
-/// This test exercises the ability to execute state model from state to
-/// finish, including the handling of a asynchronous IO operation.
-TEST_F(NameChangeTransactionTest, stateModelTest) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Launch the transaction by calling startTransaction.  The state model
-    // should run up until the "IO" operation is initiated in DO_WORK_ST.
-
-    ASSERT_NO_THROW(name_change->startTransaction());
-
-    // Verify that we are now in state of DO_WORK_ST, the last event was
-    // START_WORK_EVT, the next event is NOP_EVT, and NCR status is ST_PENDING.
-    EXPECT_EQ(NameChangeStub::DO_WORK_ST, name_change->getState());
-    EXPECT_EQ(NameChangeStub::START_WORK_EVT, name_change->getLastEvent());
-    EXPECT_EQ(NameChangeTransaction::NOP_EVT, name_change->getNextEvent());
-    EXPECT_EQ(dhcp_ddns::ST_PENDING, name_change->getNcrStatus());
-
-    // Simulate completion of DNSClient exchange by invoking the callback, as
-    // DNSClient would.  This should cause the state model to progress through
-    // completion.
-    EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS));
-
-    // Verify that the state model has progressed through to completion:
-    // it is in the DONE_ST, the status is ST_COMPLETED, and the next event
-    // is NOP_EVT.
-    EXPECT_EQ(NameChangeTransaction::DONE_ST, name_change->getState());
-    EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus());
-    EXPECT_EQ(NameChangeTransaction::NOP_EVT, name_change->getNextEvent());
 }
 }
 
 
 /// @brief Tests server selection methods.
 /// @brief Tests server selection methods.
@@ -618,4 +510,90 @@ TEST_F(NameChangeTransactionTest, serverSelectionTest) {
     EXPECT_EQ (passes, num_servers);
     EXPECT_EQ (passes, num_servers);
 }
 }
 
 
+/// @brief Tests that the transaction will be "failed" upon model errors.
+TEST_F(NameChangeTransactionTest, modelFailure) {
+    NameChangeStubPtr name_change;
+    ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+    // Now call runModel() with an undefined event which should not throw,
+    // but should result in a failed model and failed transaction.
+    EXPECT_NO_THROW(name_change->runModel(9999));
+
+    // 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());
+}
+
 }
 }

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

@@ -0,0 +1,839 @@
+// 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_DERIVED_STATE_MIN + 1;
+
+    ///@brief Starting state for the test state model.
+    static const int READY_ST = SM_DERIVED_STATE_MIN + 2;
+
+    ///@brief State which simulates doing asynchronous work.
+    static const int DO_WORK_ST = SM_DERIVED_STATE_MIN + 3;
+
+    ///@brief State which finishes off processing.
+    static const int DONE_ST = SM_DERIVED_STATE_MIN + 4;
+
+    // StateModelTest events
+    ///@brief Event used to trigger initiation of asynchronous work.
+    static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+    ///@brief Event issued when the asynchronous work "completes".
+    static const int WORK_DONE_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+    ///@brief Event issued when all the work is done.
+    static const int ALL_DONE_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+    ///@brief Event used to trigger an attempt to transition to bad state
+    static const int FORCE_UNDEFINED_ST_EVT = SM_DERIVED_EVENT_MIN + 4;
+
+    ///@brief Event used to trigger an attempt to transition to bad state
+    static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5;
+
+    /// @brief Constructor
+    ///
+    /// Parameters match those needed by StateModel.
+    StateModelTest() : dummy_called_(false), work_completed_(false),
+                       failure_explanation_("") {
+    }
+    /// @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 the failure explanation string.
+    ///
+    /// This value is set only via onModelFailure and it stores whatever
+    /// explanation that method was passed.
+    const std::string& getFailureExplanation() {
+        return (failure_explanation_);
+    }
+
+    /// @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;
+        case FORCE_UNDEFINED_ST_EVT:
+            transition(9999, ALL_DONE_EVT);
+            break;
+        case SIMULATE_ERROR_EVT:
+            throw std::logic_error("Simulated Unexpected Error");
+            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 Construct the event dictionary.
+    virtual void defineEvents() {
+        // Invoke the base call implementation first.
+        StateModel::defineEvents();
+
+        // Define our events.
+        defineEvent(WORK_START_EVT, "WORK_START_EVT");
+        defineEvent(WORK_DONE_EVT , "WORK_DONE_EVT");
+        defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
+        defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
+        defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
+    }
+
+    /// @brief Verify the event dictionary.
+    virtual void verifyEvents() {
+        // Invoke the base call implementation first.
+        StateModel::verifyEvents();
+
+        // Verify our events.
+        getEvent(WORK_START_EVT);
+        getEvent(WORK_DONE_EVT);
+        getEvent(ALL_DONE_EVT);
+        getEvent(FORCE_UNDEFINED_ST_EVT);
+        getEvent(SIMULATE_ERROR_EVT);
+    }
+
+    /// @brief Construct the state dictionary.
+    virtual void defineStates() {
+        // Invoke the base call implementation first.
+        StateModel::defineStates();
+
+        // Define our states.
+        defineState(DUMMY_ST, "DUMMY_ST",
+                    boost::bind(&StateModelTest::dummyHandler, this));
+
+        defineState(READY_ST, "READY_ST",
+            boost::bind(&StateModelTest::readyHandler, this));
+
+        defineState(DO_WORK_ST, "DO_WORK_ST",
+            boost::bind(&StateModelTest::doWorkHandler, this));
+
+        defineState(DONE_ST, "DONE_ST",
+            boost::bind(&StateModelTest::doneWorkHandler, this));
+    }
+
+    /// @brief Verify the state dictionary.
+    virtual void verifyStates() {
+        // Invoke the base call implementation first.
+        StateModel::verifyStates();
+
+        // Verify our states.
+        getState(DUMMY_ST);
+        getState(READY_ST);
+        getState(DO_WORK_ST);
+        getState(DONE_ST);
+    }
+
+    /// @brief  Manually construct the event and state dictionaries.
+    /// This allows testing without running startModel.
+    void initDictionaires() {
+        ASSERT_NO_THROW(defineEvents());
+        ASSERT_NO_THROW(verifyEvents());
+        ASSERT_NO_THROW(defineStates());
+        ASSERT_NO_THROW(verifyStates());
+    }
+
+    /// @brief Tests the event dictionary entry for the given event value.
+    bool checkEvent(const int value, const std::string& label) {
+        EventPtr event;
+        try  {
+            event = getEvent(value);
+            EXPECT_TRUE(event);
+            EXPECT_EQ(value, event->getValue());
+            EXPECT_EQ(label, event->getLabel());
+        } catch (const std::exception& ex) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /// @brief Tests the state dictionary entry for the given state value.
+    bool checkState(const int value, const std::string& label) {
+        EventPtr state;
+        try  {
+            state = getState(value);
+            EXPECT_TRUE(state);
+            EXPECT_EQ(value, state->getValue());
+            EXPECT_EQ(label, state->getLabel());
+        } catch (const std::exception& ex) {
+            return false;
+        }
+
+        return true;
+    }
+
+
+    /// @brief  Handler called when the model suffers an execution error.
+    virtual void onModelFailure(const std::string& explanation) {
+        failure_explanation_ = explanation;
+    }
+
+    /// @brief Indicator of whether or not the DUMMY_ST handler has been called.
+    bool dummy_called_;
+
+    /// @brief Indicator of whether or not DONE_ST handler has been called.
+    bool work_completed_;
+
+    /// @brief Stores the failure explanation
+    std::string failure_explanation_;
+};
+
+// Declare them so gtest can see them.
+const int StateModelTest::DUMMY_ST;
+const int StateModelTest::READY_ST;
+const int StateModelTest::DO_WORK_ST;
+const int StateModelTest::DONE_ST;
+const int StateModelTest::WORK_START_EVT;
+const int StateModelTest::WORK_DONE_EVT;
+const int StateModelTest::ALL_DONE_EVT;
+
+/// @brief Checks the fundamentals of defining and retrieving events.
+TEST_F(StateModelTest, eventDefinition) {
+    // After construction, the event dictionary should be empty. Verify that
+    // getEvent will throw when event is not defined.
+    EXPECT_THROW(getEvent(NOP_EVT), StateModelError);
+
+    // Verify that we can add a handler to the map.
+    ASSERT_NO_THROW(defineEvent(NOP_EVT, "NOP_EVT"));
+
+    // Verify that we can find the event by value and its content is correct.
+    EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+
+    // Verify that we cannot add a duplicate.
+    ASSERT_THROW(defineEvent(NOP_EVT, "NOP_EVT"), StateModelError);
+
+    // Verify that we can still find the event.
+    EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+}
+
+/// @brief Tests event dictionary construction and verification.
+TEST_F(StateModelTest, eventDictionary) {
+    // After construction, the event dictionary should be empty.
+    // Make sure that verifyEvents() throws.
+    EXPECT_THROW(verifyEvents(), StateModelError);
+
+    // Construct the dictionary and verify it.
+    EXPECT_NO_THROW(defineEvents());
+    EXPECT_NO_THROW(verifyEvents());
+
+    // Verify base class events are defined.
+    EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+    EXPECT_TRUE(checkEvent(START_EVT, "START_EVT"));
+    EXPECT_TRUE(checkEvent(END_EVT, "END_EVT"));
+    EXPECT_TRUE(checkEvent(FAIL_EVT, "FAIL_EVT"));
+
+    // Verify stub class events are defined.
+    EXPECT_TRUE(checkEvent(WORK_START_EVT, "WORK_START_EVT"));
+    EXPECT_TRUE(checkEvent(WORK_DONE_EVT, "WORK_DONE_EVT"));
+    EXPECT_TRUE(checkEvent(ALL_DONE_EVT, "ALL_DONE_EVT"));
+    EXPECT_TRUE(checkEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT"));
+    EXPECT_TRUE(checkEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT"));
+
+    // Verify that undefined events are handled correctly.
+    EXPECT_THROW(getEvent(9999), StateModelError);
+    EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getEventLabel(9999));
+}
+
+/// @brief General testing of event context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, eventContextAccessors) {
+    // Construct the event definitions, normally done by startModel.
+    ASSERT_NO_THROW(defineEvents());
+    ASSERT_NO_THROW(verifyEvents());
+
+    // Verify the post-construction 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));
+
+    // Verify the values are what we expect.
+    EXPECT_EQ(WORK_START_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+
+    // Verify that posting an undefined event throws.
+    EXPECT_THROW(postNextEvent(9999), StateModelError);
+}
+
+/// @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, stateDefinition) {
+    // After construction, the state dictionary should be empty. Verify that
+    // getState will throw when, state is not defined.
+    EXPECT_THROW(getState(READY_ST), StateModelError);
+
+    // Verify that we can add a state to the dictionary.
+    ASSERT_NO_THROW(defineState(READY_ST, "READY_ST",
+                                boost::bind(&StateModelTest::dummyHandler,
+                                            this)));
+
+    // Verify that we can find the state by its value.
+    StatePtr state;
+    EXPECT_NO_THROW(state = getState(READY_ST));
+    EXPECT_TRUE(state);
+
+    // Verify the state's value and label.
+    EXPECT_EQ(READY_ST, state->getValue());
+    EXPECT_EQ("READY_ST", state->getLabel());
+
+    // Now verify that retrieved state's handler executes the correct method.
+    // Make sure the dummy called flag is false prior to invocation.
+    EXPECT_FALSE(getDummyCalled());
+
+    // Invoke the state's handler.
+    EXPECT_NO_THROW(state->run());
+
+    // Verify the dummy called flag is now true.
+    EXPECT_TRUE(getDummyCalled());
+
+    // Verify that we cannot add a duplicate.
+    EXPECT_THROW(defineState(READY_ST, "READY_ST",
+                             boost::bind(&StateModelTest::readyHandler, this)),
+                 StateModelError);
+
+    // Verify that we can still find the state.
+    EXPECT_NO_THROW(getState(READY_ST));
+}
+
+/// @brief Tests state dictionary initialization and validation.
+/// This tests the basic concept of state dictionary initialization and
+/// verification by manually invoking the methods normally called by startModel.
+TEST_F(StateModelTest, stateDictionary) {
+    // Verify that the map validation throws prior to the dictionary being
+    // initialized.
+    EXPECT_THROW(verifyStates(), StateModelError);
+
+    // Construct the dictionary and verify it.
+    ASSERT_NO_THROW(defineStates());
+    EXPECT_NO_THROW(verifyStates());
+
+    // Verify the base class states.
+    EXPECT_TRUE(checkState(NEW_ST, "NEW_ST"));
+    EXPECT_TRUE(checkState(END_ST, "END_ST"));
+
+    // Verify stub class states.
+    EXPECT_TRUE(checkState(DUMMY_ST, "DUMMY_ST"));
+    EXPECT_TRUE(checkState(READY_ST, "READY_ST"));
+    EXPECT_TRUE(checkState(DO_WORK_ST, "DO_WORK_ST"));
+    EXPECT_TRUE(checkState(DONE_ST, "DONE_ST"));
+
+    // Verify that undefined states are handled correctly.
+    EXPECT_THROW(getState(9999), StateModelError);
+    EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getStateLabel(9999));
+}
+
+/// @brief General testing of state context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, stateContextAccessors) {
+    // setState will throw unless we initialize the handler map.
+    ASSERT_NO_THROW(defineStates());
+    ASSERT_NO_THROW(verifyStates());
+
+    // Verify post-construction state values.
+    EXPECT_EQ(NEW_ST, getCurrState());
+    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, getCurrState());
+    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, getCurrState());
+    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));
+
+    // Verify that calling setState with an undefined state throws.
+    EXPECT_THROW(setState(9999), StateModelError);
+}
+
+/// @brief Checks that invoking runModel prior to startModel is not allowed.
+TEST_F(StateModelTest, runBeforeStart) {
+    // Verify that the failure explanation is empty and work is not done.
+    EXPECT_TRUE(getFailureExplanation().empty());
+
+    // Attempt to call runModel before startModel.  This should result in an
+    // orderly model failure.
+    ASSERT_NO_THROW(runModel(START_EVT));
+
+    // Check that state and event are correct.
+    EXPECT_EQ(END_ST, getCurrState());
+    EXPECT_EQ(FAIL_EVT, getNextEvent());
+
+    // Verify that failure explanation is not empty.
+    EXPECT_FALSE(getFailureExplanation().empty());
+}
+
+/// @brief Tests that the endModel may be used to transition the model to
+/// a normal conclusion.
+TEST_F(StateModelTest, transitionWithEnd) {
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
+
+    // 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, getCurrState());
+    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, getCurrState());
+    EXPECT_EQ(DUMMY_ST, getPrevState());
+    EXPECT_EQ(END_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the abortModel may be used to transition the model to
+/// failed conclusion.
+TEST_F(StateModelTest, transitionWithAbort) {
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
+
+    // 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, getCurrState());
+    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("test invocation"));
+
+    // Verify state and event members are correctly set.
+    EXPECT_EQ(END_ST, getCurrState());
+    EXPECT_EQ(DUMMY_ST, getPrevState());
+    EXPECT_EQ(FAIL_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the boolean indicators for on state entry and exit
+/// work properly.
+TEST_F(StateModelTest, doFlags) {
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
+
+    // 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());
+
+}
+
+/// @brief Verifies that the model status methods accurately reflect the model
+/// status.  It also verifies that the dictionaries can be modified before
+/// the model is running but not after.
+TEST_F(StateModelTest, statusMethods) {
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
+
+    // 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());
+
+    // Verify that events and states can be added before the model is started.
+    EXPECT_NO_THROW(defineEvent(9998, "9998"));
+    EXPECT_NO_THROW(defineState(9998, "9998",
+                                boost::bind(&StateModelTest::readyHandler,
+                                            this)));
+
+    // "START" the model.
+    // Fake out starting the model by calling transition to move from NEW_ST
+    // to DUMMY_ST with START_EVT.  If we used startModel this would blow by
+    // the status  of "running" but not "waiting".
+    EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+
+    // Verify that events and states cannot be added after the model is started.
+    EXPECT_THROW(defineEvent(9999, "9999"), StateModelError);
+    EXPECT_THROW(defineState(9999, "9999",
+                             boost::bind(&StateModelTest::readyHandler, this)),
+                 StateModelError);
+
+    // 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());
+}
+
+/// @brief Tests that the model status methods are correct after a model
+/// failure.
+TEST_F(StateModelTest, statusMethodsOnFailure) {
+    // Construct the event and state definitions, normally done by startModel.
+    ASSERT_NO_THROW(defineEvents());
+    ASSERT_NO_THROW(verifyEvents());
+    ASSERT_NO_THROW(defineStates());
+    ASSERT_NO_THROW(verifyStates());
+
+    // 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("test invocation"));
+
+    // 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());
+}
+
+/// @brief Checks that the context strings accurately reflect context and
+/// are safe to invoke.
+TEST_F(StateModelTest, contextStrs) {
+    // Verify context methods do not throw prior to dictionary init.
+    ASSERT_NO_THROW(getContextStr());
+    ASSERT_NO_THROW(getPrevContextStr());
+
+    // Construct the event and state definitions, normally done by startModel.
+    ASSERT_NO_THROW(defineEvents());
+    ASSERT_NO_THROW(verifyEvents());
+    ASSERT_NO_THROW(defineStates());
+    ASSERT_NO_THROW(verifyStates());
+
+    // transition uses setState and setEvent, testing it tests all three.
+    EXPECT_NO_THROW(transition(READY_ST, START_EVT));
+
+    // Verify the current context string depicts correct state and event.
+    std::string ctx_str;
+    ASSERT_NO_THROW(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)));
+
+    // Verify the previous context string depicts correct state and event.
+    ASSERT_NO_THROW(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 undefined states are handled gracefully.
+/// This test verifies that attempting to transition to an undefined state,
+/// which constitutes a model violation, results in an orderly model failure.
+TEST_F(StateModelTest, undefinedState) {
+    // Verify that the failure explanation is empty and work is not done.
+    EXPECT_TRUE(getFailureExplanation().empty());
+    EXPECT_FALSE(getWorkCompleted());
+
+    // First, lets execute the state model to a known valid point, by
+    // calling startModel. 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, getCurrState());
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+
+    // Resume the model with next event set to cause the DO_WORK_ST handler
+    // to transition to an undefined state. This should cause it to return
+    // without throwing and yield a failed model.
+    EXPECT_NO_THROW(runModel(FORCE_UNDEFINED_ST_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 failure explanation is not empty.
+    EXPECT_FALSE(getFailureExplanation().empty());
+
+    // Verify that work completed flag is still false.
+    EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that an unexpected exception thrown by a state handler is
+/// handled gracefully.  State models are supposed to account for and handle
+/// all errors that they actions (i.e. handlers) may cause.  In the event they
+/// do not, this constitutes a model violation.  This test verifies such
+/// violations are handled correctly and result in an orderly model failure.
+TEST_F(StateModelTest, unexpectedError) {
+    // Verify that the failure explanation is empty and work is not done.
+    EXPECT_TRUE(getFailureExplanation().empty());
+    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, getCurrState());
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+
+    // Resume the model with next event set to cause the DO_WORK_ST handler
+    // to transition to an undefined state. This should cause it to return
+    // without throwing and yield a failed model.
+    EXPECT_NO_THROW(runModel(SIMULATE_ERROR_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 failure explanation is not empty.
+    EXPECT_FALSE(getFailureExplanation().empty());
+
+    // Verify that work completed flag is still false.
+    EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that undefined events are handled gracefully.
+/// This test verifies that submitting an undefined event to the state machine
+/// results, which constitutes a model violation, results in an orderly model
+/// failure.
+TEST_F(StateModelTest, undefinedEvent) {
+    // Verify that the failure explanation is empty and work is not done.
+    EXPECT_TRUE(getFailureExplanation().empty());
+    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, getCurrState());
+    EXPECT_EQ(NOP_EVT, getNextEvent());
+
+    // Attempting to post an undefined event within runModel should cause it
+    // to return without throwing and yield a failed model.
+    EXPECT_NO_THROW(runModel(9999));
+
+    // 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 failure explanation is not empty.
+    EXPECT_FALSE(getFailureExplanation().empty());
+
+    // 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 start 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 failure explanation is empty and work is not done.
+    EXPECT_TRUE(getFailureExplanation().empty());
+    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, getCurrState());
+    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, getCurrState());
+    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 failure explanation is empty.
+    EXPECT_TRUE(getFailureExplanation().empty());
+
+    // Verify that work completed flag is true.
+    EXPECT_TRUE(getWorkCompleted());
+}
+
+}

+ 2 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -473,10 +473,12 @@ private:
     /// initiate server shutdown procedure.
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
     volatile bool shutdown_;
 
 
+#if 0
     /// Indexes for registered hook points
     /// Indexes for registered hook points
     int hook_index_pkt6_receive_;
     int hook_index_pkt6_receive_;
     int hook_index_subnet6_select_;
     int hook_index_subnet6_select_;
     int hook_index_pkt6_send_;
     int hook_index_pkt6_send_;
+#endif
 
 
     /// UDP port number on which server listens.
     /// UDP port number on which server listens.
     uint16_t port_;
     uint16_t port_;