From: Thomas Markwalder Date: Tue, 27 Aug 2013 20:32:49 +0000 (-0400) Subject: [3086] Initial implementation of NameChangeTransaction class X-Git-Tag: bind10-1.2.0beta1-release~227^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f3c1bcd7bec13a0ff7246a66282bbd96bf032687;p=thirdparty%2Fkea.git [3086] Initial implementation of NameChangeTransaction class Interim commit for 3086 which includes the preliminary implementation of the base class, NameChangeTransaction. b10-dhcp-ddns module will use derivations of this class to carry out NameChangeRequests. --- diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am index f2898157a7..f95ae9db46 100644 --- a/src/bin/d2/Makefile.am +++ b/src/bin/d2/Makefile.am @@ -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 += nc_trans.cc nc_trans.h nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc EXTRA_DIST += d2_messages.mes diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes index c2805fa39a..917fa989bb 100644 --- a/src/bin/d2/d2_messages.mes +++ b/src/bin/d2/d2_messages.mes @@ -252,3 +252,9 @@ in event loop. % DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1 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 +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. diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc index e625c3e584..c49805029f 100644 --- a/src/bin/d2/d2_update_mgr.cc +++ b/src/bin/d2/d2_update_mgr.cc @@ -108,6 +108,12 @@ void D2UpdateMgr::pickNextJob() { if (!hasTransaction(found_ncr->getDhcid())) { queue_mgr_->dequeueAt(index); makeTransaction(found_ncr); + +#if 0 + // this will run it up to its first IO + trans->startTransaction(); +#endif + return; } } diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h index 555b7be287..9653253942 100644 --- a/src/bin/d2/d2_update_mgr.h +++ b/src/bin/d2/d2_update_mgr.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,58 +38,9 @@ public: isc::Exception(file, line, what) { }; }; -//@{ -/// @todo This is a stub implementation of NameChangeTransaction that is here -/// strictly to facilitate development of D2UpdateMgr. It will move to its own -/// source file(s) once NameChangeTransaction class development begins. - -/// @brief Defines the key for transactions. -typedef isc::dhcp_ddns::D2Dhcid TransactionKey; - -class NameChangeTransaction { -public: - NameChangeTransaction(isc::asiolink::IOService& io_service, - dhcp_ddns::NameChangeRequestPtr& ncr, - DdnsDomainPtr forward_domain, - DdnsDomainPtr reverse_domain) - : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain), - reverse_domain_(reverse_domain) { - } - - ~NameChangeTransaction(){ - } - - const dhcp_ddns::NameChangeRequestPtr& getNcr() const { - return (ncr_); - } - - const TransactionKey& getTransactionKey() const { - return (ncr_->getDhcid()); - } - - dhcp_ddns::NameChangeStatus getNcrStatus() const { - return (ncr_->getStatus()); - } - -private: - isc::asiolink::IOService& io_service_; - - dhcp_ddns::NameChangeRequestPtr ncr_; - - DdnsDomainPtr forward_domain_; - - DdnsDomainPtr reverse_domain_; -}; - -/// @brief Defines a pointer to a NameChangeTransaction. -typedef boost::shared_ptr NameChangeTransactionPtr; - -//@} - /// @brief Defines a list of transactions. typedef std::map TransactionList; - /// @brief D2UpdateMgr creates and manages update transactions. /// /// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising diff --git a/src/bin/d2/nc_trans.cc b/src/bin/d2/nc_trans.cc new file mode 100644 index 0000000000..ab128997d5 --- /dev/null +++ b/src/bin/d2/nc_trans.cc @@ -0,0 +1,244 @@ +// 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 +#include + +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::DERIVED_STATES; + +// 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; +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::CANCEL_TRANSACTION_EVT; +const int NameChangeTransaction::ALL_DONE_EVT; + +const int NameChangeTransaction::DERIVED_EVENTS; + +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), + dns_update_status_(DNSClient::OTHER), dns_update_response_(), + forward_change_completed_(false), reverse_change_completed_(false) { + if (!ncr_) { + isc_throw(NameChangeTransactionError, "NameChangeRequest cannot null"); + } + + if (ncr_->isForwardChange() && !(forward_domain_)) { + isc_throw(NameChangeTransactionError, + "Forward change must have a forward domain"); + } + + if (ncr_->isReverseChange() && !(reverse_domain_)) { + 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(){ +} + +void +NameChangeTransaction::startTransaction() { + // Initialize the state handler map first. + initStateHandlerMap(); + + // Set the current state to READY and enter the run loop. + setState(READY_ST); + runStateModel(START_TRANSACTION_EVT); +} + +void +NameChangeTransaction::cancelTransaction() { + //@todo It is up to the deriving state model to handle this event. + runStateModel(CANCEL_TRANSACTION_EVT); +} + +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. + // It won't exit until we hit the next IO wait or the state model ends. + setDnsUpdateStatus(status); + runStateModel(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; +} + +void +NameChangeTransaction::setNextEvent(unsigned int event) { + last_event_ = next_event_; + next_event_ = event; +} + +void +NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) { + dns_update_status_ = status; +} + +void +NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) { + dns_update_response_ = response; +} + +void +NameChangeTransaction::setForwardChangeCompleted(const bool value) { + forward_change_completed_ = value; +} + +void +NameChangeTransaction::setReverseChangeCompleted(const bool value) { + reverse_change_completed_ = value; +} + +const dhcp_ddns::NameChangeRequestPtr& +NameChangeTransaction::getNcr() const { + return (ncr_); +} + +const TransactionKey& +NameChangeTransaction::getTransactionKey() const { + return (ncr_->getDhcid()); +} + +dhcp_ddns::NameChangeStatus +NameChangeTransaction::getNcrStatus() const { + return (ncr_->getStatus()); +} + +void +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_); +} + +const D2UpdateMessagePtr& +NameChangeTransaction::getDnsUpdateResponse() const { + return (dns_update_response_); +} + +bool +NameChangeTransaction::getForwardChangeCompleted() const { + return (forward_change_completed_); +} + +bool +NameChangeTransaction::getReverseChangeCompleted() const { + return (reverse_change_completed_); +} + + +} // namespace isc::d2 +} // namespace isc diff --git a/src/bin/d2/nc_trans.h b/src/bin/d2/nc_trans.h new file mode 100644 index 0000000000..cec56a722a --- /dev/null +++ b/src/bin/d2/nc_trans.h @@ -0,0 +1,478 @@ +// 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 NC_TRANS_H +#define NC_TRANS_H + +/// @file nc_trans.h This file defines the class NameChangeTransaction. + +#include +#include +#include +#include +#include + +#include +#include + +namespace isc { +namespace d2 { + +/// @brief Thrown if the update manager encounters a general error. +class NameChangeTransactionError : public isc::Exception { +public: + NameChangeTransactionError(const char* file, size_t line, + const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @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 StateHandler; + +/// @brief Defines a map of states to their handler methods. +typedef std::map 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. +/// +/// Upon construction, each transaction has all of the information and +/// resources required to carry out its assigned request, including the list(s) +/// of DNS server(s) needed. It is responsible for knowing what conversations +/// it must have with which servers and in the order necessary to fulfill the +/// request. Upon fulfillment of the request, the transaction's work is complete +/// it is destroyed. +/// +/// Fulfillment of the request is carried out through the performance of the +/// transaction's state model. Using a state driven implementation accounts +/// for the conditional processing flow necessary to meet the DDNS RFCs as well +/// as the asynchronous nature of IO with DNS servers. +/// +/// Derivations of the class are responsible for defining the state model and +/// conversations necessary to carry out the specific of request. +/// +/// Conversations with DNS servers are done through the use of the DNSClient +/// class. The DNSClient provides a IOService-based means a service which +/// performs a single, packet exchange with a given DNS server. It sends a +/// single update to the server and returns the response, asynchronously, +/// through a callback. At each point in a transaction's state model, where +/// an update is to be sent, the model "suspends" until notified by the +/// DNSClient via the callback. +/// +/// The state model implementation used is a very basic approach. States +/// and events are simple integer constants. Each state must have a state +/// handler. State handlers are void methods which accept an event as their +/// only parameter. Each transaction instance contains a map of states to +/// to bound method pointers to their respective state handlers. +/// +/// When invoked, the handler determines what it should do based upon the event, +/// including what the next state and event should be. In other words the state +/// transition knowledge is distributed among the state handlers rather than +/// encapsulated in some form of state transition table. Events set from within +/// the state handlers are "internally" triggered events. Events set from +/// outside the state model, such as through the DNSClient completion callback +/// are "externally" triggered. +/// +/// Executing the model consists of iteratively invoking the state handler +/// indicated by the current state and passing it the current event. As the +/// handlers update the state and event, the machine is traversed. The loop +/// "stops" whenever the model cannot continue without an externally triggered +/// event or when it has reached its final state. In the case of the former, +/// the loop is re-entered upon arrival of the external event. +/// +/// This loop is implemented in the runStateModel method. This method accepts +/// an event as argument. This event is treated as the "next event" to process +/// and is fed to the current state's handler. The runStateModel does not exit +/// until a handler sets the next event to a special value, NOP_EVT, +/// indicating that either it is now waiting for IO to complete of the state +/// model has reached its conclusion. +/// +/// Re-entering the "loop" when a DNS update completes is done by a call to +/// runStateModel() from within the DNSClient callback, with an event value +/// of IO_COMPLETED_EVT. As above, runStateModel() will loop until either the +/// next IO is issued or the state model has reached its conclusion. +/// +/// This class defines a set of events and states that are a common to all +/// 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 { +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; + /// @brief State in which forward DNS server selection is done. + static const int SELECTING_FWD_SERVER_ST = 2; + /// @brief State in which reverse DNS server selection is done. + static const int SELECTING_REV_SERVER_ST = 3; + /// @brief Final state, all work has been performed. + static const int DONE_ST = 4; + + /// @define Value at which custom states in a derived class should begin. + static const int DERIVED_STATES = 100; + //@} + + + //@{ Events common to all transactions. + /// @brief Signifies that no event has occurred. + /// This is event used to interrupt the event loop to allow waiting for + /// an IO event or when there is no more work to be done. + static const int NOP_EVT = 0; + /// @brief Event used to start the transaction. + static const int START_TRANSACTION_EVT = 1; + /// @brief Issued when a server needs to be selected. + static const int SELECT_SERVER_EVT = 2; + /// @brief Issued when a server has been selected. + static const int SERVER_SELECTED_EVT = 3; + /// @brief Issued when an update fails due to an IO error. + static const int SERVER_IO_ERROR_EVT = 4; + /// @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; + /// @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; + /// @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; + /// @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 transaction should be cancelled. + /// @todo - still on the fence about this one. + static const int CANCEL_TRANSACTION_EVT = 9; + /// @brief Issued when the state model has no more work left to do. + static const int ALL_DONE_EVT = 10; + + /// @define Value at which custom events in a derived class should begin. + static const int DERIVED_EVENTS = 100; + //@} + + /// @brief Constructor + /// + /// Instantiates a transaction that is ready to be started. + /// + /// @param io_service IO service to be used for IO processing + /// @param ncr is the NameChangeRequest to fulfill + /// @param forward_domain is the domain to use for forward DNS updates + /// @param reverse_domain is the domain to use for reverse DNS updates + /// + /// @throw NameChangeTransaction error if given an null request, + /// if forward change is enabled but forward domain is null, if + /// reverse change is enabled but reverse domain is null. + NameChangeTransaction(isc::asiolink::IOService& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr forward_domain, + DdnsDomainPtr reverse_domain); + + virtual ~NameChangeTransaction(); + + /// @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. + void startTransaction(); + + /// @todo - Not sure about this yet. + void cancelTransaction(); + + /// @brief Serves as the DNSClient IO completion event handler. + /// + /// This is the implementation of the method inherited by our derivation + /// from DNSClient::Callback. When the DNSClient completes an update it + /// invokes this method as the completion handler. This method stores + /// the given status and invokes runStateModel() with an event value of + /// IO_COMPLETED_EVT. + /// + /// @param status is the outcome of the DNS update packet exchange. + /// This method is exception safe. + virtual void operator()(DNSClient::Status status); + +protected: + /// @brief Populates the map of state handlers. + /// + /// This method is used by derivations to construct a map of states to + /// their appropriate state handlers (bound method pointers). It is + /// invoked at the beginning of startTransaction(). + /// + /// 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 an + /// temporary empty, implementation. If we make it pure virtual now + /// D2UpdateManager will not compile. + virtual void initStateHandlerMap() {}; + + /// @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() { + /// } + /// + /// void initStateHandlerMap() { + /// addToMap(READY_ST, + /// boost::bind(&ExampleTrans::readyHandler, this)); + /// : + /// + /// @endcode + /// + /// @param state the value of the state to which to map + /// @param handler the bound method pointer to the handler for the state + /// + /// @throw NameChangeTransactionError if the map already contains an entry + /// for the given state. + void addToMap(unsigned int idx, StateHandler handler); + + /// @brief Processes events through the state model + /// + /// This method implements the state model "execution loop". It uses + /// the given event as the next event to process and begins looping by + /// passing it the state handler for the current state. As described + /// above, the invoked state handler determines the current state and the + /// next event required to implement the business logic. The method + /// continues to loop until next event is set to NOP_EVT, at which point + /// the method exits. + /// + /// 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. + /// + /// @param event is the next event to process + /// + /// This is guaranteed not to throw. + void runStateModel(unsigned int event); + + /// @brief Return the state handler for a given state. + /// + /// This method looks up the state handler for the given state from within + /// the state handler map. + /// + /// @param state is the state constant of the desired handler. + /// + /// @return A StateHandler (bound method pointer) for the method that + /// handles the given state for this transaction. + /// + /// @throw NameChangeTransactionError + StateHandler getStateHandler(unsigned int state); + + /// @brief Sets the current state to the given state value. + /// + /// This updates the transaction's notion of the current state and is the + /// state whose handler will be executed on the next iteration of the run + /// loop. + /// + /// @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); + + /// @brief Sets the update status to the given status value. + /// + /// @param status is the new value for the update status. + void setDnsUpdateStatus(const DNSClient::Status& status); + + /// @brief Sets the update response packet to the given packet. + /// + /// @param response is the new response packet to assign. + void setDnsUpdateResponse(D2UpdateMessagePtr& response); + + /// @brief Sets the forward change completion flag to the given value. + /// + /// @param value is the new value to assign to the flag. + void setForwardChangeCompleted(const bool value); + + /// @brief Sets the reverse change completion flag to the given value. + /// + /// @param value is the new value to assign to the flag. + void setReverseChangeCompleted(const bool value); + +public: + /// @brief Fetches the NameChangeRequest for this transaction. + /// + /// @return A const pointer reference to the NameChangeRequest. + const dhcp_ddns::NameChangeRequestPtr& getNcr() const; + + /// @brief Fetches the unique key that identifies this transaction. + /// + /// Transactions are uniquely identified by a TransactionKey. Currently + /// this is wrapper around a D2Dhcid. + /// + /// @return A const reference to the TransactionKey. + const TransactionKey& getTransactionKey() const; + + /// @brief Fetches the NameChangeRequest status of the transaction. + /// + /// This is the current status of the NameChangeRequest, not to + /// be confused with the state of the transaction. Once the transaction + /// is reached it's conclusion, the request will end up with a final + /// status. + /// + /// @return A dhcp_ddns::NameChangeStatus representing the current + /// status of the transaction. + dhcp_ddns::NameChangeStatus getNcrStatus() const; + + /// @brief Sets the status of the transaction's NameChangeRequest + /// + /// @param status is the new value to assign to the NCR status. + void setNcrStatus(const dhcp_ddns::NameChangeStatus& status); + + /// @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 + /// DNS update to complete. + DNSClient::Status getDnsUpdateStatus() const; + + /// @brief Fetches the most recent DNS update response packet. + /// + /// @return A const pointer reference to the D2UpdateMessage most recently + /// received. + const D2UpdateMessagePtr& getDnsUpdateResponse() const; + + /// @brief Returns whether the forward change has completed or not. + /// + /// The value returned is only meaningful if the NameChangeRequest calls + /// for a forward change to be done. The value returned indicates if + /// forward change has been completed successfully. + /// + /// @return True if the forward change has been completed, false otherwise. + bool getForwardChangeCompleted() const; + + /// @brief Returns whether the reverse change has completed or not. + /// + /// The value returned is only meaningful if the NameChangeRequest calls + /// for a reverse change to be done. The value returned indicates if + /// reverse change has been completed successfully. + /// + /// @return True if the reverse change has been completed, false otherwise. + bool getReverseChangeCompleted() const; + +private: + /// @brief Contains a map of states to their state handlers. + StateHandlerMap state_handlers_; + + /// @brief The IOService which should be used to for IO processing. + isc::asiolink::IOService& io_service_; + + /// @brief The NameChangeRequest that the transaction is to fulfill. + dhcp_ddns::NameChangeRequestPtr ncr_; + + /// @brief The forward domain that matches the request. + /// + /// The forward "domain" is DdnsDomain which contains all of the information + /// necessary, including the list of DNS servers to be used for a forward + /// change. + DdnsDomainPtr forward_domain_; + + /// @brief The reverse domain that matches the request. + /// + /// The reverse "domain" is DdnsDomain which contains all of the information + /// necessary, including the list of DNS servers to be used for a reverse + /// change. + DdnsDomainPtr reverse_domain_; + + /// @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_; + + /// @brief The DNS update response packet most recently received. + D2UpdateMessagePtr dns_update_response_; + + /// @brief Indicator for whether or not the forward change completed ok. + bool forward_change_completed_; + + /// @brief Indicator for whether or not the reverse change completed ok. + bool reverse_change_completed_; +}; + +/// @brief Defines a pointer to a NameChangeTransaction. +typedef boost::shared_ptr NameChangeTransactionPtr; + +} // namespace isc::d2 +} // namespace isc +#endif diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am index 6d0b89414f..872de27da6 100644 --- a/src/bin/d2/tests/Makefile.am +++ b/src/bin/d2/tests/Makefile.am @@ -64,6 +64,7 @@ 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 += ../nc_trans.cc ../nc_trans.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 @@ -76,6 +77,7 @@ 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 += nc_trans_unittests.cc nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) diff --git a/src/bin/d2/tests/nc_trans_unittests.cc b/src/bin/d2/tests/nc_trans_unittests.cc new file mode 100644 index 0000000000..1248a07297 --- /dev/null +++ b/src/bin/d2/tests/nc_trans_unittests.cc @@ -0,0 +1,336 @@ +// 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 + +#include +#include +#include + +using namespace std; +using namespace isc; +using namespace isc::d2; + +namespace { + +/// @brief Test derivation of NameChangeTransaction for exercising state +/// model mechanics. +/// +/// This class faciliates 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 NameChangeStub : public NameChangeTransaction { +public: + + // NameChangeStub states + static const int DO_WORK_ST = DERIVED_STATES + 1; + + // NameChangeStub events + static const int START_WORK_EVT = DERIVED_EVENTS + 1; + + /// @brief Constructor + /// + /// Parameters match those needed by NameChangeTransaction. + NameChangeStub(isc::asiolink::IOService& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr forward_domain, + DdnsDomainPtr reverse_domain) + : NameChangeTransaction(io_service, ncr, forward_domain, + reverse_domain) { + } + + /// @brief Destructor + virtual ~NameChangeStub() { + } + + /// @brief State handler for the READY_ST. + /// + /// Serves as the starting state handler, it consumes the + /// START_TRANSACTION_EVT "transitioing" to the state, DO_WORK_ST and + /// sets the next event to START_WORK_EVT. + void readyHandler() { + switch(getNextEvent()) { + case START_TRANSACTION_EVT: + setState(DO_WORK_ST); + setNextEvent(START_WORK_EVT); + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, "invalid event: " + << getNextEvent() << " for state: " << getState()); + } + } + + /// @brief State handler for the DO_WORK_ST. + /// + /// Simulates a state that starts some form of asynchronous work. + /// When next event is START_WROK_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() { + switch(getNextEvent()) { + case START_WORK_EVT: + setNcrStatus(dhcp_ddns::ST_PENDING); + setNextEvent(NOP_EVT); + break; + //case WORK_DONE_EVT: + case IO_COMPLETED_EVT: + setState(DONE_ST); + setNextEvent(ALL_DONE_EVT); + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, "invalid event: " + << getNextEvent() << " for state: " << getState()); + } + } + + /// @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 doneHandler() { + switch(getNextEvent()) { + case ALL_DONE_EVT: + setNcrStatus(dhcp_ddns::ST_COMPLETED); + setNextEvent(NOP_EVT); + break; + default: + // its bogus + isc_throw(NameChangeTransactionError, "invalid event: " + << getNextEvent() << " for state: " << getState()); + } + } + + /// @brief Initializes the state handler map. + void initStateHandlerMap() { + addToMap(READY_ST, + boost::bind(&NameChangeStub::readyHandler, this)); + + addToMap(DO_WORK_ST, + boost::bind(&NameChangeStub::doWorkHandler, this)); + + addToMap(DONE_ST, + boost::bind(&NameChangeStub::doneHandler, this)); + } + + // 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; +}; + +const int NameChangeStub::DO_WORK_ST; +const int NameChangeStub::START_WORK_EVT; + +/// @brief Defines a pointer to a D2UpdateMgr instance. +typedef boost::shared_ptr NameChangeStubPtr; + +/// @brief Test fixture for testing NameChangeTransaction +/// +/// Note this class uses NameChangeStub class to exercise non-public +/// aspects of NameChangeTransaction. +class NameChangeTransactionTest : public ::testing::Test { +public: + isc::asiolink::IOService io_service_; + + virtual ~NameChangeTransactionTest() { + } + + /// @brief Instantiates a NameChangeStub built around a canned + /// NameChangeRequest. + NameChangeStubPtr makeCannedTransaction() { + const char* msg_str = + "{" + " \"change_type\" : 0 , " + " \"forward_change\" : true , " + " \"reverse_change\" : true , " + " \"fqdn\" : \"walah.walah.org.\" , " + " \"ip_address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"0102030405060708\" , " + " \"lease_expires_on\" : \"20130121132405\" , " + " \"lease_length\" : 1300 " + "}"; + + dhcp_ddns::NameChangeRequestPtr ncr; + DnsServerInfoStoragePtr servers; + DdnsDomainPtr forward_domain; + DdnsDomainPtr reverse_domain; + + ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str); + forward_domain.reset(new DdnsDomain("*", "", servers)); + reverse_domain.reset(new DdnsDomain("*", "", servers)); + return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr, + forward_domain, reverse_domain))); + + } + +}; + +/// @brief Tests NameChangeTransaction construction. +/// This test verifies that: +/// 1. Construction with null NameChangeRequest +/// 2. Construction with null forward domain is not allowed when the request +/// requires forward change. +/// 3. Construction with null reverse domain is not allowed when the request +/// requires reverse change. +/// 4. Valid construction functions properly +TEST(NameChangeTransaction, construction) { + isc::asiolink::IOService io_service; + + const char* msg_str = + "{" + " \"change_type\" : 0 , " + " \"forward_change\" : true , " + " \"reverse_change\" : true , " + " \"fqdn\" : \"walah.walah.org.\" , " + " \"ip_address\" : \"192.168.2.1\" , " + " \"dhcid\" : \"0102030405060708\" , " + " \"lease_expires_on\" : \"20130121132405\" , " + " \"lease_length\" : 1300 " + "}"; + + dhcp_ddns::NameChangeRequestPtr ncr; + + dhcp_ddns::NameChangeRequestPtr empty_ncr; + DnsServerInfoStoragePtr servers; + DdnsDomainPtr forward_domain; + DdnsDomainPtr reverse_domain; + DdnsDomainPtr empty_domain; + + ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str)); + ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", "", servers))); + ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", "", servers))); + + // Verify that construction with an empty NameChangeRequest throws. + EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr, + forward_domain, reverse_domain), + NameChangeTransactionError); + + // Verify that construction with an empty forward domain when the + // NameChangeRequest calls for a forward change throws. + EXPECT_THROW(NameChangeTransaction(io_service, ncr, + empty_domain, reverse_domain), + NameChangeTransactionError); + + // Verify that construction with an empty reverse domain when the + // NameChangeRequest calls for a reverse change throws. + EXPECT_THROW(NameChangeTransaction(io_service, ncr, + forward_domain, empty_domain), + NameChangeTransactionError); + + // Verify that a valid construction attempt works. + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + forward_domain, reverse_domain)); + + // Verify that an empty forward domain is allowed when the requests does + // include a forward change. + ncr->setForwardChange(false); + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + empty_domain, reverse_domain)); + + // Verify that an empty reverse domain is allowed when the requests does + // include a reverse change. + ncr->setReverseChange(false); + EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr, + empty_domain, empty_domain)); +} + +/// @brief Test the basic mechanics of state model execution. +/// It first verifies basic state handle map fucntionality, and then +/// runs the NameChangeStub state model through from start to finish. +TEST_F(NameChangeTransactionTest, stateModelTest) { + NameChangeStubPtr name_change; + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Verify that getStateHandler will throw when, handler map is empty. + 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::readyHandler, + name_change.get()))); + + // Verify that we can find the handler by its state. + EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction:: + READY_ST)); + + // 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)); + + + // Get a fresh transaction. + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Manually call checkHandlerMap to ensure our test map populates. + // This is the method startTranscation invokes. + ASSERT_NO_THROW(name_change->initStateHandlerMap()); + + // Verify that we can find all the handlers by their state. + EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction:: + READY_ST)); + EXPECT_NO_THROW(name_change->getStateHandler(NameChangeStub::DO_WORK_ST)); + EXPECT_NO_THROW(name_change->getStateHandler(NameChangeTransaction:: + DONE_ST)); + + + // Default value for state is NEW_ST. Attempting to run the model + // with an invalid state will result in status of ST_FAILED. + ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState()); + EXPECT_NO_THROW(name_change->runStateModel(NameChangeTransaction:: + START_TRANSACTION_EVT)); + + EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus()); + + // Get a fresh transaction. + ASSERT_NO_THROW(name_change = makeCannedTransaction()); + + // Launch the transaction properly by calling startTranscation. + // Verify that this transitions through to state of DO_WORK_ST, + // last event is START_WORK_EVT, next event is NOP_EVT, and + // NCR status is ST_PENDING. + ASSERT_NO_THROW(name_change->startTransaction()); + + 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. + 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()); +} + +}