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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
// Copyright (C) 2015-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/.

#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/interval_timer.h>
#include <dhcpsrv/cfg_expiration.h>
#include <dhcpsrv/timer_mgr.h>
#include <exceptions/exceptions.h>
#include <testutils/test_to_element.h>
#include <boost/shared_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <gtest/gtest.h><--- 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 <stdint.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;

namespace {

/// @brief Type definition of the @c CfgExpiration modified function.
typedef std::function<void(CfgExpiration*, const int64_t)> ModifierFun;
/// @brief Type definition of the @c CfgExpiration accessor function
/// returning uint16_t value.
typedef std::function<uint16_t(CfgExpiration*)> AccessorFunUint16;
/// @brief Type definition of the @c CfgExpiration accessor function
/// returning uint32_t value.
typedef std::function<uint32_t(CfgExpiration*)> AccessorFunUint32;

/// @brief Tests the accessor and modifier function for a particular
/// configuration parameter held in @c CfgExpiration.
///
/// This is a simple test which tries to set the given parameter to
/// different values:
/// - value greater than maximum allowed for this parameter - expects
///   the exception to be thrown,
/// - value lower than 0 - expects the exception to be thrown,
/// - value equal to the maximum allowed,
/// - value equal to maximum allowed minus 1.
///
/// @param limit Maximum allowed value for the parameter.
/// @param modifier Pointer to the modifier function to be tested.
/// @param accessor Pointer to the accessor function to be tested.
/// @tparam ReturnType Type of the value returned by the accessor,
/// i.e. uint16_t or uint32_t.
template<typename ReturnType>
void
testAccessModify(const int64_t limit, const ModifierFun& modifier,
                 const std::function<ReturnType(CfgExpiration*)>& accessor) {
    CfgExpiration cfg;

    // Setting the value to maximum allowed + 1 should result in
    // an exception.
    ASSERT_THROW(modifier(&cfg, limit + 1), OutOfRange);

    // Setting to the negative value should result in an exception.
    ASSERT_THROW(modifier(&cfg, -1), OutOfRange);

    // Setting the value to the maximum allowed should pass.
    ASSERT_NO_THROW(modifier(&cfg, limit));
    EXPECT_EQ(limit, accessor(&cfg));

    // Setting the value to the maximum allowed - 1 should pass.
    ASSERT_NO_THROW(modifier(&cfg, limit - 1));
    EXPECT_EQ(limit - 1, accessor(&cfg));

    // Setting the value to 0 should pass.
    ASSERT_NO_THROW(modifier(&cfg, 0));
    EXPECT_EQ(0, accessor(&cfg));
}

/// @brief Tests that modifier and the accessor returning uint16_t value
/// work as expected.
///
/// @param limit Maximum allowed value for the parameter.
/// @param modifier Pointer to the modifier function to be tested.
/// @param accessor Pointer to the accessor function to be tested.
void
testAccessModifyUint16(const int64_t limit, const ModifierFun& modifier,
                       const AccessorFunUint16& accessor) {
    testAccessModify<uint16_t>(limit, modifier, accessor);
}

/// @brief Tests that modifier and the accessor returning uint32_t value
/// work as expected.
///
/// @param limit Maximum allowed value for the parameter.
/// @param modifier Pointer to the modifier function to be tested.
/// @param accessor Pointer to the accessor function to be tested.
void
testAccessModifyUint32(const int64_t limit, const ModifierFun& modifier,
                       const AccessorFunUint32& accessor) {
    testAccessModify<uint32_t>(limit, modifier, accessor);
}

/// Test the default values of CfgExpiration object.
TEST(CfgExpirationTest, defaults) {<--- syntax error
    CfgExpiration cfg;
    EXPECT_EQ(CfgExpiration::DEFAULT_RECLAIM_TIMER_WAIT_TIME,
              cfg.getReclaimTimerWaitTime());
    EXPECT_EQ(CfgExpiration::DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
              cfg.getFlushReclaimedTimerWaitTime());
    EXPECT_EQ(CfgExpiration::DEFAULT_HOLD_RECLAIMED_TIME,
              cfg.getHoldReclaimedTime());
    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
              cfg.getMaxReclaimLeases());
    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
              cfg.getMaxReclaimTime());
    EXPECT_EQ(CfgExpiration::DEFAULT_UNWARNED_RECLAIM_CYCLES,
              cfg.getUnwarnedReclaimCycles());
}

/// @brief Tests that unparse returns an expected value
TEST(CfgExpirationTest, unparse) {
    CfgExpiration cfg;
    std::string defaults = "{\n"
        "\"reclaim-timer-wait-time\": 10,\n"
        "\"flush-reclaimed-timer-wait-time\": 25,\n"
        "\"hold-reclaimed-time\": 3600,\n"
        "\"max-reclaim-leases\": 100,\n"
        "\"max-reclaim-time\": 250,\n"
        "\"unwarned-reclaim-cycles\": 5 }";
    isc::test::runToElementTest<CfgExpiration>(defaults, cfg);
}

// Test the {get,set}ReclaimTimerWaitTime.
TEST(CfgExpirationTest, getReclaimTimerWaitTime) {
    testAccessModify<uint16_t>(CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME,
                               &CfgExpiration::setReclaimTimerWaitTime,
                               &CfgExpiration::getReclaimTimerWaitTime);
}

// Test the {get,set}FlushReclaimedTimerWaitTime.
TEST(CfgExpirationTest, getFlushReclaimedTimerWaitTime) {
    testAccessModifyUint16(CfgExpiration::LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
                           &CfgExpiration::setFlushReclaimedTimerWaitTime,
                           &CfgExpiration::getFlushReclaimedTimerWaitTime);
}

// Test the {get,set}HoldReclaimedTime.
TEST(CfgExpirationTest, getHoldReclaimedTime) {
    testAccessModifyUint32(CfgExpiration::LIMIT_HOLD_RECLAIMED_TIME,
                           &CfgExpiration::setHoldReclaimedTime,
                           &CfgExpiration::getHoldReclaimedTime);
}

// Test the {get,set}MaxReclaimLeases.
TEST(CfgExpirationTest, getMaxReclaimLeases) {
    testAccessModifyUint32(CfgExpiration::LIMIT_MAX_RECLAIM_LEASES,
                           &CfgExpiration::setMaxReclaimLeases,
                           &CfgExpiration::getMaxReclaimLeases);
}

// Test the {get,set}MaxReclaimTime.
TEST(CfgExpirationTest, getMaxReclaimTime) {
    testAccessModifyUint16(CfgExpiration::LIMIT_MAX_RECLAIM_TIME,
                           &CfgExpiration::setMaxReclaimTime,
                           &CfgExpiration::getMaxReclaimTime);
}

// Test the {get,set}UnwarnedReclaimCycles.
TEST(CfgExpirationTest, getUnwarnedReclaimCycles) {
    testAccessModifyUint16(CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES,
                           &CfgExpiration::setUnwarnedReclaimCycles,
                           &CfgExpiration::getUnwarnedReclaimCycles);
}

/// @brief Implements test routines for leases reclamation.
///
/// This class implements two routines called by the @c CfgExpiration object
/// instead of the typical routines for leases' reclamation in the
/// @c AllocEngine. These methods do not perform the actual reclamation,
/// but instead they record the number of calls to them and the parameters
/// with which they were executed. This allows for checking if the
/// @c CfgExpiration object calls the leases reclamation routine with the
/// appropriate parameters.
class LeaseReclamationStub {
public:

    /// @brief Collection of parameters with which the @c reclaimExpiredLeases
    /// method is called.
    ///
    /// Examination of these values allows for assessment if the @c CfgExpiration
    /// calls the routine with the appropriate values.
    struct RecordedParams {
        /// @brief Maximum number of leases to be processed.
        size_t max_leases;

        /// @brief Timeout for processing leases in milliseconds.
        uint16_t timeout;

        /// @brief Boolean flag which indicates if the leases should be removed
        /// when reclaimed.
        bool remove_lease;

        /// @brief Maximum number of reclamation attempts after which all leases
        /// should be reclaimed.
        uint16_t max_unwarned_cycles;

        /// @brief Constructor
        ///
        /// Sets all numeric values to 0xFFFF and the boolean values to false.
        RecordedParams()
            : max_leases(0xFFFF), timeout(0xFFFF), remove_lease(false),
              max_unwarned_cycles(0xFFFF) {
        }
    };

    /// @brief Constructor.
    ///
    /// Resets recorded parameters and obtains the instance of the @c TimerMgr.
    LeaseReclamationStub()
        : reclaim_calls_count_(0), delete_calls_count_(0), reclaim_params_(),
          secs_param_(0), timer_mgr_(TimerMgr::instance()) {
    }

    /// @brief Stub implementation of the leases' reclamation routine.
    ///
    /// @param max_leases Maximum number of leases to be processed.
    /// @param timeout Timeout for processing leases in milliseconds.
    /// @remove_lease Boolean flag which indicates if the leases should be
    /// removed when it is reclaimed.
    /// @param Maximum number of reclamation attempts after which all leases
    /// should be reclaimed.
    void
    reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
                         const bool remove_lease,
                         const uint16_t max_unwarned_cycles) {
        // Increase calls counter for this method.
        ++reclaim_calls_count_;
        // Record all parameters with which this method has been called.
        reclaim_params_.max_leases = max_leases;
        reclaim_params_.timeout = timeout;
        reclaim_params_.remove_lease = remove_lease;
        reclaim_params_.max_unwarned_cycles = max_unwarned_cycles;

        // Leases' reclamation routine is responsible for re-scheduling
        // the timer.
        timer_mgr_->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
    }

    /// @brief Stub implementation of the routine which flushes
    /// expired-reclaimed leases.
    ///
    /// @param secs Specifies the minimum amount of time, expressed in
    /// seconds, that must elapse before the expired-reclaimed lease is
    /// deleted from the database.
    void
    deleteReclaimedLeases(const uint32_t secs) {
        // Increase calls counter for this method.
        ++delete_calls_count_;
        // Record the value of the parameter.
        secs_param_ = secs;

        // Routine which flushes the reclaimed leases is responsible for
        // re-scheduling the timer.
        timer_mgr_->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
    }

    /// @brief Counter holding the number of calls to @c reclaimExpiredLeases.
    long reclaim_calls_count_;

    /// @brief Counter holding the number of calls to @c deleteReclaimedLeases.
    long delete_calls_count_;

    /// @brief Structure holding values of parameters with which the
    /// @c reclaimExpiredLeases was called.
    ///
    /// These values are overridden on subsequent calls to this method.
    RecordedParams reclaim_params_;

    /// @brief Value of the parameter with which the @c deleteReclaimedLeases
    /// was called.
    uint32_t secs_param_;

private:

    /// @brief Pointer to the @c TimerMgr.
    TimerMgrPtr timer_mgr_;

};

/// @brief Pointer to the @c LeaseReclamationStub.
typedef boost::shared_ptr<LeaseReclamationStub> LeaseReclamationStubPtr;

/// @brief Test fixture class for the @c CfgExpiration.
class CfgExpirationTimersTest : public ::testing::Test {
public:

    /// @brief Constructor.
    ///
    /// Creates instance of the test fixture class. Besides initialization
    /// of the class members, it also stops the @c TimerMgr worker thread
    /// and removes any registered timers.
    CfgExpirationTimersTest()
        : io_service_(new IOService()),
          timer_mgr_(TimerMgr::instance()),
          stub_(new LeaseReclamationStub()),
          cfg_(true) {
        cleanupTimerMgr();
    }

    /// @brief Destructor.
    ///
    /// It stops the @c TimerMgr worker thread and removes any registered
    /// timers.
    virtual ~CfgExpirationTimersTest() {
        cleanupTimerMgr();
        io_service_->stopAndPoll();
    }

    /// @brief Stop @c TimerMgr worker thread and remove the timers.
    void cleanupTimerMgr() const {
        timer_mgr_->unregisterTimers();
        timer_mgr_->setIOService(io_service_);
    }

    /// @brief Runs IOService and stops after a specified time.
    ///
    /// @param timeout_ms Amount of time after which the method returns.
    void runTimersWithTimeout(const long timeout_ms) {
        IntervalTimer timer(io_service_);
        timer.setup([this]() {
                io_service_->stop();
        }, timeout_ms, IntervalTimer::ONE_SHOT);

        io_service_->run();
        io_service_->stop();
        io_service_->restart();
    }

    /// @brief Setup timers according to the configuration and run them
    /// for the specified amount of time.
    ///
    /// @param timeout_ms Timeout in milliseconds.
    void setupAndRun(const long timeout_ms) {
        cfg_.setupTimers(&LeaseReclamationStub::reclaimExpiredLeases,
                         &LeaseReclamationStub::deleteReclaimedLeases,
                         stub_.get());
        // Run timers.
        ASSERT_NO_THROW({
            runTimersWithTimeout(timeout_ms);
        });
    }

    /// @brief Pointer to the IO service used by the tests.
    IOServicePtr io_service_;

    /// @brief Pointer to the @c TimerMgr.
    TimerMgrPtr timer_mgr_;

    /// @brief Pointer to the @c LeaseReclamationStub instance.
    LeaseReclamationStubPtr stub_;

    /// @brief Instance of the @c CfgExpiration class used by the tests.
    CfgExpiration cfg_;
};

// Test that the reclamation routines are called with the appropriate parameters.
TEST_F(CfgExpirationTimersTest, reclamationParameters) {
    // Set this value to true, to make sure that the timer callback would
    // modify this value to false.
    stub_->reclaim_params_.remove_lease = true;

    // Set parameters to some non-default values.
    cfg_.setMaxReclaimLeases(1000);
    cfg_.setMaxReclaimTime(1500);
    cfg_.setHoldReclaimedTime(1800);
    cfg_.setUnwarnedReclaimCycles(13);

    // Run timers for 500ms.
    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));

    // Make sure we had more than one call to the reclamation routine.
    ASSERT_GT(stub_->reclaim_calls_count_, 1);
    // Make sure it was called with appropriate arguments.
    EXPECT_EQ(1000, stub_->reclaim_params_.max_leases);
    EXPECT_EQ(1500, stub_->reclaim_params_.timeout);
    EXPECT_FALSE(stub_->reclaim_params_.remove_lease);
    EXPECT_EQ(13, stub_->reclaim_params_.max_unwarned_cycles);

    // Make sure we had more than one call to the routine which flushes
    // expired reclaimed leases.
    ASSERT_GT(stub_->delete_calls_count_, 1);
    // Make sure that the argument was correct.
    EXPECT_EQ(1800, stub_->secs_param_);
}

// This test verifies that if the value of "flush-reclaimed-timer-wait-time"
// configuration parameter is set to 0, the lease reclamation routine would
// delete reclaimed leases from a lease database.
TEST_F(CfgExpirationTimersTest, noLeaseAffinity) {
    // Set the timer for flushing leases to 0. This effectively disables
    // the timer.
    cfg_.setFlushReclaimedTimerWaitTime(0);

    // Run the lease reclamation timer for a while.
    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));

    // Make sure that the lease reclamation routine has been executed a
    // couple of times.
    ASSERT_GT(stub_->reclaim_calls_count_, 1);
    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
              stub_->reclaim_params_.max_leases);
    EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
              stub_->reclaim_params_.timeout);
    // When the "flush" timer is disabled, the lease reclamation routine is
    // responsible for removal of reclaimed leases. This is controlled using
    // the "remove_lease" parameter which should be set to true in this case.
    EXPECT_TRUE(stub_->reclaim_params_.remove_lease);

    // The routine flushing reclaimed leases should not be run at all.
    EXPECT_EQ(0, stub_->delete_calls_count_);
}

// This test verifies that lease reclamation may be disabled.
TEST_F(CfgExpirationTimersTest, noLeaseReclamation) {
    // Disable both timers.
    cfg_.setReclaimTimerWaitTime(0);
    cfg_.setFlushReclaimedTimerWaitTime(0);

    // Wait for 500ms.
    ASSERT_NO_FATAL_FAILURE(setupAndRun(500));

    // Make sure that neither leases' reclamation routine nor the routine
    // flushing expired-reclaimed leases was run.
    EXPECT_EQ(0, stub_->reclaim_calls_count_);
    EXPECT_EQ(0, stub_->delete_calls_count_);
}

} // end of anonymous namespace