Parcourir la source

[master] Merge branch 'trac3156'

Extracts the d2 finite state machine implementation from within
d2::NameChangeTransacation into its own class d2::StateModel.
Thomas Markwalder il y a 11 ans
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_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
+b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes

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

@@ -199,7 +199,7 @@ public:
     /// 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.
     ///
-    /// @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.
     ///

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

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

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

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

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

+ 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_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
+d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -77,7 +79,9 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
+d2_unittests_SOURCES += labeled_value_unittests.cc
 d2_unittests_SOURCES += nc_trans_unittests.cc
+d2_unittests_SOURCES += state_model_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

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