Commit ea2cd98c authored by Thomas Markwalder's avatar Thomas Markwalder

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

b10-dhcp-ddns Finite state machine logic was refactored into its own class,
StateModel.
parent bc9c1208
......@@ -60,6 +60,7 @@ b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
......
......@@ -253,8 +253,8 @@ in event loop.
This is informational message issued when the application has been instructed
to shut down by the controller.
% DHCP_DDNS_TRANS_PROCESS_EROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
This is error message issued when the application fails to process a
% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1 , %2
This is error message issued when the application fails to process a
NameChangeRequest correctly. Some or all of the DNS updates requested as part
of this update did not succeed. This is a programmatic error and should be
reported.
reported.
......@@ -19,17 +19,15 @@ namespace isc {
namespace d2 {
// Common transaction states
const int NameChangeTransaction::NEW_ST;
const int NameChangeTransaction::READY_ST;
const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
const int NameChangeTransaction::DONE_ST;
const int NameChangeTransaction::PROCESS_TRANS_OK_ST;
const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST;
const int NameChangeTransaction::DERIVED_STATES;
const int NameChangeTransaction::NCT_STATE_MAX;
// Common transaction events
const int NameChangeTransaction::NOP_EVT;
const int NameChangeTransaction::START_TRANSACTION_EVT;
const int NameChangeTransaction::SELECT_SERVER_EVT;
const int NameChangeTransaction::SERVER_SELECTED_EVT;
const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
......@@ -37,18 +35,16 @@ const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
const int NameChangeTransaction::IO_COMPLETED_EVT;
const int NameChangeTransaction::UPDATE_OK_EVT;
const int NameChangeTransaction::UPDATE_FAILED_EVT;
const int NameChangeTransaction::ALL_DONE_EVT;
const int NameChangeTransaction::DERIVED_EVENTS;
const int NameChangeTransaction::NCT_EVENT_MAX;
NameChangeTransaction::
NameChangeTransaction(isc::asiolink::IOService& io_service,
dhcp_ddns::NameChangeRequestPtr& ncr,
DdnsDomainPtr& forward_domain,
DdnsDomainPtr& reverse_domain)
: state_handlers_(), io_service_(io_service), ncr_(ncr),
forward_domain_(forward_domain), reverse_domain_(reverse_domain),
dns_client_(), state_(NEW_ST), next_event_(NOP_EVT),
: io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
reverse_domain_(reverse_domain), dns_client_(),
dns_update_status_(DNSClient::OTHER), dns_update_response_(),
forward_change_completed_(false), reverse_change_completed_(false),
current_server_list_(), current_server_(), next_server_pos_(0) {
......@@ -65,10 +61,6 @@ NameChangeTransaction(isc::asiolink::IOService& io_service,
isc_throw(NameChangeTransactionError,
"Reverse change must have a reverse domain");
}
// Use setters here so we get proper values for previous state, last event.
setState(state_);
setNextEvent(NOP_EVT);
}
NameChangeTransaction::~NameChangeTransaction(){
......@@ -76,85 +68,32 @@ NameChangeTransaction::~NameChangeTransaction(){
void
NameChangeTransaction::startTransaction() {
// Initialize the state handler map first.
initStateHandlerMap();
// Test validity of the handler map. This provides an opportunity to
// sanity check the map prior to attempting to execute the model.
verifyStateHandlerMap();
// Set the current state to READY and enter the run loop.
setState(READY_ST);
runStateModel(START_TRANSACTION_EVT);
startModel(READY_ST);
}
void
NameChangeTransaction::operator()(DNSClient::Status status) {
// Stow the completion status and re-enter the run loop with the event
// set to indicate IO completed.
// runStateModel is exception safe so we are good to call it here.
// runModel is exception safe so we are good to call it here.
// It won't exit until we hit the next IO wait or the state model ends.
setDnsUpdateStatus(status);
runStateModel(IO_COMPLETED_EVT);
runModel(IO_COMPLETED_EVT);
}
void
NameChangeTransaction::runStateModel(unsigned int run_event) {
try {
// Seed the loop with the given event as the next to process.
setNextEvent(run_event);
do {
// Invoke the current state's handler. It should consume the
// next event, then determine what happens next by setting
// current state and/or the next event.
(getStateHandler(state_))();
// Keep going until a handler sets next event to a NOP_EVT.
} while (getNextEvent() != NOP_EVT);
}
catch (const std::exception& ex) {
// Transaction has suffered an unexpected exception. This indicates
// a programmatic shortcoming. Log it and set status to ST_FAILED.
// In theory, the model should account for all error scenarios and
// deal with them accordingly.
LOG_ERROR(dctl_logger, DHCP_DDNS_TRANS_PROCESS_EROR).arg(ex.what());
setNcrStatus(dhcp_ddns::ST_FAILED);
}
}
StateHandler
NameChangeTransaction::getStateHandler(unsigned int state) {
StateHandlerMap::iterator it = state_handlers_.find(state);
if (it == state_handlers_.end()) {
isc_throw(NameChangeTransactionError, "Invalid state: " << state);
}
return ((*it).second);
}
void
NameChangeTransaction::addToMap(unsigned int state, StateHandler handler) {
StateHandlerMap::iterator it = state_handlers_.find(state);
if (it != state_handlers_.end()) {
isc_throw(NameChangeTransactionError,
"Attempted duplicate entry in state handler mape, state: "
<< state);
}
state_handlers_[state] = handler;
}
void
NameChangeTransaction::setState(unsigned int state) {
prev_state_ = state_;
state_ = state;
NameChangeTransaction::verifyStateHandlerMap() {
getStateHandler(READY_ST);
getStateHandler(SELECTING_FWD_SERVER_ST);
getStateHandler(SELECTING_REV_SERVER_ST);
getStateHandler(PROCESS_TRANS_OK_ST);
getStateHandler(PROCESS_TRANS_FAILED_ST);
}
void
NameChangeTransaction::setNextEvent(unsigned int event) {
last_event_ = next_event_;
next_event_ = event;
NameChangeTransaction::onModelFailure() {
setNcrStatus(dhcp_ddns::ST_FAILED);
}
void
......@@ -244,26 +183,6 @@ NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
return (ncr_->setStatus(status));
}
unsigned int
NameChangeTransaction::getState() const {
return (state_);
}
unsigned int
NameChangeTransaction::getPrevState() const {
return (prev_state_);
}
unsigned int
NameChangeTransaction::getLastEvent() const {
return (last_event_);
}
unsigned int
NameChangeTransaction::getNextEvent() const {
return (next_event_);
}
DNSClient::Status
NameChangeTransaction::getDnsUpdateStatus() const {
return (dns_update_status_);
......@@ -284,6 +203,66 @@ NameChangeTransaction::getReverseChangeCompleted() const {
return (reverse_change_completed_);
}
const char*
NameChangeTransaction::getStateLabel(const int state) const {
const char* str = "Unknown";
switch(state) {
case READY_ST:
str = "NameChangeTransaction::READY_ST";
break;
case SELECTING_FWD_SERVER_ST:
str = "NameChangeTransaction::SELECTING_FWD_SERVER_ST";
break;
case SELECTING_REV_SERVER_ST:
str = "NameChangeTransaction::SELECTING_REV_SERVER_ST";
break;
case PROCESS_TRANS_OK_ST:
str = "NameChangeTransaction::PROCESS_TRANS_OK_ST";
break;
case PROCESS_TRANS_FAILED_ST:
str = "NameChangeTransaction::PROCESS_TRANS_FAILED_ST";
break;
default:
str = StateModel::getStateLabel(state);
break;
}
return (str);
}
const char*
NameChangeTransaction::getEventLabel(const int event) const {
const char* str = "Unknown";
switch(event) {
case SELECT_SERVER_EVT:
str = "NameChangeTransaction::SELECT_SERVER_EVT";
break;
case SERVER_SELECTED_EVT:
str = "NameChangeTransaction::SERVER_SELECTED_EVT";
break;
case SERVER_IO_ERROR_EVT:
str = "NameChangeTransaction::SERVER_IO_ERROR_EVT";
break;
case NO_MORE_SERVERS_EVT:
str = "NameChangeTransaction::NO_MORE_SERVERS_EVT";
break;
case IO_COMPLETED_EVT:
str = "NameChangeTransaction::IO_COMPLETED_EVT";
break;
case UPDATE_OK_EVT:
str = "NameChangeTransaction::UPDATE_OK_EVT";
break;
case UPDATE_FAILED_EVT:
str = "NameChangeTransaction::UPDATE_FAILED_EVT";
break;
default:
str = StateModel::getEventLabel(event);
break;
}
return (str);
}
} // namespace isc::d2
} // namespace isc
This diff is collapsed.
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <d2/d2_log.h>
#include <d2/state_model.h>
#include <string>
namespace isc {
namespace d2 {
// Common state model states
const int StateModel::NEW_ST;
const int StateModel::END_ST;
const int StateModel::SM_STATE_MAX;
// Common state model events
const int StateModel::NOP_EVT;
const int StateModel::START_EVT;
const int StateModel::END_EVT;
const int StateModel::FAIL_EVT;
const int StateModel::SM_EVENT_MAX;
StateModel::StateModel() : state_handlers_(), state_(NEW_ST),
prev_state_(NEW_ST), last_event_(NOP_EVT),
next_event_(NOP_EVT), on_entry_flag_(false),
on_exit_flag_(false) {
}
StateModel::~StateModel(){
}
void
StateModel::startModel(const int start_state) {
// Initialize the state handler map first.
initStateHandlerMap();
// Test validity of the handler map. This provides an opportunity to
// sanity check the map prior to attempting to execute the model.
verifyStateHandlerMap();
// Set the current state to startng state and enter the run loop.
setState(start_state);
runModel(START_EVT);
}
void
StateModel::runModel(unsigned int run_event) {
try {
// Seed the loop with the given event as the next to process.
postNextEvent(run_event);
do {
// Invoke the current state's handler. It should consume the
// next event, then determine what happens next by setting
// current state and/or the next event.
(getStateHandler(state_))();
// Keep going until a handler sets next event to a NOP_EVT.
} while (!isModelDone() && getNextEvent() != NOP_EVT);
}
catch (const std::exception& ex) {
// The model has suffered an unexpected exception. This constitutes a
// a model violation and indicates a programmatic shortcoming.
// In theory, the model should account for all error scenarios and
// deal with them accordingly. Log it and transition to END_ST with
// FAILED_EVT via abortModel.
LOG_ERROR(dctl_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
.arg(ex.what()).arg(getContextStr());
abortModel();
}
}
StateHandler
StateModel::getStateHandler(unsigned int state) {
StateHandlerMap::iterator it = state_handlers_.find(state);
// It is wrong to try to find a state that isn't mapped.
if (it == state_handlers_.end()) {
isc_throw(StateModelError, "No handler for state: "
<< state << " - " << getStateLabel(state));
}
return ((*it).second);
}
void
StateModel::addToMap(unsigned int state, StateHandler handler) {
StateHandlerMap::iterator it = state_handlers_.find(state);
// Check for a duplicate attempt. State's can't have more than one.
if (it != state_handlers_.end()) {
isc_throw(StateModelError,
"Attempted duplicate entry in state handler map, state: "
<< state << " - " << getStateLabel(state));
}
// Do not allow handlers for special states fo NEW_ST and END_ST.
if (state == NEW_ST || state == END_ST) {
isc_throw(StateModelError, "A handler is not supported for state: "
<< state << " - " << getStateLabel(state));
}
state_handlers_[state] = handler;
}
void
StateModel::transition(unsigned int state, unsigned int event) {
setState(state);
postNextEvent(event);
}
void
StateModel::endModel() {
transition(END_ST, END_EVT);
}
void
StateModel::abortModel() {
transition(END_ST, FAIL_EVT);
onModelFailure();
}
void
StateModel::setState(unsigned int state) {
// If the new state isn't NEW_ST or END_ST, make sure it has a handler.
if ((state && state != NEW_ST && state != END_ST)
&& (state_handlers_.end() == state_handlers_.find(state))) {
isc_throw(StateModelError, "Attempt to set state to invalid stat :"
<< state << "=" << getStateLabel(state));
}
prev_state_ = state_;
state_ = state;
// Set the "do" flags if we are transitioning.
on_entry_flag_ = ((state != END_ST) && (prev_state_ != state_));
// At this time they are calculated the same way.
on_exit_flag_ = on_entry_flag_;
}
void
StateModel::postNextEvent(unsigned int event) {
last_event_ = next_event_;
next_event_ = event;
}
bool
StateModel::doOnEntry() {
bool ret = on_entry_flag_;
on_entry_flag_ = false;
return (ret);
}
bool
StateModel::doOnExit() {
bool ret = on_exit_flag_;
on_exit_flag_ = false;
return (ret);
}
unsigned int
StateModel::getState() const {
return (state_);
}
unsigned int
StateModel::getPrevState() const {
return (prev_state_);
}
unsigned int
StateModel::getLastEvent() const {
return (last_event_);
}
unsigned int
StateModel::getNextEvent() const {
return (next_event_);
}
bool
StateModel::isModelNew() const {
return (state_ == NEW_ST);
}
bool
StateModel::isModelRunning() const {
return ((state_ != NEW_ST) && (state_ != END_ST));
}
bool
StateModel::isModelWaiting() const {
return (isModelRunning() && (next_event_ == NOP_EVT));
}
bool
StateModel::isModelDone() const {
return (state_ == END_ST);
}
bool
StateModel::didModelFail() const {
return (isModelDone() && (next_event_ == FAIL_EVT));
}
const char*
StateModel::getStateLabel(const int state) const {
const char* str = "Unknown";
switch(state) {
case NEW_ST:
str = "StateModel::NEW_ST";
break;
case END_ST:
str = "StateModel::END_ST";
break;
default:
break;
}
return (str);
}
std::string
StateModel::getContextStr() const {
std::ostringstream stream;
stream << "current state: [ "
<< state_ << " " << getStateLabel(state_)
<< " ] next event: [ "
<< next_event_ << " " << getEventLabel(next_event_) << " ]";
return(stream.str());
}
std::string
StateModel::getPrevContextStr() const {
std::ostringstream stream;
stream << "previous state: [ "
<< prev_state_ << " " << getStateLabel(prev_state_)
<< " ] last event: [ "
<< next_event_ << " " << getEventLabel(last_event_) << " ]";
return(stream.str());
}
const char*
StateModel::getEventLabel(const int event) const {
const char* str = "Unknown";
switch(event) {
case NOP_EVT:
str = "StateModel::NOP_EVT";
break;
case START_EVT:
str = "StateModel::START_EVT";
break;
case END_EVT:
str = "StateModel::END_EVT";
break;
case FAIL_EVT:
str = "StateModel::FAIL_EVT";
break;
default:
break;
}
return (str);
}
} // namespace isc::d2
} // namespace isc
This diff is collapsed.
......@@ -65,6 +65,7 @@ d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
d2_unittests_SOURCES += ../state_model.cc ../state_model.h
d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
d2_unittests_SOURCES += d2_process_unittests.cc
......@@ -78,6 +79,7 @@ d2_unittests_SOURCES += d2_update_mgr_unittests.cc
d2_unittests_SOURCES += d2_zone_unittests.cc
d2_unittests_SOURCES += dns_client_unittests.cc
d2_unittests_SOURCES += nc_trans_unittests.cc
d2_unittests_SOURCES += state_model_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment