Commit f69dc1f6 authored by Thomas Markwalder's avatar Thomas Markwalder

[3156] Addressed review comments for b10-dhcp-ddns/NameChangeTransaction

Created new classes, LabeledValue and LabeledValueSet to
provide a cleaner mechanism for defining the set of valid events
and states.  With this commit, events now use these new constructs.
The modifications to use these constructs for states will be done
as separate commit.

Some addtional, minor review comments were also addressed.
parent 7edd2656
......@@ -59,6 +59,7 @@ b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
b10_dhcp_ddns_SOURCES += d2_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
......
// 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 char* label)
: value_(value), label_(label) {
if (label == NULL || strlen(label) == 0) {
isc_throw(LabeledValueError, "labels cannot be empty");
}
}
LabeledValue::~LabeledValue(){
}
int
LabeledValue::getValue() const {
return (value_);
}
const char*
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 char* 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());
}
const char*
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 (UNDEFINED_LABEL);
}
} // namespace isc::d2
} // namespace isc
// 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 null or empty.
LabeledValue(const int value, const char* 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 const char*
const char* 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.
const char* 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 null or empty, or if the set
/// already contains an entry with the same value.
void add(const int value, const char* 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.
const char* getLabel(const int value) const;
private:
/// @brief The map of labeled values.
LabeledValueMap map_;
};
} // namespace isc::d2
} // namespace isc
#endif
......@@ -81,6 +81,29 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
runModel(IO_COMPLETED_EVT);
}
void
NameChangeTransaction::defineEvents() {
StateModel::defineEvents();
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");
}
void
NameChangeTransaction::verifyEvents() {
StateModel::verifyEvents();
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::verifyStateHandlerMap() {
......@@ -230,39 +253,5 @@ NameChangeTransaction::getStateLabel(const int state) const {
return (str);
}
const char*
NameChangeTransaction::getEventLabel(const int event) const {
const char* str = "Unknown";
switch(event) {
case SELECT_SERVER_EVT:
str = "NameChangeTransaction::SELECT_SERVER_EVT";
break;
case SERVER_SELECTED_EVT:
str = "NameChangeTransaction::SERVER_SELECTED_EVT";
break;
case SERVER_IO_ERROR_EVT:
str = "NameChangeTransaction::SERVER_IO_ERROR_EVT";
break;
case NO_MORE_SERVERS_EVT:
str = "NameChangeTransaction::NO_MORE_SERVERS_EVT";
break;
case IO_COMPLETED_EVT:
str = "NameChangeTransaction::IO_COMPLETED_EVT";
break;
case UPDATE_OK_EVT:
str = "NameChangeTransaction::UPDATE_OK_EVT";
break;
case UPDATE_FAILED_EVT:
str = "NameChangeTransaction::UPDATE_FAILED_EVT";
break;
default:
str = StateModel::getEventLabel(event);
break;
}
return (str);
}
} // namespace isc::d2
} // namespace isc
......@@ -182,6 +182,28 @@ public:
virtual void operator()(DNSClient::Status status);
protected:
/// @brief Adds events defined by NameChangeTransaction to the event set.
///
/// 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.
///
/// @throw StateModelError if an event definition is invalid or a duplicate.
virtual void defineEvents();
/// @brief Validates the contents of the set of events.
///
/// 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.
///
/// @throw StateModelError if an event value is undefined.
virtual void verifyEvents();
/// @brief Populates the map of state handlers.
///
/// This method is used by derivations to construct a map of states to
......@@ -404,43 +426,6 @@ public:
/// "Unknown" if the value cannot be mapped.
virtual const char* getStateLabel(const int state) const;
/// @brief Converts a event value into a text label.
///
/// This method supplies labels for NameChangeTransactions's predefined
/// events. It is declared virtual to allow derivations to embed a call to
/// this method within their own implementation which would define labels
/// for their events. An example implementation might look like the
/// following:
/// @code
///
/// class DerivedTrans : public NameChangeTransaction {
/// :
/// static const int EXAMPLE_1_EVT = NCT_EVENT_MAX + 1;
/// :
/// const char* getEventLabel(const int event) const {
/// const char* str = "Unknown";
/// switch(event) {
/// case EXAMPLE_1_EVT:
/// str = "DerivedTrans::EXAMPLE_1_EVT";
/// break;
/// :
/// default:
/// // Not a derived event, pass it to NameChangeTransaction's
/// // method.
/// str = StateModel::getEventLabel(event);
/// break;
/// }
///
/// return (str);
/// }
///
/// @endcode
/// @param event is the event for which a label is desired.
///
/// @return Returns a const char* containing the event label or
/// "Unknown" if the value cannot be mapped.
virtual const char* getEventLabel(const int state) const;
private:
/// @brief The IOService which should be used to for IO processing.
isc::asiolink::IOService& io_service_;
......
......@@ -45,6 +45,13 @@ StateModel::~StateModel(){
void
StateModel::startModel(const int start_state) {
try {
defineEvents();
verifyEvents();
} catch (const std::exception& ex) {
isc_throw(StateModelError, "Event set is invalid: " << ex.what());
}
// Initialize the state handler map first.
initStateHandlerMap();
......@@ -83,6 +90,32 @@ StateModel::runModel(unsigned int run_event) {
}
}
void
StateModel::defineEvent(unsigned int event_value, const char* label) {
if (!isModelNew()) {
// Don't allow for self-modifying maps.
isc_throw(StateModelError, "Events may only be added to a new model."
<< event_value << " - " << label);
}
// add method may throw on duplicate or empty label.
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));
}
StateHandler
StateModel::getStateHandler(unsigned int state) {
StateHandlerMap::iterator it = state_handlers_.find(state);
......@@ -96,7 +129,14 @@ StateModel::getStateHandler(unsigned int state) {
}
void
StateModel::addToMap(unsigned int state, StateHandler handler) {
StateModel::addToStateHandlerMap(unsigned int state, StateHandler handler) {
if (!isModelNew()) {
// Don't allow for self-modifying maps.
isc_throw(StateModelError,
"State handler mappings can only be added to a new model."
<< state << " - " << getStateLabel(state));
}
StateHandlerMap::iterator it = state_handlers_.find(state);
// Check for a duplicate attempt. State's can't have more than one.
if (it != state_handlers_.end()) {
......@@ -114,6 +154,23 @@ StateModel::addToMap(unsigned int state, StateHandler handler) {
state_handlers_[state] = handler;
}
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::transition(unsigned int state, unsigned int event) {
setState(state);
......@@ -150,9 +207,14 @@ StateModel::setState(unsigned int state) {
}
void
StateModel::postNextEvent(unsigned int event) {
StateModel::postNextEvent(unsigned int event_value) {
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;
next_event_ = event_value;
}
bool
......@@ -252,25 +314,7 @@ StateModel::getPrevContextStr() const {
const char*
StateModel::getEventLabel(const int event) const {
const char* str = "Unknown";
switch(event) {
case NOP_EVT:
str = "StateModel::NOP_EVT";
break;
case START_EVT:
str = "StateModel::START_EVT";
break;
case END_EVT:
str = "StateModel::END_EVT";
break;
case FAIL_EVT:
str = "StateModel::FAIL_EVT";
break;
default:
break;
}
return (str);
return (events_.getLabel(event));
}
} // namespace isc::d2
......
......@@ -15,12 +15,13 @@
#ifndef STATE_MODEL_H
#define STATE_MODEL_H
/// @file nc_trans.h This file defines the class StateModel.
/// @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>
......@@ -38,6 +39,12 @@ public:
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;
......@@ -50,8 +57,8 @@ typedef std::map<unsigned int, StateHandler> StateHandlerMap;
/// of a basic finite state machine.
///
/// The state model implementation used is a very basic approach. States
/// and events are simple integer constants. Each state must have a state
/// handler. State handlers are void methods which require no parameters.
/// and events described by simple integer constants. Each state must have a
/// state handler. State handlers are void methods which require no parameters.
/// Each model instance contains a map of states to instance method pointers
/// to their respective state handlers. The model tracks the following
/// context values:
......@@ -92,7 +99,7 @@ typedef std::map<unsigned int, StateHandler> StateHandlerMap;
///
/// 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