memfile_lease_mgr.cc 36.4 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
224
        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_));
}


225
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
226
227
228
229
    : LeaseMgr(parameters),
      lfc_setup_(new LFCSetup(boost::bind(&Memfile_LeaseMgr::lfcCallback, this),
                              *getIOService()))
    {
230
231
232
233
234
    // 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()) {
235
236
            loadLeasesFromFiles<Lease4, CSVLeaseFile4>(file4, lease_file4_,
                                                       storage4_);
237
238
239
240
        }
    } else {
        std::string file6 = initLeaseFilePath(V6);
        if (!file6.empty()) {
241
242
            loadLeasesFromFiles<Lease6, CSVLeaseFile6>(file6, lease_file6_,
                                                       storage6_);
243
        }
244
    }
245
246
247
248
249

    // 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.
250
   if (!persistLeases(V4) && !persistLeases(V6)) {
251
        LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_NO_STORAGE);
252
253

    } else  {
254
        lfcSetup();
255
    }
256
257
258
}

Memfile_LeaseMgr::~Memfile_LeaseMgr() {
259
260
261
262
263
264
265
266
    if (lease_file4_) {
        lease_file4_->close();
        lease_file4_.reset();
    }
    if (lease_file6_) {
        lease_file6_->close();
        lease_file6_.reset();
    }
267
268
}

269
270
271
272
273
274
275
276
std::string
Memfile_LeaseMgr::getDBVersion() {
    std::stringstream tmp;
    tmp << "Memfile backend " << MAJOR_VERSION;
    tmp << "." << MINOR_VERSION;
    return (tmp.str());
}

277
278
bool
Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
279
280
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
281

282
283
284
285
    if (getLease4(lease->addr_)) {
        // there is a lease with specified address already
        return (false);
    }
286
287
288
289

    // 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.
290
    if (persistLeases(V4)) {
291
292
293
        lease_file4_->append(*lease);
    }

294
295
    storage4_.insert(lease);
    return (true);
296
297
}

298
299
bool
Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
300
301
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
302

303
    if (getLease6(lease->type_, lease->addr_)) {
304
305
306
        // there is a lease with specified address already
        return (false);
    }
307
308
309
310

    // 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.
311
    if (persistLeases(V6)) {
312
313
314
        lease_file6_->append(*lease);
    }

315
316
317
318
    storage6_.insert(lease);
    return (true);
}

319
320
Lease4Ptr
Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
321
322
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
323

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

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

        // Every Lease4 has a hardware address, so we can compare it
343
        if ( (*(*lease)->hwaddr_) == hwaddr) {
344
            collection.push_back((*lease));
345
346
        }
    }
347

348
    return (collection);
349
350
}

351
352
Lease4Ptr
Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
353
354
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
355
356
        .arg(hwaddr.toText());

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

368
    // Lease was found. Return it to the caller.
369
    return (Lease4Ptr(new Lease4(**lease)));
370
371
}

372
Lease4Collection
373
Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
374
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
375
              DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
376
    Lease4Collection collection;
377
378
    const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
    for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
379
380
381
382
        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
383
384
        if((*lease)->client_id_ && *(*lease)->client_id_ == client_id) {
            collection.push_back((*lease));
385
386
387
388
        }
    }

    return (collection);
389
}
390

391
392
Lease4Ptr
Memfile_LeaseMgr::getLease4(const ClientId& client_id,
393
394
395
396
397
398
399
                            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);

400
401
402
    // Get the index by client id, HW address and subnet id.
    const Lease4StorageClientIdHWAddressSubnetIdIndex& idx =
        storage4_.get<ClientIdHWAddressSubnetIdIndexTag>();
403
    // Try to get the lease using client id, hardware address and subnet id.
404
    Lease4StorageClientIdHWAddressSubnetIdIndex::const_iterator lease =
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        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 {
420
421
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
422
              .arg(client_id.toText());
423

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

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

453
Lease6Collection
454
Memfile_LeaseMgr::getLeases6(Lease::Type type,
455
                            const DUID& duid, uint32_t iaid) const {
456
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
457
458
459
460
              DHCPSRV_MEMFILE_GET_IAID_DUID)
        .arg(iaid)
        .arg(duid.toText())
        .arg(Lease::typeToText(type));
461

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

474
    return (collection);
475
476
}

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

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

    return (collection);
504
505
}

506
507
508
509
510
511
512
513
514
515
516
void
Memfile_LeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
                                    const size_t max_leases) const {
    // 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 =
517
        index.upper_bound(boost::make_tuple(false, time(NULL)));
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538

    // 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 {
    // 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 =
539
        index.upper_bound(boost::make_tuple(false, time(NULL)));
540
541
542
543
544
545
546
547
548
549

    // 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)));
    }
}

550
551
void
Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
552
553
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
554

555
556
557
558
559
    // 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_);
560
561
    if (lease_it == storage4_.end()) {
        isc_throw(NoSuchLease, "failed to update the lease with address "
562
                  << lease->addr_ << " - no such lease");
563
    }
564
565
566
567

    // 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.
568
    if (persistLeases(V4)) {
569
570
571
        lease_file4_->append(*lease);
    }

572
573
    // Use replace() to re-index leases.
    index.replace(lease_it, Lease4Ptr(new Lease4(*lease)));
574
575
}

576
577
void
Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
578
579
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
580

581
582
583
584
585
586
    // 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()) {
587
        isc_throw(NoSuchLease, "failed to update the lease with address "
588
                  << lease->addr_ << " - no such lease");
589
    }
590
591
592
593

    // 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.
594
    if (persistLeases(V6)) {
595
596
597
        lease_file6_->append(*lease);
    }

598
599
    // Use replace() to re-index leases.
    index.replace(lease_it, Lease6Ptr(new Lease6(*lease)));
600
601
}

602
603
bool
Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
604
605
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
606
    if (addr.isV4()) {
607
608
609
610
611
612
        // v4 lease
        Lease4Storage::iterator l = storage4_.find(addr);
        if (l == storage4_.end()) {
            // No such lease
            return (false);
        } else {
613
            if (persistLeases(V4)) {
614
615
616
617
618
619
620
621
                // 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);
            }
622
623
624
            storage4_.erase(l);
            return (true);
        }
625

626
    } else {
627
        // v6 lease
628
629
630
631
632
        Lease6Storage::iterator l = storage6_.find(addr);
        if (l == storage6_.end()) {
            // No such lease
            return (false);
        } else {
633
            if (persistLeases(V6)) {
634
635
636
637
638
639
640
641
642
                // 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);
            }

643
644
645
            storage6_.erase(l);
            return (true);
        }
646
647
648
    }
}

649
650
void
Memfile_LeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
651
652
653
654
655
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4)
        .arg(secs);
    deleteExpiredReclaimedLeases<Lease4StorageExpirationIndex, Lease4>(secs, V4, storage4_,
                                                                       lease_file4_);
656
657
658
659
}

void
Memfile_LeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
660
661
662
663
664
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6)
        .arg(secs);
    deleteExpiredReclaimedLeases<Lease6StorageExpirationIndex, Lease6>(secs, V6, storage6_,
                                                                       lease_file6_);
665
666
}

667
668
template<typename IndexType, typename LeaseType, typename StorageType,
         typename LeaseFileType>
669
670
void
Memfile_LeaseMgr::deleteExpiredReclaimedLeases(const uint32_t secs,
671
672
673
                                               const Universe& universe,
                                               StorageType& storage,
                                               LeaseFileType& lease_file) const {
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
    // 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.
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
    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.
723
724
725
726
727
        index.erase(lower_limit, upper_limit);
    }
}


728
729
std::string
Memfile_LeaseMgr::getDescription() const {
730
731
732
733
    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."));
}
734
735
736

void
Memfile_LeaseMgr::commit() {
737
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
738
739
740
741
}

void
Memfile_LeaseMgr::rollback() {
742
743
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
              DHCPSRV_MEMFILE_ROLLBACK);
744
}
745

746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
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;
763
764
765
    case FILE_PID:
        name += ".pid";
        break;
766
767
768
769
770
771
772
773
774
    default:
        // Do not append any suffix for the FILE_CURRENT.
        ;
    }

    return (name);
}


775
776
uint32_t
Memfile_LeaseMgr::getIOServiceExecInterval() const {
777
    return (static_cast<uint32_t>(lfc_setup_->getInterval() / 1000));
778
779
}

780
781
782
783
784
785
786
787
788
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());
}

789
790
791
792
793
794
795
796
797
std::string
Memfile_LeaseMgr::getLeaseFilePath(Universe u) const {
    if (u == V4) {
        return (lease_file4_ ? lease_file4_->getFilename() : "");
    }

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

798
799
bool
Memfile_LeaseMgr::persistLeases(Universe u) const {
800
801
802
803
804
805
806
807
    // 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_);
808
809
810
811
}

std::string
Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
812
    std::string persist_val;
813
    try {
814
        persist_val = getParameter("persist");
815
    } catch (const Exception&) {
816
817
        // If parameter persist hasn't been specified, we use a default value
        // 'yes'.
818
        persist_val = "true";
819
    }
820
    // If persist_val is 'false' we will not store leases to disk, so let's
821
    // return empty file name.
822
    if (persist_val == "false") {
823
        return ("");
824

825
826
827
    } else if (persist_val != "true") {
        isc_throw(isc::BadValue, "invalid value 'persist="
                  << persist_val << "'");
828
829
    }

830
831
    std::string lease_file;
    try {
832
        lease_file = getParameter("name");
833
    } catch (const Exception&) {
834
835
836
837
        lease_file = getDefaultLeaseFilePath(u);
    }
    return (lease_file);
}
838

839
840
841
842
843
844
845
846
847
848
849
850
851
852
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");
853
854
    }

855
    storage.clear();
856

857
858
859
860
861
    // 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);
862

863
864
865
866
867
868
869
    } 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);
870
        }
871

872
873
874
875
876
877
        lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
        if (lease_file->exists()) {
            LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
                                                   MAX_LEASE_ERRORS);
        }
    }
878

879
880
881
882
883
884
885
886
    // 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
887
                                           MAX_LEASE_ERRORS, false);
888
}
889

890

891
892
893
bool
Memfile_LeaseMgr::isLFCRunning() const {
    return (lfc_setup_->isRunning());
894
895
}

896
897
898
899
int
Memfile_LeaseMgr::getLFCExitStatus() const {
    return (lfc_setup_->getExitStatus());
}
900

901
902
void
Memfile_LeaseMgr::lfcCallback() {
903
    LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_START);
904
905
906

    // Check if we're in the v4 or v6 space and use the appropriate file.
    if (lease_file4_) {
907
        lfcExecute(lease_file4_);
908
909

    } else if (lease_file6_) {
910
911
912
913
914
915
916
917
918
        lfcExecute(lease_file6_);
    }
}

void
Memfile_LeaseMgr::lfcSetup() {
    std::string lfc_interval_str = "0";
    try {
        lfc_interval_str = getParameter("lfc-interval");
919
    } catch (const std::exception&) {
920
921
922
923
924
925
        // Ignore and default to 0.
    }

    uint32_t lfc_interval = 0;
    try {
        lfc_interval = boost::lexical_cast<uint32_t>(lfc_interval_str);
926
    } catch (boost::bad_lexical_cast&) {
927
928
929
        isc_throw(isc::BadValue, "invalid value of the lfc-interval "
                  << lfc_interval_str << " specified");
    }
930

931
932
    if (lfc_interval > 0) {
        lfc_setup_->setup(lfc_interval, lease_file4_, lease_file6_);
933
934
935
936
    }
}

template<typename LeaseFileType>
937
void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) {
938
    bool do_lfc = true;
939

940
941
    // Check the status of the LFC instance.
    // If the finish file exists or the copy of the lease file exists it
942
943
944
    // 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.
945
    CSVFile lease_file_finish(appendSuffix(lease_file->getFilename(), FILE_FINISH));
946
    CSVFile lease_file_copy(appendSuffix(lease_file->getFilename(), FILE_INPUT));
947
    if (!lease_file_finish.exists() && !lease_file_copy.exists()) {
948
        // Close the current file so as we can move it to the copy file.
949
        lease_file->close();
950
951
952
953
        // 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);
954
955
956
957
958
959
960

        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));
        }
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980

        // 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)
981
982
                .arg(lease_file->getFilename())
                .arg(ex.what());
983
984
985
986
987
            // Reset the pointer to the file so as the backend doesn't
            // try to write leases to disk.
            lease_file.reset();
            do_lfc = false;
        }
988
    }
989
990
    // Once the files have been rotated, or untouched if another LFC had
    // not finished, a new process is started.
991
    if (do_lfc) {
992
        lfc_setup_->execute();
993
    }
994
995
}

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