// Copyright (C) 2013-2015,2017,2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef D2_QUEUE_MGR_H #define D2_QUEUE_MGR_H /// @file d2_queue_mgr.h This file defines the class D2QueueMgr. #include #include #include #include #include #include namespace isc { namespace d2 { /// @brief Defines a queue of requests. /// @todo This may be replaced with an actual class in the future. typedef std::deque RequestQueue; /// @brief Thrown if the queue manager encounters a general error. class D2QueueMgrError : public isc::Exception { public: D2QueueMgrError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Thrown if the queue manager's receive handler is passed /// a failure result. /// @todo use or remove it. class D2QueueMgrReceiveError : public isc::Exception { public: D2QueueMgrReceiveError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Thrown if the request queue is full when an enqueue is attempted. /// @todo use or remove it. class D2QueueMgrQueueFull : public isc::Exception { public: D2QueueMgrQueueFull(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Thrown if the request queue empty and a read is attempted. class D2QueueMgrQueueEmpty : public isc::Exception { public: D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Thrown if a queue index is beyond the end of the queue class D2QueueMgrInvalidIndex : public isc::Exception { public: D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief D2QueueMgr creates and manages a queue of DNS update requests. /// /// D2QueueMgr is a class specifically designed as an integral part of DHCP-DDNS. /// Its primary responsibility is to listen for NameChangeRequests from /// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In /// addition it may provide a number of services to locate entries in the queue /// such as by FQDN or DHCID. These services may eventually be used /// for processing optimization. The initial implementation will support /// simple FIFO access. /// /// D2QueueMgr uses a NameChangeListener to asynchronously receive requests. /// It derives from NameChangeListener::RequestReceiveHandler and supplies an /// implementation of the operator()(Result, NameChangeRequestPtr). It is /// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr /// will add each newly received request onto the back of the request queue /// /// D2QueueMgr defines a simple state model constructed around the status of /// its NameChangeListener, consisting of the following states: /// /// * NOT_INITTED - D2QueueMgr has been constructed, but its listener has /// not been initialized. /// /// * INITTED - The listener has been initialized, but it is not open for /// listening. To move from NOT_INITTED to INITTED, one of the D2QueueMgr /// listener initialization methods must be invoked. Currently there is /// only one type of listener, NameChangeUDPListener, hence there is only /// one listener initialization method, initUDPListener. As more listener /// types are created, listener initialization methods will need to be /// added. /// /// * RUNNING - The listener is open and listening for requests. /// Once initialized, in order to begin listening for requests, the /// startListener() method must be invoked. Upon successful completion of /// of this call, D2QueueMgr will begin receiving requests as they arrive /// without any further steps. This method may be called from the INITTED /// or one of the STOPPED states. /// /// * STOPPING - The listener is in the process of stopping active /// listening. This is transitory state between RUNNING and STOPPED, which /// is completed by IO cancellation event. /// /// * STOPPED - The listener has been listening but has been stopped /// without error. To return to listening, startListener() must be invoked. /// /// * STOPPED_QUEUE_FULL - Request queue is full, the listener has been /// stopped. D2QueueMgr will enter this state when the request queue /// reaches the maximum queue size. Once this limit is reached, the /// listener will be closed and no further requests will be received. /// To return to listening, startListener() must be invoked. Note that so /// long as the queue is full, any attempt to queue a request will fail. /// /// * STOPPED_RECV_ERROR - The listener has experienced a receive error /// and has been stopped. D2QueueMgr will enter this state when it is /// passed a failed status into the request completion handler. To return /// to listening, startListener() must be invoked. /// /// D2QueueMgr does not attempt to recover from stopped conditions, this is left /// to upper layers. /// /// It is important to note that the queue contents are preserved between /// state transitions. In other words entries in the queue remain there /// until they are removed explicitly via the deque() or implicitly by /// via the clearQueue() method. /// class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler, boost::noncopyable { public: /// @brief Maximum number of entries allowed in the request queue. /// NOTE that 1024 is an arbitrary choice picked for the initial /// implementation. static const size_t MAX_QUEUE_DEFAULT = 1024; /// @brief Defines the list of possible states for D2QueueMgr. enum State { NOT_INITTED, INITTED, RUNNING, STOPPING, STOPPED_QUEUE_FULL, STOPPED_RECV_ERROR, STOPPED, }; /// @brief Constructor /// /// Creates a D2QueueMgr instance. Note that the listener is not created /// in the constructor. The initial state will be NOT_INITTED. /// /// @param io_service IOService instance to be passed into the listener for /// IO management. /// @param max_queue_size the maximum number of entries allowed in the /// queue. /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT. /// /// @throw D2QueueMgrError if max_queue_size is zero. D2QueueMgr(asiolink::IOServicePtr& io_service, const size_t max_queue_size = MAX_QUEUE_DEFAULT); /// @brief Destructor virtual ~D2QueueMgr(); /// @brief Initializes the listener as a UDP listener. /// /// Instantiates the listener_ member as NameChangeUDPListener passing /// the given parameters. Upon successful completion, the D2QueueMgr state /// will be INITTED. /// /// @param ip_address is the network address on which to listen /// @param port is the IP port on which to listen /// @param format is the wire format of the inbound requests. /// @param reuse_address enables IP address sharing when true /// It defaults to false. void initUDPListener(const isc::asiolink::IOAddress& ip_address, const uint32_t port, const dhcp_ddns::NameChangeFormat format, const bool reuse_address = false); /// @brief Starts actively listening for requests. /// /// Invokes the listener's startListening method passing in our /// IOService instance. /// /// @throw D2QueueMgrError if the listener has not been initialized, /// state is already RUNNING, or the listener fails to actually start. void startListening(); /// @brief Function operator implementing the NCR receive callback. /// /// This method is invoked by the listener as part of its receive /// completion callback and is how the inbound NameChangeRequests are /// passed up to the D2QueueMgr for queuing. /// If the given result indicates a successful receive completion and /// there is room left in the queue, the given request is queued. /// /// If the queue is at maximum capacity, stopListening() is invoked and /// the state is set to STOPPED_QUEUE_FULL. /// /// If the result indicates IO stopped, then the state is set to STOPPED. /// Note this is not an error, it results from a deliberate cancellation /// of listener IO as part of a normal stopListener call. /// /// If the result indicates a failed receive, stopListening() is invoked /// and the state is set to STOPPED_RECV_ERROR. /// /// This method specifically avoids throwing on an error as any such throw /// would surface at the io_service::run (or run variant) method invocation /// site. The upper layers are expected to monitor D2QueueMgr's state and /// act accordingly. /// /// @param result contains that receive outcome status. /// @param ncr is a pointer to the newly received NameChangeRequest if /// result is NameChangeListener::SUCCESS. It is indeterminate other /// wise. virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result, dhcp_ddns::NameChangeRequestPtr& ncr); /// @brief Stops listening for requests. /// /// Invokes the listener's stopListening method which will cause it to /// cancel any pending IO and close its IO source. It the sets target /// stop state to the given value. /// /// If there is no IO pending, the manager state is immediately set to the /// target stop state, otherwise the manager state is set to STOPPING. /// /// @param target_stop_state is one of the three stopped state values. /// /// @throw D2QueueMgrError if stop_state is a valid stop state. void stopListening(const State target_stop_state = STOPPED); /// @brief Deletes the current listener /// /// This method will delete the current listener and returns the manager /// to the NOT_INITTED state. This is provided to support reconfiguring /// a new listener without losing queued requests. /// /// @throw D2QueueMgrError if called when the manager state is RUNNING. void removeListener(); /// @brief Returns the number of entries in the queue. size_t getQueueSize() const { return (ncr_queue_.size()); }; /// @brief Returns the maximum number of entries allowed in the queue. size_t getMaxQueueSize() const { return (max_queue_size_); } /// @brief Sets the maximum number of entries allowed in the queue. /// /// @param max_queue_size is the new maximum size of the queue. /// /// @throw D2QueueMgrError if the new value is less than one or if /// the new value is less than the number of entries currently in the /// queue. void setMaxQueueSize(const size_t max_queue_size); /// @brief Returns the current state. State getMgrState() const { return (mgr_state_); } /// @brief Returns the entry at the front of the queue. /// /// The entry returned is next in line to be processed, assuming a FIFO /// approach to task selection. Note, the entry is not removed from the /// queue. /// /// @return Pointer reference to the queue entry. /// /// @throw D2QueueMgrQueueEmpty if there are no entries in the queue. const dhcp_ddns::NameChangeRequestPtr& peek() const; /// @brief Returns the entry at a given position in the queue. /// /// Note that the entry is not removed from the queue. /// @param index the index of the entry in the queue to fetch. /// Valid values are 0 (front of the queue) to (queue size - 1). /// /// @return Pointer reference to the queue entry. /// /// @throw D2QueueMgrInvalidIndex if the given index is beyond the /// end of the queue. const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const; /// @brief Removes the entry at a given position in the queue. /// /// @param index the index of the entry in the queue to remove. /// Valid values are 0 (front of the queue) to (queue size - 1). /// /// @throw D2QueueMgrInvalidIndex if the given index is beyond the /// end of the queue. void dequeueAt(const size_t index); /// @brief Removes the entry at the front of the queue. /// /// @throw D2QueueMgrQueueEmpty if there are no entries in the queue. void dequeue(); /// @brief Adds a request to the end of the queue. /// /// @param ncr pointer to the NameChangeRequest to add to the queue. void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr); /// @brief Removes all entries from the queue. void clearQueue(); private: /// @brief Sets the manager state to the target stop state. /// /// Convenience method which sets the manager state to the target stop /// state and logs that the manager is stopped. void updateStopState(); /// @brief IOService that our listener should use for IO management. asiolink::IOServicePtr io_service_; /// @brief Dictates the maximum number of entries allowed in the queue. size_t max_queue_size_; /// @brief Queue of received NameChangeRequests. RequestQueue ncr_queue_; /// @brief Listener instance from which requests are received. boost::shared_ptr listener_; /// @brief Current state of the manager. State mgr_state_; /// @brief Tracks the state the manager should be in once stopped. State target_stop_state_; }; /// @brief Defines a pointer for manager instances. typedef boost::shared_ptr D2QueueMgrPtr; } // namespace isc::d2 } // namespace isc #endif