1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// Copyright (C) 2013-2024 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 <asiolink/io_service.h>
#include <exceptions/exceptions.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp_ddns/ncr_io.h>

#include <boost/noncopyable.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <deque><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

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<dhcp_ddns::NameChangeRequestPtr> 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<dhcp_ddns::NameChangeListener> 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<D2QueueMgr> D2QueueMgrPtr;

} // namespace isc::d2
} // namespace isc

#endif