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
// Copyright (C) 2023-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 TRACKING_LEASE_MGR_H
#define TRACKING_LEASE_MGR_H

#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index_container.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/composite_key.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/indexed_by.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/member.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/hashed_index.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/ordered_index.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/multi_index/sequenced_index.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/shared_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <functional><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <string><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <unordered_set><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

namespace isc {
namespace dhcp {

/// @brief Introduces callbacks into the @c LeaseMgr.
///
/// The LeaseMgr is a central point of lease management and is aware of all
/// lease changes within the server instance. Thus, it is a good opportunity
/// to allow installing callbacks in the @c LeaseMgr to track all lease
/// changes. An allocator maintaining a list of free leases (FLQ allocator)
/// can benefit from it by/ installing the callbacks that add or remove
/// free leases from this list, depending on the lease manager's activity.
/// The callbacks are invoked regardless if the lease changes result from a
/// normal lease allocation or a control command. The callbacks can also be
/// useful for maintaining a log of lease changes. Such a log could be made
/// available externally and consumed by another application (e.g., Stork).
///
/// The lease manager can track three types of calls: new lease insertion
/// (add), an existing lease update, and lease deletion. Even though the
/// lease reclamation is similar to deleting a lease because it becomes free,
/// it is a lease update from the lease manager's standpoint. Currently,
/// the lease manager cannot track the deletion of the reclaimed leases
/// (i.e., the leases in the expired-reclaimed state).
///
/// The lease backends should call the suitable tracking functions:
/// @c trackAddLease, @c trackUpdateLease, and @c trackDeleteLease.
/// However, the backends must ensure that there are no race conditions
/// between modifying the lease in the database and running the callbacks.
/// Suppose that two threads modify the same lease. Thread A inserts the
/// lease in the database, and thread B removes it. The callback for
/// thread A should be invoked before the callback for thread B. If they
/// are invoked in reverse order, it can result in an inconsistent state
/// in the free lease queue allocator because the allocator should record
/// the lease as available after the thread B callback. The reverse
/// invocation order would result in marking the lease as unavailable for
/// allocation after both callbacks.
///
/// The race condition does not occur for the Memfile backend because it
/// guards entire functions with a mutex. However, the SQL backends rely
/// on the database to guard against concurrent writes. In these cases,
/// the backend must protect against the reverse order of callbacks. They
/// should use the lease locking mechanism introduced in the
/// @c TrackingLeaseMgr.
///
/// The lease locking is modeled on an @c unordered_set container holding
/// the leases with the ongoing allocations. The leases are inserted into
/// this container by the @c tryLock function. If another thread has already
/// locked the lease, this function returns @c false to indicate an
/// unsuccessful attempt. In this case, the thread should resign from updating
/// the lease and return early. It can result in a lease allocation failure,
/// but two concurrent threads extremely rarely work on allocating a lease for
/// the same client. A passive wait could be another option here, but it is a
/// much more complicated solution for a bit of gain.
class TrackingLeaseMgr : public LeaseMgr {
public:

    /// The @c LeaseMgrFactory manages the @c LeaseMgr instances and has
    /// to be able to move installed callbacks between them. No other external
    /// class can have access to the callbacks container. Thus, we can't make
    /// the container public. The friend declaration deals with it cleanly.
    friend class LeaseMgrFactory;

    /// @brief An enumeration differentiating between lease write operations.
    typedef enum {
        TRACK_ADD_LEASE,
        TRACK_UPDATE_LEASE,
        TRACK_DELETE_LEASE
    } CallbackType;

    /// @brief Type of a callback function invoked upon a lease insertion,
    /// update or deletion.
    ///
    /// The first argument is a pointer to the lease for which the callback
    /// is invoked.
    typedef std::function<void(LeasePtr)> CallbackFn;

    /// @brief A structure representing a registered callback.
    ///
    /// It associates the callback with a type, its owner, subnet
    /// identifier, and a lease type. The owner is a string specified
    /// by the registration function caller. There must be at most one
    /// callback registered for the particular owner, subnet identifier
    /// and the lease type.
    typedef struct {
        /// @brief Callback type (i.e., lease add, update, delete).
        CallbackType type;

        /// @brief An entity owning callback registration (e.g., FLQ allocator).
        std::string owner;

        /// Subnet identifier associated with the callback.
        SubnetID subnet_id;

        /// @brief Lease types for which the callback should be invoked.
        Lease::Type lease_type;

        /// @brief Callback function.
        CallbackFn fn;
    } Callback;

protected:

    /// @brief A multi-index container holding registered callbacks.
    ///
    /// The callbacks are accessible via two indexes. The first composite index
    /// filters the callbacks by the callback type (i.e., lease add, update or delete)
    /// and the subnet id. The second index filters the callbacks by the subnet id
    /// and the lease type.
    typedef boost::multi_index_container<
        Callback,
        boost::multi_index::indexed_by<
            boost::multi_index::ordered_non_unique<
                boost::multi_index::composite_key<
                    Callback,
                    boost::multi_index::member<Callback, CallbackType, &Callback::type>,
                    boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>,
                    boost::multi_index::member<Callback, Lease::Type, &Callback::lease_type>
                >
            >,
            boost::multi_index::ordered_non_unique<
                boost::multi_index::composite_key<
                    Callback,
                    boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>,
                    boost::multi_index::member<Callback, Lease::Type, &Callback::lease_type>
                >
            >
        >
    > CallbackContainer;

    /// @brief Pointer to the callback container.
    typedef boost::shared_ptr<CallbackContainer> CallbackContainerPtr;

    /// @brief Constructor.
    TrackingLeaseMgr();

    /// @brief Attempts to lock a lease.
    ///
    /// If a lease is successfully locked, no other thread can lock it. It protects
    /// against running the callbacks out of order when two threads modify the same
    /// lease. Such a locking should only be used when the lease allocation followed by
    /// the callbacks invocation are not protected by some other synchronization
    /// mechanism. In particular, the Memfile backend uses a mutex for locking in the
    /// lease allocation functions. In this case, it is unnecessary to apply a lock at the
    /// lease level. The SQL backends rely on the database locking mechanisms to prevent
    /// the concurrent updates of the same lease. These backends must use the lease locking
    /// to ensure the correct callbacks invocation order.
    ///
    /// This function is not thread-safe and must be invoked in a thread-safe context.
    ///
    /// @param lease a lease instance for which the lock should be attempted.
    /// @return true when locking was successful, false otherwise. In the latter case,
    /// the thread should stop a lease allocation or deallocation attempt.
    bool tryLock(const LeasePtr& lease);

    /// @brief Attempts to unlock a lease.
    ///
    /// This function is not thread-safe and must be invoked in a thread-safe context.
    ///
    /// @param lease a lease instance for which unlocking should be attempted.
    void unlock(const LeasePtr& lease);

public:

    /// @brief Checks if the lease is locked.
    ///
    /// This function is useful in the unit tests.
    ///
    /// @return true if the lease is locked, false otherwise.
    bool isLocked(const LeasePtr& lease);

protected:

    /// @brief Invokes the callbacks when a new lease is added.
    ///
    /// It executes all callbacks of the @c TRACK_ADD_LEASE type for a subnet id of 0
    /// and the subnet id associated with the lease.
    ///
    /// The callbacks execution order is not guaranteed.
    ///
    /// @param lease new lease instance.
    void trackAddLease(const LeasePtr& lease);

    /// @brief Invokes the callbacks when a lease is updated.
    ///
    /// It executes all callbacks of the @c TRACK_UPDATE_LEASE type for a subnet id of 0
    /// and the subnet id associated with the lease.
    ///
    /// The callbacks execution order is not guaranteed.
    ///
    /// @param lease updated lease instance.
    void trackUpdateLease(const LeasePtr& lease);

    /// @brief Invokes the callbacks when a lease is deleted.
    ///
    /// It executes all callbacks of the @c TRACK_DELETE_LEASE type for a subnet id of 0
    /// and the subnet id associated with the lease.
    ///
    /// The callbacks execution order is not guaranteed.
    ///
    /// @param lease deleted lease instance.
    void trackDeleteLease(const LeasePtr& lease);

public:

    /// @brief Registers a callback function for a subnet.
    ///
    /// @param type callback type.
    /// @param owner callback owner identifier.
    /// @param subnet_id subnet identifier; it can be set to 0 if the callback should be
    /// called for all subnets.
    /// @param lease_type a lease type.
    /// @param callback_fn callback function instance.
    /// @throw InvalidOperation when the callback has been already registered for the given owner and
    /// the subnet identifier.
    void registerCallback(CallbackType type, std::string owner, SubnetID subnet_id,
                          Lease::Type lease_type, CallbackFn callback_fn);

    /// @brief Registers a callback function for all subnets.
    ///
    /// @param type callback type.
    /// @param owner callback owner identifier.
    /// @param lease_type a lease type.
    /// @param callback_fn callback function instance.
    /// @throw InvalidOperation when the callback has been already registered for the given owner and
    /// all subnets.
    void registerCallback(CallbackType type, std::string owner, Lease::Type lease_type,
                          CallbackFn callback_fn);

    /// @brief Unregisters all callbacks for a given subnet identifier.
    ///
    /// @param subnet_id a subnet identifier.
    /// @param lease_type a lease type.
    void unregisterCallbacks(SubnetID subnet_id, Lease::Type lease_type);

    /// @brief Unregisters all callbacks.
    void unregisterAllCallbacks();

    /// @brief Checks if any callbacks have been registered.
    ///
    /// It is a quick check to be performed by the backends whether or not
    /// the callbacks mechanism is used.
    ///
    /// @return true if any callbacks have been registered.
    bool hasCallbacks() const;

protected:

    /// @brief Converts callback type to string for logging purposes.
    ///
    /// @param type callback type.
    /// @return callback type name or the 'unknown' string.
    static std::string callbackTypeToString(CallbackType type);

    /// @brief Runs registered callbacks of the particular type.
    ///
    /// The specified lease instance contains the subnet identifier used to
    /// filter the callbacks to be invoked.
    ///
    /// @param type callback type.
    /// @param lease lease instance for which the callbacks are invoked.
    void runCallbacks(CallbackType type, const LeasePtr& lease);

    /// @brief Runs registered callbacks of the particular type for a subnet id.
    ///
    /// It is called internally by the @c runCallbacks function.
    ///
    /// @param type callback type.
    /// @param subnet_id subnet identifier for which the callbacks are invoked.
    /// @param lease lease instance for which the callbacks are invoked.
    void runCallbacksForSubnetID(CallbackType type, SubnetID subnet_id,
                                 const LeasePtr& lease);

    /// @brief The multi-index container holding registered callbacks.
    CallbackContainerPtr callbacks_;

    /// @brief A set of locked leases.
    ///
    /// It is empty if locking is not used (e.g. Memfile backend) or when there
    /// are no ongoing allocations.
    std::unordered_set<asiolink::IOAddress, asiolink::IOAddress::Hash> locked_leases_;
};

/// @brief TrackingLeaseMgr pointer
typedef std::unique_ptr<TrackingLeaseMgr> TrackingLeaseMgrPtr;

} // end of namespace isc::dhcp
} // end of namespace isc

#endif // TRACKING_LEASE_MGR_H