memfile_lease_mgr.cc 36.9 KB
Newer Older
1
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// 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.

15
#include <config.h>
16
#include <dhcpsrv/cfgmgr.h>
17
#include <dhcpsrv/dhcpsrv_log.h>
18
#include <dhcpsrv/lease_file_loader.h>
19
#include <dhcpsrv/memfile_lease_mgr.h>
20
#include <exceptions/exceptions.h>
21
#include <util/pid_file.h>
22
#include <util/process_spawn.h>
23
#include <util/signal_set.h>
24
#include <cstdio>
25
26
#include <cstring>
#include <errno.h>
27
#include <iostream>
28
#include <limits>
29
#include <sstream>
30

31
32
33
34
35
namespace {

/// @brief Maximum number of errors to read the leases from the lease file.
const uint32_t MAX_LEASE_ERRORS = 100;

36
37
38
39
40
41
42
/// @brief A name of the environmental variable specifying the kea-lfc
/// program location.
///
/// This variable can be set by tests to point to the location of the
/// kea-lfc program within a build directory. If this variable is not
/// set, the backend will use the location of the kea-lfc in the
/// Kea installation directory.
43
44
const char* KEA_LFC_EXECUTABLE_ENV_NAME = "KEA_LFC_EXECUTABLE";

45
46
} // end of anonymous namespace

47
using namespace isc::util;
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
namespace isc {
namespace dhcp {

/// @brief Represents a configuration for Lease File Cleanup.
///
/// This class is solely used by the @c Memfile_LeaseMgr as a configuration
/// information storage for %Lease File Cleanup. Internally, it creates
/// the interval timer and assigns a callback function (pointer to which is
/// passed in the constructor), which will be called at the specified
/// intervals to perform the cleanup. It is also responsible for creating
/// and maintaing the object which is used to spawn the new process which
/// executes the @c kea-lfc program.
///
/// This functionality is enclosed in a separate class so as the implementation
/// details are not exposed in the @c Memfile_LeaseMgr header file and
/// to maintain a single place with the LFC configuration, instead of multiple
/// members and functions scattered in the @c Memfile_LeaseMgr class.
class LFCSetup {
public:

    /// @brief Constructor.
    ///
    /// Assigns a pointer to the function triggered to perform the cleanup.
    /// This pointer should point to the appropriate method of the
    /// @c Memfile_LeaseMgr class.
    ///
    /// @param callback A pointer to the callback function.
    /// @param io_service An io service used to create the interval timer.
    LFCSetup(asiolink::IntervalTimer::Callback callback,
             asiolink::IOService& io_service);

    /// @brief Sets the new configuration for the %Lease File Cleanup.
    ///
    /// @param lfc_interval An interval in seconds at which the cleanup should
    /// be performed.
    /// @param lease_file4 A pointer to the DHCPv4 lease file to be cleaned up
    /// or NULL. If this is NULL, the @c lease_file6 must be non-null.
    /// @param lease_file6 A pointer to the DHCPv6 lease file to be cleaned up
    /// or NULL. If this is NULL, the @c lease_file4 must be non-null.
    void setup(const uint32_t lfc_interval,
               const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
               const boost::shared_ptr<CSVLeaseFile6>& lease_file6);

    /// @brief Spawns a new process.
    void execute();

    /// @brief Returns interval at which the cleanup is performed.
    ///
    /// @return Interval in milliseconds.
    long getInterval() const;

    /// @brief Checks if the lease file cleanup is in progress.
    ///
    /// @return true if the lease file cleanup is being executed.
    bool isRunning() const;

    /// @brief Returns exit code of the last completed cleanup.
    int getExitStatus() const;

private:

    /// @brief Interval timer for LFC.
    asiolink::IntervalTimer timer_;

    /// @brief A pointer to the @c ProcessSpawn object used to execute
    /// the LFC.
    boost::scoped_ptr<util::ProcessSpawn> process_;

    /// @brief A pointer to the callback function executed by the timer.
    asiolink::IntervalTimer::Callback callback_;

    /// @brief A PID of the last executed LFC process.
    pid_t pid_;
};

LFCSetup::LFCSetup(asiolink::IntervalTimer::Callback callback,
                   asiolink::IOService& io_service)
    : timer_(io_service), process_(), callback_(callback), pid_(0) {
}

void
LFCSetup::setup(const uint32_t lfc_interval,
                const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
                const boost::shared_ptr<CSVLeaseFile6>& lease_file6) {

    // If LFC is enabled, we have to setup the interval timer and prepare for
    // executing the kea-lfc process.
    if (lfc_interval > 0) {
        std::string executable;
        char* c_executable = getenv(KEA_LFC_EXECUTABLE_ENV_NAME);
        if (c_executable == NULL) {
            executable = KEA_LFC_EXECUTABLE;

        } else {
            executable = c_executable;
        }

        // Set the timer to call callback function periodically.
        LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SETUP).arg(lfc_interval);
        // Multiple the lfc_interval value by 1000 as this value specifies
        // a timeout in seconds, whereas the setup() method expects the
        // timeout in milliseconds.
        timer_.setup(callback_, lfc_interval * 1000);

        // Start preparing the command line for kea-lfc.

        // Gather the base file name.
        std::string lease_file = lease_file4 ? lease_file4->getFilename() :
            lease_file6->getFilename();

        // Create the other names by appending suffixes to the base name.
        util::ProcessArgs args;
        // Universe: v4 or v6.
        args.push_back(lease_file4 ? "-4" : "-6");
        // Previous file.
        args.push_back("-x");
        args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
                                                      Memfile_LeaseMgr::FILE_PREVIOUS));
        // Input file.
        args.push_back("-i");
        args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
                                                      Memfile_LeaseMgr::FILE_INPUT));
        // Output file.
        args.push_back("-o");
        args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
                                                      Memfile_LeaseMgr::FILE_OUTPUT));
        // Finish file.
        args.push_back("-f");
        args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
                                                      Memfile_LeaseMgr::FILE_FINISH));
        // PID file.
        args.push_back("-p");
        args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
                                                      Memfile_LeaseMgr::FILE_PID));

        // The configuration file is currently unused.
        args.push_back("-c");
        args.push_back("ignored-path");

        // Create the process (do not start it yet).
        process_.reset(new util::ProcessSpawn(executable, args));
    }
}

long
LFCSetup::getInterval() const {
    return (timer_.getInterval());
}

void
LFCSetup::execute() {
    try {
        LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_EXECUTE)
            .arg(process_->getCommandLine());
        pid_ = process_->spawn();

205
    } catch (const ProcessSpawnError&) {
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SPAWN_FAIL);
    }
}

bool
LFCSetup::isRunning() const {
    return (process_ && process_->isRunning(pid_));
}

int
LFCSetup::getExitStatus() const {
    if (!process_) {
        isc_throw(InvalidOperation, "unable to obtain LFC process exit code: "
                  " the process is NULL");
    }
    return (process_->getExitStatus(pid_));
}

224
225
226
227
// Explicit definition of class static constants.  Values are given in the
// declaration so they're not needed here.
const int Memfile_LeaseMgr::MAJOR_VERSION;
const int Memfile_LeaseMgr::MINOR_VERSION;
228

229
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
230
231
232
233
    : LeaseMgr(parameters),
      lfc_setup_(new LFCSetup(boost::bind(&Memfile_LeaseMgr::lfcCallback, this),
                              *getIOService()))
    {
234
235
236
237
238
    // Check the universe and use v4 file or v6 file.
    std::string universe = getParameter("universe");
    if (universe == "4") {
        std::string file4 = initLeaseFilePath(V4);
        if (!file4.empty()) {
239
240
            loadLeasesFromFiles<Lease4, CSVLeaseFile4>(file4, lease_file4_,
                                                       storage4_);
241
242
243
244
        }
    } else {
        std::string file6 = initLeaseFilePath(V6);
        if (!file6.empty()) {
245
246
            loadLeasesFromFiles<Lease6, CSVLeaseFile6>(file6, lease_file6_,
                                                       storage6_);
247
        }
248
    }
249
250
251
252
253

    // If lease persistence have been disabled for both v4 and v6,
    // issue a warning. It is ok not to write leases to disk when
    // doing testing, but it should not be done in normal server
    // operation.
254
   if (!persistLeases(V4) && !persistLeases(V6)) {
255
        LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_NO_STORAGE);
256
257

    } else  {
258
        lfcSetup();
259
    }
260
261
262
}

Memfile_LeaseMgr::~Memfile_LeaseMgr() {
263
264
265
266
267
268
269
270
    if (lease_file4_) {
        lease_file4_->close();
        lease_file4_.reset();
    }
    if (lease_file6_) {
        lease_file6_->close();
        lease_file6_.reset();
    }
271
272
}

273
274
275
276
277
278
279
280
std::string
Memfile_LeaseMgr::getDBVersion() {
    std::stringstream tmp;
    tmp << "Memfile backend " << MAJOR_VERSION;
    tmp << "." << MINOR_VERSION;
    return (tmp.str());
}

281
282
bool
Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
283
284
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
285

286
287
288
289
    if (getLease4(lease->addr_)) {
        // there is a lease with specified address already
        return (false);
    }
290
291
292
293

    // Try to write a lease to disk first. If this fails, the lease will
    // not be inserted to the memory and the disk and in-memory data will
    // remain consistent.
294
    if (persistLeases(V4)) {
295
296
297
        lease_file4_->append(*lease);
    }

298
299
    storage4_.insert(lease);
    return (true);
300
301
}

302
303
bool
Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
304
305
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
306

307
    if (getLease6(lease->type_, lease->addr_)) {
308
309
310
        // there is a lease with specified address already
        return (false);
    }
311
312
313
314

    // Try to write a lease to disk first. If this fails, the lease will
    // not be inserted to the memory and the disk and in-memory data will
    // remain consistent.
315
    if (persistLeases(V6)) {
316
317
318
        lease_file6_->append(*lease);
    }

319
320
321
322
    storage6_.insert(lease);
    return (true);
}

323
324
Lease4Ptr
Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
325
326
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
327

328
329
330
    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
    Lease4StorageAddressIndex::iterator l = idx.find(addr);
    if (l == idx.end()) {
331
332
        return (Lease4Ptr());
    } else {
333
        return (Lease4Ptr(new Lease4(**l)));
334
    }
335
336
}

337
338
Lease4Collection
Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
339
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
340
              DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
341
    Lease4Collection collection;
342
343
    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
    for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
344
345
346
        lease != idx.end(); ++lease) {

        // Every Lease4 has a hardware address, so we can compare it
347
        if ( (*(*lease)->hwaddr_) == hwaddr) {
348
            collection.push_back((*lease));
349
350
        }
    }
351

352
    return (collection);
353
354
}

355
356
Lease4Ptr
Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
357
358
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
359
360
        .arg(hwaddr.toText());

361
362
363
    // Get the index by HW Address and Subnet Identifier.
    const Lease4StorageHWAddressSubnetIdIndex& idx =
        storage4_.get<HWAddressSubnetIdIndexTag>();
364
    // Try to find the lease using HWAddr and subnet id.
365
    Lease4StorageHWAddressSubnetIdIndex::const_iterator lease =
366
367
        idx.find(boost::make_tuple(hwaddr.hwaddr_, subnet_id));
    // Lease was not found. Return empty pointer to the caller.
368
    if (lease == idx.end()) {
369
        return (Lease4Ptr());
370
371
    }

372
    // Lease was found. Return it to the caller.
373
    return (Lease4Ptr(new Lease4(**lease)));
374
375
}

376
Lease4Collection
377
Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
378
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
379
              DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
380
    Lease4Collection collection;
381
382
    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
    for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
383
384
385
386
        lease != idx.end(); ++ lease) {

        // client-id is not mandatory in DHCPv4. There can be a lease that does
        // not have a client-id. Dereferencing null pointer would be a bad thing
387
388
        if((*lease)->client_id_ && *(*lease)->client_id_ == client_id) {
            collection.push_back((*lease));
389
390
391
392
        }
    }

    return (collection);
393
}
394

395
396
Lease4Ptr
Memfile_LeaseMgr::getLease4(const ClientId& client_id,
397
398
399
400
401
402
403
                            const HWAddr& hwaddr,
                            SubnetID subnet_id) const {
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_CLIENTID_HWADDR_SUBID).arg(client_id.toText())
                                                        .arg(hwaddr.toText())
                                                        .arg(subnet_id);

404
405
406
    // Get the index by client id, HW address and subnet id.
    const Lease4StorageClientIdHWAddressSubnetIdIndex& idx =
        storage4_.get<ClientIdHWAddressSubnetIdIndexTag>();
407
    // Try to get the lease using client id, hardware address and subnet id.
408
    Lease4StorageClientIdHWAddressSubnetIdIndex::const_iterator lease =
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
        idx.find(boost::make_tuple(client_id.getClientId(), hwaddr.hwaddr_,
                                   subnet_id));

    if (lease == idx.end()) {
        // Lease was not found. Return empty pointer to the caller.
        return (Lease4Ptr());
    }

    // Lease was found. Return it to the caller.
    return (*lease);
}

Lease4Ptr
Memfile_LeaseMgr::getLease4(const ClientId& client_id,
                            SubnetID subnet_id) const {
424
425
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
426
              .arg(client_id.toText());
427

428
429
430
    // Get the index by client and subnet id.
    const Lease4StorageClientIdSubnetIdIndex& idx =
        storage4_.get<ClientIdSubnetIdIndexTag>();
431
    // Try to get the lease using client id and subnet id.
432
    Lease4StorageClientIdSubnetIdIndex::const_iterator lease =
433
434
        idx.find(boost::make_tuple(client_id.getClientId(), subnet_id));
    // Lease was not found. Return empty pointer to the caller.
435
    if (lease == idx.end()) {
436
        return (Lease4Ptr());
437
    }
438
    // Lease was found. Return it to the caller.
439
    return (Lease4Ptr(new Lease4(**lease)));
440
441
}

442
Lease6Ptr
443
Memfile_LeaseMgr::getLease6(Lease::Type type,
444
                            const isc::asiolink::IOAddress& addr) const {
445
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
446
447
448
              DHCPSRV_MEMFILE_GET_ADDR6)
        .arg(addr.toText())
        .arg(Lease::typeToText(type));
449
    Lease6Storage::iterator l = storage6_.find(addr);
450
    if (l == storage6_.end() || !(*l) || ((*l)->type_ != type)) {
451
452
        return (Lease6Ptr());
    } else {
453
        return (Lease6Ptr(new Lease6(**l)));
454
455
456
    }
}

457
Lease6Collection
458
Memfile_LeaseMgr::getLeases6(Lease::Type type,
459
                            const DUID& duid, uint32_t iaid) const {
460
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
461
462
463
464
              DHCPSRV_MEMFILE_GET_IAID_DUID)
        .arg(iaid)
        .arg(duid.toText())
        .arg(Lease::typeToText(type));
465

466
467
    // Get the index by DUID, IAID, lease type.
    const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
468
    // Try to get the lease using the DUID, IAID and lease type.
469
470
    std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
              Lease6StorageDuidIaidTypeIndex::const_iterator> l =
471
472
        idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
    Lease6Collection collection;
473
474
    for(Lease6StorageDuidIaidTypeIndex::const_iterator lease =
            l.first; lease != l.second; ++lease) {
475
476
        collection.push_back(Lease6Ptr(new Lease6(**lease)));
    }
477

478
    return (collection);
479
480
}

481
Lease6Collection
482
Memfile_LeaseMgr::getLeases6(Lease::Type type,
483
484
                             const DUID& duid, uint32_t iaid,
                             SubnetID subnet_id) const {
485
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
486
              DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
487
488
489
490
        .arg(iaid)
        .arg(subnet_id)
        .arg(duid.toText())
        .arg(Lease::typeToText(type));
491

492
493
    // Get the index by DUID, IAID, lease type.
    const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
494
    // Try to get the lease using the DUID, IAID and lease type.
495
496
    std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
              Lease6StorageDuidIaidTypeIndex::const_iterator> l =
497
498
        idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
    Lease6Collection collection;
499
500
    for(Lease6StorageDuidIaidTypeIndex::const_iterator lease =
            l.first; lease != l.second; ++lease) {
501
502
503
504
        // Filter out the leases which subnet id doesn't match.
        if((*lease)->subnet_id_ == subnet_id) {
            collection.push_back(Lease6Ptr(new Lease6(**lease)));
        }
505
    }
506
507

    return (collection);
508
509
}

510
511
512
void
Memfile_LeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
                                    const size_t max_leases) const {
513
514
515
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_EXPIRED4)
        .arg(max_leases);

516
517
518
519
520
521
522
523
    // Obtain the index which segragates leases by state and time.
    const Lease6StorageExpirationIndex& index = storage6_.get<ExpirationIndexTag>();

    // Retrieve leases which are not reclaimed and which haven't expired. The
    // 'less-than' operator will be used for both components of the index. So,
    // for the 'state' 'false' is less than 'true'. Also the leases with
    // expiration time lower than current time will be returned.
    Lease6StorageExpirationIndex::const_iterator ub =
524
        index.upper_bound(boost::make_tuple(false, time(NULL)));
525
526
527
528
529
530
531
532
533
534
535
536
537

    // Copy only the number of leases indicated by the max_leases parameter.
    for (Lease6StorageExpirationIndex::const_iterator lease = index.begin();
         (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
                                                 max_leases));
         ++lease) {
        expired_leases.push_back(Lease6Ptr(new Lease6(**lease)));
    }
}

void
Memfile_LeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
                                    const size_t max_leases) const {
538
539
540
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_EXPIRED6)
        .arg(max_leases);

541
542
543
544
545
546
547
548
    // Obtain the index which segragates leases by state and time.
    const Lease4StorageExpirationIndex& index = storage4_.get<ExpirationIndexTag>();

    // Retrieve leases which are not reclaimed and which haven't expired. The
    // 'less-than' operator will be used for both components of the index. So,
    // for the 'state' 'false' is less than 'true'. Also the leases with
    // expiration time lower than current time will be returned.
    Lease4StorageExpirationIndex::const_iterator ub =
549
        index.upper_bound(boost::make_tuple(false, time(NULL)));
550
551
552
553
554
555
556
557
558
559

    // Copy only the number of leases indicated by the max_leases parameter.
    for (Lease4StorageExpirationIndex::const_iterator lease = index.begin();
         (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
                                                 max_leases));
         ++lease) {
        expired_leases.push_back(Lease4Ptr(new Lease4(**lease)));
    }
}

560
561
void
Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
562
563
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
564

565
566
567
568
569
    // Obtain 'by address' index.
    Lease4StorageAddressIndex& index = storage4_.get<AddressIndexTag>();

    // Lease must exist if it is to be updated.
    Lease4StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
570
    if (lease_it == index.end()) {
571
        isc_throw(NoSuchLease, "failed to update the lease with address "
572
                  << lease->addr_ << " - no such lease");
573
    }
574
575
576
577

    // Try to write a lease to disk first. If this fails, the lease will
    // not be inserted to the memory and the disk and in-memory data will
    // remain consistent.
578
    if (persistLeases(V4)) {
579
580
581
        lease_file4_->append(*lease);
    }

582
583
    // Use replace() to re-index leases.
    index.replace(lease_it, Lease4Ptr(new Lease4(*lease)));
584
585
}

586
587
void
Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
588
589
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
590

591
592
593
594
595
596
    // Obtain 'by address' index.
    Lease6StorageAddressIndex& index = storage6_.get<AddressIndexTag>();

    // Lease must exist if it is to be updated.
    Lease6StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
    if (lease_it == index.end()) {
597
        isc_throw(NoSuchLease, "failed to update the lease with address "
598
                  << lease->addr_ << " - no such lease");
599
    }
600
601
602
603

    // Try to write a lease to disk first. If this fails, the lease will
    // not be inserted to the memory and the disk and in-memory data will
    // remain consistent.
604
    if (persistLeases(V6)) {
605
606
607
        lease_file6_->append(*lease);
    }

608
609
    // Use replace() to re-index leases.
    index.replace(lease_it, Lease6Ptr(new Lease6(*lease)));
610
611
}

612
613
bool
Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
614
615
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
616
    if (addr.isV4()) {
617
618
619
620
621
622
        // v4 lease
        Lease4Storage::iterator l = storage4_.find(addr);
        if (l == storage4_.end()) {
            // No such lease
            return (false);
        } else {
623
            if (persistLeases(V4)) {
624
625
626
627
628
629
630
631
                // Copy the lease. The valid lifetime needs to be modified and
                // we don't modify the original lease.
                Lease4 lease_copy = **l;
                // Setting valid lifetime to 0 means that lease is being
                // removed.
                lease_copy.valid_lft_ = 0;
                lease_file4_->append(lease_copy);
            }
632
633
634
            storage4_.erase(l);
            return (true);
        }
635

636
    } else {
637
        // v6 lease
638
639
640
641
642
        Lease6Storage::iterator l = storage6_.find(addr);
        if (l == storage6_.end()) {
            // No such lease
            return (false);
        } else {
643
            if (persistLeases(V6)) {
644
645
646
647
648
649
650
651
652
                // Copy the lease. The lifetimes need to be modified and we
                // don't modify the original lease.
                Lease6 lease_copy = **l;
                // Setting lifetimes to 0 means that lease is being removed.
                lease_copy.valid_lft_ = 0;
                lease_copy.preferred_lft_ = 0;
                lease_file6_->append(lease_copy);
            }

653
654
655
            storage6_.erase(l);
            return (true);
        }
656
657
658
    }
}

659
uint64_t
660
Memfile_LeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
661
662
663
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4)
        .arg(secs);
664
665
666
    return (deleteExpiredReclaimedLeases<
            Lease4StorageExpirationIndex, Lease4
            >(secs, V4, storage4_, lease_file4_));
667
668
}

669
uint64_t
670
Memfile_LeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
671
672
673
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6)
        .arg(secs);
674
675
676
    return (deleteExpiredReclaimedLeases<
            Lease6StorageExpirationIndex, Lease6
            >(secs, V6, storage6_, lease_file6_));
677
678
}

679
680
template<typename IndexType, typename LeaseType, typename StorageType,
         typename LeaseFileType>
681
uint64_t
682
Memfile_LeaseMgr::deleteExpiredReclaimedLeases(const uint32_t secs,
683
684
685
                                               const Universe& universe,
                                               StorageType& storage,
                                               LeaseFileType& lease_file) const {
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
    // Obtain the index which segragates leases by state and time.
    IndexType& index = storage.template get<ExpirationIndexTag>();

    // This returns the first element which is greater than the specified
    // tuple (true, time(NULL) - secs). However, the range between the
    // beginnng of the index and returned element also includes all the
    // elements for which the first value is false (lease state is NOT
    // reclaimed), because false < true. All elements between the
    // beginning of the index and the element returned, for which the
    // first value is true, represent the reclaimed leases which should
    // be deleted, because their expiration time + secs has occured earlier
    // than current time.
    typename IndexType::const_iterator upper_limit =
        index.upper_bound(boost::make_tuple(true, time(NULL) - secs));

    // Now, we have to exclude all elements of the index which represent
    // leases in the state other than reclaimed - with the first value
    // in the index equal to false. Note that elements in the index are
    // ordered from the lower to the higher ones. So, all elements with
    // the first value of false are placed before the elements with the
    // value of true. Hence, we have to find the first element which
    // contains value of true. The time value is the lowest possible.
    typename IndexType::const_iterator lower_limit =
        index.upper_bound(boost::make_tuple(true, std::numeric_limits<int64_t>::min()));

    // If there are some elements in this range, delete them.
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
    uint64_t num_leases = static_cast<uint64_t>(std::distance(lower_limit, upper_limit));
    if (num_leases > 0) {

        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
                  DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START)
            .arg(num_leases);

        // If lease persistence is enabled, we also have to mark leases
        // as deleted in the lease file. We do this by setting the
        // lifetime to 0.
        if (persistLeases(universe)) {
            for (typename IndexType::const_iterator lease = lower_limit;
                 lease != upper_limit; ++lease) {
                // Copy lease to not affect the lease in the container.
                LeaseType lease_copy(**lease);
                // Set the valid lifetime to 0 to indicate the removal
                // of the lease.
                lease_copy.valid_lft_ = 0;
                lease_file->append(lease_copy);
            }
        }

        // Erase leases from memory.
735
736
        index.erase(lower_limit, upper_limit);
    }
737
738
    // Return number of leases deleted.
    return (num_leases);
739
740
741
}


742
743
std::string
Memfile_LeaseMgr::getDescription() const {
744
745
746
747
    return (std::string("This is a dummy memfile backend implementation.\n"
                        "It does not offer any useful lease management and its only\n"
                        "purpose is to test abstract lease manager API."));
}
748
749
750

void
Memfile_LeaseMgr::commit() {
751
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
752
753
754
755
}

void
Memfile_LeaseMgr::rollback() {
756
757
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ROLLBACK);
758
}
759

760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
std::string
Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
                               const LFCFileType& file_type) {
    std::string name(file_name);
    switch (file_type) {
    case FILE_INPUT:
        name += ".1";
        break;
    case FILE_PREVIOUS:
        name += ".2";
        break;
    case FILE_OUTPUT:
        name += ".output";
        break;
    case FILE_FINISH:
        name += ".completed";
        break;
777
778
779
    case FILE_PID:
        name += ".pid";
        break;
780
781
782
783
784
785
786
787
788
    default:
        // Do not append any suffix for the FILE_CURRENT.
        ;
    }

    return (name);
}


789
790
uint32_t
Memfile_LeaseMgr::getIOServiceExecInterval() const {
791
    return (static_cast<uint32_t>(lfc_setup_->getInterval() / 1000));
792
793
}

794
795
796
797
798
799
800
801
802
std::string
Memfile_LeaseMgr::getDefaultLeaseFilePath(Universe u) const {
    std::ostringstream s;
    s << CfgMgr::instance().getDataDir() << "/kea-leases";
    s << (u == V4 ? "4" : "6");
    s << ".csv";
    return (s.str());
}

803
804
805
806
807
808
809
810
811
std::string
Memfile_LeaseMgr::getLeaseFilePath(Universe u) const {
    if (u == V4) {
        return (lease_file4_ ? lease_file4_->getFilename() : "");
    }

    return (lease_file6_ ? lease_file6_->getFilename() : "");
}

812
813
bool
Memfile_LeaseMgr::persistLeases(Universe u) const {
814
815
816
817
818
819
820
821
    // Currently, if the lease file IO is not created, it means that writes to
    // disk have been explicitly disabled by the administrator. At some point,
    // there may be a dedicated ON/OFF flag implemented to control this.
    if (u == V4 && lease_file4_) {
        return (true);
    }

    return (u == V6 && lease_file6_);
822
823
824
825
}

std::string
Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
826
    std::string persist_val;
827
    try {
828
        persist_val = getParameter("persist");
829
    } catch (const Exception&) {
830
831
        // If parameter persist hasn't been specified, we use a default value
        // 'yes'.
832
        persist_val = "true";
833
    }
834
    // If persist_val is 'false' we will not store leases to disk, so let's
835
    // return empty file name.
836
    if (persist_val == "false") {
837
        return ("");
838

839
840
841
    } else if (persist_val != "true") {
        isc_throw(isc::BadValue, "invalid value 'persist="
                  << persist_val << "'");
842
843
    }

844
845
    std::string lease_file;
    try {
846
        lease_file = getParameter("name");
847
    } catch (const Exception&) {
848
849
850
851
        lease_file = getDefaultLeaseFilePath(u);
    }
    return (lease_file);
}
852

853
854
855
856
857
858
859
860
861
862
863
864
865
866
template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
void Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
                                           boost::shared_ptr<LeaseFileType>& lease_file,
                                           StorageType& storage) {
    // Check if the instance of the LFC is running right now. If it is
    // running, we refuse to load leases as the LFC may be writing to the
    // lease files right now. When the user retries server configuration
    // it should go through.
    /// @todo Consider applying a timeout for an LFC and retry when this
    /// timeout elapses.
    PIDFile pid_file(appendSuffix(filename, FILE_PID));
    if (pid_file.check()) {
        isc_throw(DbOpenError, "unable to load leases from files while the "
                  "lease file cleanup is in progress");
867
868
    }

869
    storage.clear();
870

871
872
873
874
875
    // Load the leasefile.completed, if exists.
    lease_file.reset(new LeaseFileType(std::string(filename + ".completed")));
    if (lease_file->exists()) {
        LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                               MAX_LEASE_ERRORS);
876

877
878
879
880
881
882
883
    } else {
        // If the leasefile.completed doesn't exist, let's load the leases
        // from leasefile.2 and leasefile.1, if they exist.
        lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_PREVIOUS)));
        if (lease_file->exists()) {
            LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                                   MAX_LEASE_ERRORS);
884
        }
885

886
887
888
889
890
891
        lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
        if (lease_file->exists()) {
            LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                                   MAX_LEASE_ERRORS);
        }
    }
892

893
894
895
896
897
898
899
900
    // Always load leases from the primary lease file. If the lease file
    // doesn't exist it will be created by the LeaseFileLoader. Note
    // that the false value passed as the last parameter to load
    // function causes the function to leave the file open after
    // it is parsed. This file will be used by the backend to record
    // future lease updates.
    lease_file.reset(new LeaseFileType(filename));
    LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
Francis Dupont's avatar
Francis Dupont committed
901
                                           MAX_LEASE_ERRORS, false);
902
}
903

904

905
906
907
bool
Memfile_LeaseMgr::isLFCRunning() const {
    return (lfc_setup_->isRunning());
908
909
}

910
911
912
913
int
Memfile_LeaseMgr::getLFCExitStatus() const {
    return (lfc_setup_->getExitStatus());
}
914

915
916
void
Memfile_LeaseMgr::lfcCallback() {
917
    LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_START);
918
919
920

    // Check if we're in the v4 or v6 space and use the appropriate file.
    if (lease_file4_) {
921
        lfcExecute(lease_file4_);
922
923

    } else if (lease_file6_) {
924
925
926
927
928
929
930
931
932
        lfcExecute(lease_file6_);
    }
}

void
Memfile_LeaseMgr::lfcSetup() {
    std::string lfc_interval_str = "0";
    try {
        lfc_interval_str = getParameter("lfc-interval");
933
    } catch (const std::exception&) {
934
935
936
937
938
939
        // Ignore and default to 0.
    }

    uint32_t lfc_interval = 0;
    try {
        lfc_interval = boost::lexical_cast<uint32_t>(lfc_interval_str);
940
    } catch (boost::bad_lexical_cast&) {
941
942
943
        isc_throw(isc::BadValue, "invalid value of the lfc-interval "
                  << lfc_interval_str << " specified");
    }
944

945
946
    if (lfc_interval > 0) {
        lfc_setup_->setup(lfc_interval, lease_file4_, lease_file6_);
947
948
949
950
    }
}

template<typename LeaseFileType>
951
void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) {
952
    bool do_lfc = true;
953

954
955
    // Check the status of the LFC instance.
    // If the finish file exists or the copy of the lease file exists it
956
957
958
    // is an indication that another LFC instance may be in progress or
    // may be stalled. In that case we don't want to rotate the current
    // lease file to avoid overriding the contents of the existing file.
959
    CSVFile lease_file_finish(appendSuffix(lease_file->getFilename(), FILE_FINISH));
960
    CSVFile lease_file_copy(appendSuffix(lease_file->getFilename(), FILE_INPUT));
961
    if (!lease_file_finish.exists() && !lease_file_copy.exists()) {
962
        // Close the current file so as we can move it to the copy file.
963
        lease_file->close();
964
965
966
967
        // Move the current file to the copy file. Remember the result
        // because we don't want to run LFC if the rename failed.
        do_lfc = (rename(lease_file->getFilename().c_str(),
                         lease_file_copy.getFilename().c_str()) == 0);
968
969
970
971
972
973
974

        if (!do_lfc) {
            LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL)
                .arg(lease_file->getFilename())
                .arg(lease_file_copy.getFilename())
                .arg(strerror(errno));
        }
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994

        // Regardless if we successfully moved the current file or not,
        // we need to re-open the current file for the server to write
        // new lease updates. If the file has been successfully moved,
        // this will result in creation of the new file. Otherwise,
        // an existing file will be opened.
        try {
            lease_file->open(true);

        } catch (const CSVFileError& ex) {
            // If we're unable to open the lease file this is a serious
            // error because the server will not be able to persist
            // leases.
            /// @todo We need to better address this error. It should
            /// trigger an alarm (once we have a monitoring system in
            /// place) so as an administrator can correct it. In
            /// practice it should be very rare that this happens and
            /// is most likely related to a human error, e.g. changing
            /// file permissions.
            LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL)
995
996
                .arg(lease_file->getFilename())
                .arg(ex.what());
997
998
999
1000
1001
            // Reset the pointer to the file so as the backend doesn't
            // try to write leases to disk.
            lease_file.reset();
            do_lfc = false;
        }
1002
    }
1003
1004
    // Once the files have been rotated, or untouched if another LFC had
    // not finished, a new process is started.
1005
    if (do_lfc) {
1006
        lfc_setup_->execute();
1007
    }
1008
1009
}

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