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

#include <config.h>
16

17
#include <asiolink/io_address.h>
Stephen Morris's avatar
Stephen Morris committed
18
#include <dhcp/duid.h>
19
#include <dhcpsrv/mysql_lease_mgr.h>
20

21
#include <boost/static_assert.hpp>
22
23
#include <mysql/mysqld_error.h>

24
#include <algorithm>
25
26
27
28
29
#include <iostream>
#include <iomanip>
#include <string>
#include <time.h>

30
using namespace isc;
31
using namespace isc::dhcp;
32
33
using namespace std;

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
/// @file
///
/// This file holds the implementation of the Lease Manager using MySQL.  The
/// implementation uses MySQL's C API, as it comes as standard with the MySQL
/// client libraries.
///
/// In general, each of the database access methods corresponds to one SQL
/// statement.  To avoid the overhead of parsing a statement every time it is
/// used, when the database is opened "prepared statements" are created -
/// essentially doing the SQL parsing up front.  Every time a method is used
/// to access data, the corresponding prepared statement is referenced. Each
/// prepared statement contains a set of placeholders for data, each
/// placeholder being for:
///
/// - data being added to the database (as in adding or updating a lease)
/// - data being retrieved from the database (as in getting lease information)
/// - selection criteria used to determine which records to update/retrieve.
///
/// All such data is associated with the prepared statment using an array of
/// MYSQL_BIND structures.  Each element in the array corresponds to one
/// parameter in the prepared statement - the first element in the array is
/// associated with the first parameter, the second element with the second
/// parameter etc.
///
/// Within this file, the setting up of the MYSQL_BIND arrays for data being
/// passed to and retrieved from the database is handled in the
/// isc::dhcp::MySqlLease4Exchange and isc::dhcp::MySqlLease6Exchange classes.
/// The classes also hold intermediate variables required for exchanging some
/// of the data.
///
/// With these exchange objects in place, many of the methods follow similar
/// logic:
/// - Set up the MYSQL_BIND array for data being transferred to/from the
///   database.  For data being transferred to the database, some of the
///   data is extracted from the lease to intermediate variables, whilst
///   in other cases the MYSQL_BIND arrays point to the data in the lease.
/// - Set up the MYSQL_BIND array for the data selection parameters.
/// - Bind these arrays to the prepared statement.
/// - Execute the statement.
/// - If there is output, copy the data from the bound variables to the output
///   lease object.

76
77
namespace {
///@{
78
79

/// @brief Maximum size of database fields
80
81
82
83
84
///
/// The following constants define buffer sizes for variable length database
/// fields.  The values should be greater than or equal to the length set in
/// the schema definition.
///
85
86
87
/// The exception is the length of any VARCHAR fields: buffers for these should
/// be set greater than or equal to the length of the field plus 1: this allows
/// for the insertion of a trailing null whatever data is returned.
88

89
90
91
92
93
94
95
96
97
98
99
100
101
102
/// @brief Maximum size of an IPv6 address represented as a text string.
///
/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;

/// @brief Maximum size of a hardware address.
const size_t HWADDR_MAX_LEN = 20;

/// @brief Number of columns in Lease4 table
const size_t LEASE4_COLUMNS = 6;

/// @brief Number of columns in Lease6 table
const size_t LEASE6_COLUMNS = 9;
103
104
105
106
107

/// @brief MySQL True/False constants
///
/// Declare typed values so as to avoid problems of data conversion.  These
/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
108
/// avoid any likely conflicts with variables in header files named TRUE or
109
/// FALSE.
110
111
112
113

const my_bool MLM_FALSE = 0;                ///< False value
const my_bool MLM_TRUE = 1;                 ///< True value

114
///@}
115
116
117

/// @brief MySQL Selection Statements
///
118
119
120
/// Each statement is associated with an index, which is used to reference the
/// associated prepared statement.

121
122
123
124
125
126
struct TaggedStatement {
    MySqlLeaseMgr::StatementIndex index;
    const char*                   text;
};

TaggedStatement tagged_statements[] = {
127
128
    {MySqlLeaseMgr::DELETE_LEASE4,
                    "DELETE FROM lease4 WHERE address = ?"},
129
130
    {MySqlLeaseMgr::DELETE_LEASE6,
                    "DELETE FROM lease6 WHERE address = ?"},
131
132
133
134
135
136
137
138
139
140
    {MySqlLeaseMgr::GET_LEASE4_ADDR,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE address = ?"},
    {MySqlLeaseMgr::GET_LEASE4_CLIENTID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE client_id = ?"},
141
142
143
144
145
    {MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE client_id = ? AND subnet_id = ?"},
146
147
148
149
150
    {MySqlLeaseMgr::GET_LEASE4_HWADDR,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE hwaddr = ?"},
151
152
153
154
155
    {MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
                    "SELECT address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id "
                            "FROM lease4 "
                            "WHERE hwaddr = ? AND subnet_id = ?"},
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    {MySqlLeaseMgr::GET_LEASE6_ADDR,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE address = ?"},
    {MySqlLeaseMgr::GET_LEASE6_DUID_IAID,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE duid = ? AND iaid = ?"},
    {MySqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID,
                    "SELECT address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len "
                            "FROM lease6 "
                            "WHERE duid = ? AND iaid = ? AND subnet_id = ?"},
    {MySqlLeaseMgr::GET_VERSION,
                    "SELECT version, minor FROM schema_version"},
176
177
178
179
    {MySqlLeaseMgr::INSERT_LEASE4,
                    "INSERT INTO lease4(address, hwaddr, client_id, "
                        "valid_lifetime, expire, subnet_id) "
                            "VALUES (?, ?, ?, ?, ?, ?)"},
180
181
182
183
184
    {MySqlLeaseMgr::INSERT_LEASE6,
                    "INSERT INTO lease6(address, duid, valid_lifetime, "
                        "expire, subnet_id, pref_lifetime, "
                        "lease_type, iaid, prefix_len) "
                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
185
186
187
188
189
    {MySqlLeaseMgr::UPDATE_LEASE4,
                    "UPDATE lease4 SET address = ?, hwaddr = ?, "
                        "client_id = ?, valid_lifetime = ?, expire = ?, "
                        "subnet_id = ? "
                            "WHERE address = ?"},
190
191
192
193
194
195
196
197
    {MySqlLeaseMgr::UPDATE_LEASE6,
                    "UPDATE lease6 SET address = ?, duid = ?, "
                        "valid_lifetime = ?, expire = ?, subnet_id = ?, "
                        "pref_lifetime = ?, lease_type = ?, iaid = ?, "
                        "prefix_len = ? "
                            "WHERE address = ?"},
    // End of list sentinel
    {MySqlLeaseMgr::NUM_STATEMENTS, NULL}
198
199
};

200
201
};  // Anonymous namespace

202
203
204
namespace isc {
namespace dhcp {

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
/// @brief Common MySQL and Lease Data Methods
///
/// The MySqlLease4Exchange and MySqlLease6Exchange classes provide the
/// functionaility to set up binding information between variables in the
/// program and data extracted from the database.  This class is the common
/// base to both of them, containing some common methods.

class MySqlLeaseExchange {
public:
    /// @brief Set error indicators
    ///
    /// Sets the error indicator for each of the MYSQL_BIND elements.  It points
    /// the "error" field within an element of the input array to the
    /// corresponding element of the passed error array.
    ///
    /// @param bind Array of BIND elements
    /// @param error Array of error elements.  If there is an error in getting
    ///        data associated with one of the "bind" elements, the
    ///        corresponding element in the error array is set to MLM_TRUE.
    /// @param count Size of each of the arrays.
    void setErrorIndicators(MYSQL_BIND* bind, my_bool* error, size_t count) {
        for (size_t i = 0; i < count; ++i) {
            error[i] = MLM_FALSE;
            bind[i].error = reinterpret_cast<char*>(&error[i]);
        }
    }

    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @param error Array of error elements.  An element is set to MLM_TRUE
    ///        if the corresponding column in the database is the source of
    ///        the error.
    /// @param names Array of column names, the same size as the error array.
    /// @param count Size of each of the arrays.
    std::string getColumnsInError(my_bool* error, std::string* names,
                                  size_t count) {
        std::string result = "";

        // Accumulate list of column names
        for (size_t i = 0; i < count; ++i) {
            if (error[i] == MLM_TRUE) {
                if (!result.empty()) {
                    result += ", ";
                }
                result += names[i];
            }
        }

        if (result.empty()) {
            result = "(None)";
        }

        return (result);
    }
};


268
269
270
271
272
/// @brief Exchange MySQL and Lease4 Data
///
/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
/// describe the parameters in the prepared statements.  Where information is
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
273
/// structure is identical.  This class handles the creation of that array.
274
275
///
/// Owing to the MySQL API, the process requires some intermediate variables
276
/// to hold things like data length etc.  This object holds those variables.
277
278
279
280
///
/// @note There are no unit tests for this class.  It is tested indirectly
/// in all MySqlLeaseMgr::xxx4() calls where it is used.

281
class MySqlLease4Exchange : public MySqlLeaseExchange {
282
public:
283
284
285
    /// @brief Set number of columns in this lease structure
    static const size_t LEASE_COLUMNS = LEASE4_COLUMNS;

286
287
    /// @brief Constructor
    ///
288
    /// The initialization of the variables here is only to satisfy cppcheck -
289
290
    /// all variables are initialized/set in the methods before they are used.
    MySqlLease4Exchange() : addr4_(0), hwaddr_length_(0), client_id_length_(0) {
291
292
        memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
        memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
293
294
295
296
297
298
299
300
301
302
        std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
 
        // Set the column names (for error messages)
        columns_[0] = "address";
        columns_[1] = "hwaddr";
        columns_[2] = "client_id";
        columns_[3] = "valid_lifetime";
        columns_[4] = "expire";
        columns_[5] = "subnet_id";
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
303
304
305
306
    }

    /// @brief Create MYSQL_BIND objects for Lease4 Pointer
    ///
307
308
    /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
    /// the database.
309
    ///
310
311
    /// @param lease Lease object to be added to the database.  None of the
    ///        fields in the lease are modified - the lease data is only read.
312
313
314
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const Lease4Ptr& lease) {
315

316
317
318
        // Store lease object to ensure it remains valid.
        lease_ = lease;

319
        // Initialize prior to constructing the array of MYSQL_BIND structures.
320
321
        memset(bind_, 0, sizeof(bind_));

322
323
324
        // Set up the structures for the various components of the lease4
        // structure.

325
        // Address: uint32_t
326
        // The address in the Lease structure is an IOAddress object.  Convert
327
        // this to an integer for storage.
328
329
330
        addr4_ = static_cast<uint32_t>(lease_->addr_);
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
331
        bind_[0].is_unsigned = MLM_TRUE;
332

333
        // hwaddr: varbinary(128)
334
335
336
337
338
339
340
341
        // For speed, we avoid copying the data into temporary storage and
        // instead extract it from the lease structure directly.
        hwaddr_length_ = lease_->hwaddr_.size();
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(&(lease_->hwaddr_[0]));
        bind_[1].buffer_length = hwaddr_length_;
        bind_[1].length = &hwaddr_length_;

342
        // client_id: varbinary(128)
343
344
345
346
347
348
349
350
351
352
        client_id_ = lease_->client_id_->getClientId();
        client_id_length_ = client_id_.size();
        bind_[2].buffer_type = MYSQL_TYPE_BLOB;
        bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
        bind_[2].buffer_length = client_id_length_;
        bind_[2].length = &client_id_length_;

        // valid lifetime: unsigned int
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
353
        bind_[3].is_unsigned = MLM_TRUE;
354
355
356
357

        // expire: timestamp
        // The lease structure holds the client last transmission time (cltt_)
        // For convenience for external tools, this is converted to lease
358
        // expiry time (expire).  The relationship is given by:
359
360
361
        //
        // expire = cltt_ + valid_lft_
        //
362
        // @todo Handle overflows - a large enough valid_lft_ could cause
363
        //       an overflow on a 32-bit system.
364
365
366
367
368
369
370
371
372
373
        MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
                                             expire_);
        bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[4].buffer = reinterpret_cast<char*>(&expire_);
        bind_[4].buffer_length = sizeof(expire_);

        // subnet_id: unsigned int
        // Can use lease_->subnet_id_ directly as it is of type uint32_t.
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
374
        bind_[5].is_unsigned = MLM_TRUE;
375

376
377
378
379
380
381
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);

382
383
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
384
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
385
386
387
388
389
    }

    /// @brief Create BIND array to receive data
    ///
    /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
390
    /// After data is successfully received, getLeaseData() can be used to copy
391
392
393
394
    /// it to a Lease6 object.
    ///
    std::vector<MYSQL_BIND> createBindForReceive() {

395
        // Initialize MYSQL_BIND array.
396
397
398
399
400
        memset(bind_, 0, sizeof(bind_));

        // address:  uint32_t
        bind_[0].buffer_type = MYSQL_TYPE_LONG;
        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
401
        bind_[0].is_unsigned = MLM_TRUE;
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419

        // hwaddr: varbinary(20)
        hwaddr_length_ = sizeof(hwaddr_buffer_);
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
        bind_[1].buffer_length = hwaddr_length_;
        bind_[1].length = &hwaddr_length_;

        // client_id: varbinary(128)
        client_id_length_ = sizeof(client_id_buffer_);
        bind_[2].buffer_type = MYSQL_TYPE_BLOB;
        bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
        bind_[2].buffer_length = client_id_length_;
        bind_[2].length = &client_id_length_;

        // lease_time: unsigned int
        bind_[3].buffer_type = MYSQL_TYPE_LONG;
        bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
420
        bind_[3].is_unsigned = MLM_TRUE;
421
422
423
424
425
426
427
428
429

        // expire: timestamp
        bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[4].buffer = reinterpret_cast<char*>(&expire_);
        bind_[4].buffer_length = sizeof(expire_);

        // subnet_id: unsigned int
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
430
        bind_[5].is_unsigned = MLM_TRUE;
431

432
433
434
435
436
437
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);

438
439
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
440
        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
441
442
443
444
445
    }

    /// @brief Copy Received Data into Lease6 Object
    ///
    /// Called after the MYSQL_BIND array created by createBindForReceive()
446
    /// has been used, this copies data from the internal member variables
447
    /// into a Lease4 objec.
448
    ///
449
    /// @return Lease4Ptr Pointer to a Lease6 object holding the relevant
450
451
    ///         data.
    Lease4Ptr getLeaseData() {
452
453
        // Convert times received from the database to times for the lease
        // structure
454
455
456
457
458
459
460
461
        time_t cltt = 0;
        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);

        return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
                                     client_id_buffer_, client_id_length_,
                                     valid_lifetime_, cltt, subnet_id_)));
    }

462
463
464
465
466
467
468
469
470
471
472
473
474
475
    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @return Comma-separated list of columns in error, or the string
    ///         "(None)".
    std::string getErrorColumns() {
        return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
    }

476
private:
477

478
    // Note: All array lengths are equal to the corresponding variable in the
479
480
    //       schema.
    // Note: Arrays are declared fixed length for speed of creation
481
    uint32_t        addr4_;             ///< IPv4 address
482
483
484
    MYSQL_BIND      bind_[LEASE_COLUMNS]; ///< Bind array
    std::string     columns_[LEASE_COLUMNS];///< Column names
    my_bool         error_[LEASE_COLUMNS];  ///< Error array
485
    std::vector<uint8_t> hwaddr_;       ///< Hardware address
486
487
    uint8_t         hwaddr_buffer_[HWADDR_MAX_LEN];
                                        ///< Hardware address buffer
488
489
    unsigned long   hwaddr_length_;     ///< Hardware address length
    std::vector<uint8_t> client_id_;    ///< Client identification
Stephen Morris's avatar
Stephen Morris committed
490
    uint8_t         client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
491
                                        ///< Client ID buffer
492
493
494
495
    unsigned long   client_id_length_;  ///< Client ID address length
    MYSQL_TIME      expire_;            ///< Lease expiry time
    Lease4Ptr       lease_;             ///< Pointer to lease object
    uint32_t        subnet_id_;         ///< Subnet identification
496
    uint32_t        valid_lifetime_;    ///< Lease time
497
498
499
500
};



501
/// @brief Exchange MySQL and Lease6 Data
502
///
503
504
/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
/// describe the parameters in the prepared statements.  Where information is
505
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
506
/// structure is identical.  This class handles the creation of that array.
507
///
508
/// Owing to the MySQL API, the process requires some intermediate variables
509
/// to hold things like data length etc.  This object holds those variables.
510
511
512
///
/// @note There are no unit tests for this class.  It is tested indirectly
/// in all MySqlLeaseMgr::xxx6() calls where it is used.
513

514
515
516
517
class MySqlLease6Exchange : public MySqlLeaseExchange {
    /// @brief Set number of columns in this lease structure
    static const size_t LEASE_COLUMNS = LEASE6_COLUMNS;

518
519
public:
    /// @brief Constructor
520
    ///
521
522
523
    /// The initialization of the variables here is nonly to satisfy cppcheck -
    /// all variables are initialized/set in the methods before they are used.
    MySqlLease6Exchange() : addr6_length_(0), duid_length_(0) {
524
525
        memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
        memset(duid_buffer_, 0, sizeof(duid_buffer_));
526
527
528
529
530
531
532
533
534
535
536
537
538
        std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
 
        // Set the column names (for error messages)
        columns_[0] = "address";
        columns_[1] = "duid";
        columns_[2] = "valid_lifetime";
        columns_[3] = "expire";
        columns_[4] = "subnet_id";
        columns_[5] = "pref_lifetime";
        columns_[6] = "lease_type";
        columns_[7] = "iaid";
        columns_[8] = "prefix_len";
        BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
539
540
541
542
    }

    /// @brief Create MYSQL_BIND objects for Lease6 Pointer
    ///
543
544
    /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
    /// the database.
545
    ///
546
547
548
549
    /// @param lease Lease object to be added to the database.
    ///
    /// @return Vector of MySQL BIND objects representing the data to be added.
    std::vector<MYSQL_BIND> createBindForSend(const Lease6Ptr& lease) {
550
551
552
        // Store lease object to ensure it remains valid.
        lease_ = lease;

553
554
        // Ensure bind_ array clear for constructing the MYSQL_BIND structures
        // for this lease.
555
556
        memset(bind_, 0, sizeof(bind_));

557
        // address: varchar(39)
558
559
560
        addr6_ = lease_->addr_.toText();
        addr6_length_ = addr6_.size();

561
562
563
        // In the following statement, the string is being read.  However, the
        // MySQL C interface does not use "const", so the "buffer" element
        // is declared as "char*" instead of "const char*".  To resolve this,
564
        // the "const" is discarded.  (Note that the address of addr6_.c_str()
565
        // is guaranteed to be valid until the next non-const operation on
566
567
        // addr6_.)
        //
568
569
570
        // The const_cast could be avoided by copying the string to a writeable
        // buffer and storing the address of that in the "buffer" element.
        // However, this introduces a copy operation (with additional overhead)
571
        // purely to get round the structures introduced by design of the
572
573
574
        // MySQL interface (which uses the area pointed to by "buffer" as input
        // when specifying query parameters and as output when retrieving data).
        // For that reason, "const_cast" has been used.
575
576
577
578
579
        bind_[0].buffer_type = MYSQL_TYPE_STRING;
        bind_[0].buffer = const_cast<char*>(addr6_.c_str());
        bind_[0].buffer_length = addr6_length_;
        bind_[0].length = &addr6_length_;

580
581
582
        // duid: varchar(128)
        duid_ = lease_->duid_->getDuid();
        duid_length_ = duid_.size();
583

584
585
586
587
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(&(duid_[0]));
        bind_[1].buffer_length = duid_length_;
        bind_[1].length = &duid_length_;
588

589
        // valid lifetime: unsigned int
590
        bind_[2].buffer_type = MYSQL_TYPE_LONG;
591
        bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
592
        bind_[2].is_unsigned = MLM_TRUE;
593
594

        // expire: timestamp
595
596
597
598
599
        // The lease structure holds the client last transmission time (cltt_)
        // For convenience for external tools, this is converted to lease
        /// expiry time (expire).  The relationship is given by:
        //
        // expire = cltt_ + valid_lft_
600
        //
601
        // @todo Handle overflows
602
603
        MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
                                             expire_);
604
605
606
        bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[3].buffer = reinterpret_cast<char*>(&expire_);
        bind_[3].buffer_length = sizeof(expire_);
607
608
609

        // subnet_id: unsigned int
        // Can use lease_->subnet_id_ directly as it is of type uint32_t.
610
611
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
612
        bind_[4].is_unsigned = MLM_TRUE;
613
614
615

        // pref_lifetime: unsigned int
        // Can use lease_->preferred_lft_ directly as it is of type uint32_t.
616
617
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
        bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
618
        bind_[5].is_unsigned = MLM_TRUE;
619
620

        // lease_type: tinyint
621
        // Must convert to uint8_t as lease_->type_ is a LeaseType variable.
622
        lease_type_ = lease_->type_;
623
624
        bind_[6].buffer_type = MYSQL_TYPE_TINY;
        bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
625
        bind_[6].is_unsigned = MLM_TRUE;
626
627
628

        // iaid: unsigned int
        // Can use lease_->iaid_ directly as it is of type uint32_t.
629
630
        bind_[7].buffer_type = MYSQL_TYPE_LONG;
        bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
631
        bind_[7].is_unsigned = MLM_TRUE;
632
633
634

        // prefix_len: unsigned tinyint
        // Can use lease_->prefixlen_ directly as it is uint32_t.
635
636
        bind_[8].buffer_type = MYSQL_TYPE_TINY;
        bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
637
        bind_[8].is_unsigned = MLM_TRUE;
638

639
640
641
642
643
644
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);

645
646
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
647
        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
648
649
    }

650
651
652
653
654
655
    /// @brief Create BIND array to receive data
    ///
    /// Creates a MYSQL_BIND array to receive Lease6 data from the database.
    /// After data is successfully received, getLeaseData() is used to copy
    /// it to a Lease6 object.
    ///
656
657
    /// @return Vector of MySQL BIND objects passed to the MySQL data retrieval
    ///         functions.
658
    std::vector<MYSQL_BIND> createBindForReceive() {
659

660
        // Initialize MYSQL_BIND array.
661
662
        memset(bind_, 0, sizeof(bind_));

663
        // address:  varchar(39)
664
        // A Lease6_ address has a maximum of 39 characters.  The array is
665
666
        // one byte longer than this to guarantee that we can always null
        // terminate it whatever is returned.
667
668
669
670
671
672
673
        addr6_length_ = sizeof(addr6_buffer_) - 1;
        bind_[0].buffer_type = MYSQL_TYPE_STRING;
        bind_[0].buffer = addr6_buffer_;
        bind_[0].buffer_length = addr6_length_;
        bind_[0].length = &addr6_length_;

        // client_id: varbinary(128)
674
        duid_length_ = sizeof(duid_buffer_);
675
676
677
678
        bind_[1].buffer_type = MYSQL_TYPE_BLOB;
        bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
        bind_[1].buffer_length = duid_length_;
        bind_[1].length = &duid_length_;
679
680

        // lease_time: unsigned int
681
682
        bind_[2].buffer_type = MYSQL_TYPE_LONG;
        bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
683
        bind_[2].is_unsigned = MLM_TRUE;
684
685

        // expire: timestamp
686
687
688
        bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
        bind_[3].buffer = reinterpret_cast<char*>(&expire_);
        bind_[3].buffer_length = sizeof(expire_);
689
690

        // subnet_id: unsigned int
691
692
        bind_[4].buffer_type = MYSQL_TYPE_LONG;
        bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
693
        bind_[4].is_unsigned = MLM_TRUE;
694
695

        // pref_lifetime: unsigned int
696
        bind_[5].buffer_type = MYSQL_TYPE_LONG;
697
        bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
698
        bind_[5].is_unsigned = MLM_TRUE;
699

700
701
702
        // lease_type: tinyint
        bind_[6].buffer_type = MYSQL_TYPE_TINY;
        bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
703
        bind_[6].is_unsigned = MLM_TRUE;
704

705
706
707
        // iaid: unsigned int
        bind_[7].buffer_type = MYSQL_TYPE_LONG;
        bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
708
        bind_[7].is_unsigned = MLM_TRUE;
709

710
        // prefix_len: unsigned tinyint
711
712
        bind_[8].buffer_type = MYSQL_TYPE_TINY;
        bind_[8].buffer = reinterpret_cast<char*>(&prefixlen_);
713
        bind_[8].is_unsigned = MLM_TRUE;
714

715
716
717
718
719
720
        // Add the error flags
        setErrorIndicators(bind_, error_, LEASE_COLUMNS);

        // .. and check that we have the numbers correct at compile time.
        BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);

721
722
        // Add the data to the vector.  Note the end element is one after the
        // end of the array.
723
        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
724
725
726
727
728
    }

    /// @brief Copy Received Data into Lease6 Object
    ///
    /// Called after the MYSQL_BIND array created by createBindForReceive()
729
    /// has been used, this copies data from the internal member variables
730
731
732
733
734
    /// into a Lease6 object.
    ///
    /// @return Lease6Ptr Pointer to a Lease6 object holding the relevant
    ///         data.
    ///
735
    /// @throw isc::BadValue Unable to convert Lease Type value in database
736
    Lease6Ptr getLeaseData() {
737
738
        // The address buffer is declared larger than the buffer size passed
        // to the access function so that we can always append a null byte.
739
        // Create the IOAddress object corresponding to the received data.
740
741
        addr6_buffer_[addr6_length_] = '\0';
        std::string address = addr6_buffer_;
742
        isc::asiolink::IOAddress addr(address);
743

744
        // Set the lease type in a variable of the appropriate data type, which
745
        // has been initialized with an arbitrary (but valid) value.
746
        Lease6::LeaseType type = Lease6::LEASE_IA_NA;
747
748
        switch (lease_type_) {
            case Lease6::LEASE_IA_NA:
749
                type = Lease6::LEASE_IA_NA;
750
751
752
                break;

            case Lease6::LEASE_IA_TA:
753
                type = Lease6::LEASE_IA_TA;
754
755
756
                break;

            case Lease6::LEASE_IA_PD:
757
                type = Lease6::LEASE_IA_PD;
758
759
760
                break;

            default:
761
762
                isc_throw(BadValue, "invalid lease type returned (" <<
                          lease_type_ << ") for lease with address " <<
763
                          address << ". Only 0, 1, or 2 are allowed.");
764
        }
765
766
767
768
769
770
771
772
773
774
775
776

        // Set up DUID,
        DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));

        // Create the lease and set the cltt (after converting from the
        // expire time retrieved from the database).
        Lease6Ptr result(new Lease6(type, addr, duid_ptr, iaid_,
                                    pref_lifetime_, valid_lifetime_, 0, 0,
                                    subnet_id_, prefixlen_));
        time_t cltt = 0;
        MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
        result->cltt_ = cltt;
777
778
779
780

        return (result);
    }

781
782
783
784
785
786
787
788
789
790
791
792
793
794
    /// @brief Return columns in error
    ///
    /// If an error is returned from a fetch (in particular, a truncated
    /// status), this method can be called to get the names of the fields in
    /// error.  It returns a string comprising the names of the fields
    /// separated by commas.  In the case of there being no error indicators
    /// set, it returns the string "(None)".
    ///
    /// @return Comma-separated list of columns in error, or the string
    ///         "(None)".
    std::string getErrorColumns() {
        return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
    }

795
private:
796
    // Note: All array lengths are equal to the corresponding variable in the
797
    // schema.
798
    // Note: arrays are declared fixed length for speed of creation
799
    std::string     addr6_;             ///< String form of address
800
    char            addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];  ///< Character 
801
                                        ///< array form of V6 address
802
    unsigned long   addr6_length_;      ///< Length of the address
803
804
    MYSQL_BIND      bind_[LEASE_COLUMNS]; ///< Bind array
    std::string     columns_[LEASE_COLUMNS];///< Column names
805
    std::vector<uint8_t> duid_;         ///< Client identification
Stephen Morris's avatar
Stephen Morris committed
806
    uint8_t         duid_buffer_[DUID::MAX_DUID_LEN]; ///< Buffer form of DUID
807
    unsigned long   duid_length_;       ///< Length of the DUID
808
    my_bool         error_[LEASE_COLUMNS]; ///< Error indicators
809
    MYSQL_TIME      expire_;            ///< Lease expiry time
810
    uint32_t        iaid_;              ///< Identity association ID
811
812
    Lease6Ptr       lease_;             ///< Pointer to lease object
    uint8_t         lease_type_;        ///< Lease type
813
814
815
    uint8_t         prefixlen_;         ///< Prefix length
    uint32_t        pref_lifetime_;     ///< Preferred lifetime
    uint32_t        subnet_id_;         ///< Subnet identification
816
    uint32_t        valid_lifetime_;    ///< Lease time
817
818
819
};


820
821
822
823
/// @brief Fetch and Release MySQL Results
///
/// When a MySQL statement is exected, to fetch the results the function
/// mysql_stmt_fetch() must be called.  As well as getting data, this
824
825
826
/// allocates internal state.  Subsequent calls to mysql_stmt_fetch can be
/// made, but when all the data is retrieved, mysql_stmt_free_result must be
/// called to free up the resources allocated.
827
///
828
829
/// Created prior to the first fetch, this class's destructor calls
/// mysql_stmt_free_result, so eliminating the need for an explicit release
830
/// in the method calling mysql_stmt_free_result.  In this way, it guarantees
831
832
/// that the resources are released even if the MySqlLeaseMgr method concerned
/// exits via an exception.
833

834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
class MySqlFreeResult {
public:

    /// @brief Constructor
    ///
    /// Store the pointer to the statement for which data is being fetched.
    ///
    /// Note that according to the MySQL documentation, mysql_stmt_free_result
    /// only releases resources if a cursor has been allocated for the
    /// statement.  This implies that it is a no-op if none have been.  Either
    /// way, any error from mysql_stmt_free_result is ignored. (Generating
    /// an exception is not much help, as it will only confuse things if the
    /// method calling mysql_stmt_fetch is exiting via an exception.)
    MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
    {}

    /// @brief Destructor
    ///
    /// Frees up fetch context if a fetch has been successfully executed.
    ~MySqlFreeResult() {
        (void) mysql_stmt_free_result(statement_);
    }

private:
    MYSQL_STMT*     statement_;     ///< Statement for which results are freed
};
860
861


862
// MySqlLeaseMgr Constructor and Destructor
863

864
865
866
867
868
869
870
871
872
MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters) 
    : LeaseMgr(parameters), mysql_(NULL) {

    // Allocate context for MySQL - it is destroyed in the destructor.
    mysql_ = mysql_init(NULL);
    if (mysql_ == NULL) {
        isc_throw(DbOpenError, "unable to initialize MySQL");
    }

873
    // Open the database.
874
875
    openDatabase();

876
877
878
    // Enable autocommit.  To avoid a flush to disk on every commit, the global
    // parameter innodb_flush_log_at_trx_commit should be set to 2.  This will
    // cause the changes to be written to the log, but flushed to disk in the
879
880
    // background every second.  Setting the parameter to that value will speed
    // up the system, but at the risk of losing data if the system crashes.
881
    my_bool result = mysql_autocommit(mysql_, 1);
882
883
884
885
886
887
    if (result != 0) {
        isc_throw(DbOperationError, mysql_error(mysql_));
    }

    // Prepare all statements likely to be used.
    prepareStatements();
888

889
    // Create the exchange objects for use in exchanging data between the
890
    // program and the database.
891
    exchange4_.reset(new MySqlLease4Exchange());
892
    exchange6_.reset(new MySqlLease6Exchange());
893
894
}

895

896
897
MySqlLeaseMgr::~MySqlLeaseMgr() {
    // Free up the prepared statements, ignoring errors. (What would we do
898
899
    // about them? We're destroying this object and are not really concerned
    // with errors on a database connection that is about to go away.)
900
901
902
903
904
905
906
907
908
909
910
911
    for (int i = 0; i < statements_.size(); ++i) {
        if (statements_[i] != NULL) {
            (void) mysql_stmt_close(statements_[i]);
            statements_[i] = NULL;
        }
    }

    // Close the database
    mysql_close(mysql_);
    mysql_ = NULL;
}

912

913
914
915
916
// Time conversion methods.
//
// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
// from the current timezone to UTC for storage, and from UTC to the current
917
918
919
920
921
922
// timezone for retrieval.
//
// This causes no problems providing that:
// a) cltt is given in local time
// b) We let the system take care of timezone conversion when converting
//    from a time read from the database into a local time.
923
924

void
925
926
MySqlLeaseMgr::convertToDatabaseTime(time_t cltt, uint32_t valid_lifetime,
                                    MYSQL_TIME& expire) {
927

928
    // Calculate expiry time.
929
    // @TODO: handle overflows
930
931
932
    time_t expire_time = cltt + valid_lifetime;

    // Convert to broken-out time
933
934
935
936
937
938
939
940
941
942
    struct tm expire_tm;
    (void) localtime_r(&expire_time, &expire_tm);

    // Place in output expire structure.
    expire.year = expire_tm.tm_year + 1900;
    expire.month = expire_tm.tm_mon + 1;     // Note different base
    expire.day = expire_tm.tm_mday;
    expire.hour = expire_tm.tm_hour;
    expire.minute = expire_tm.tm_min;
    expire.second = expire_tm.tm_sec;
943
944
    expire.second_part = 0;                  // No fractional seconds
    expire.neg = my_bool(0);                 // Not negative
945
946
947
}

void
948
MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
949
                                       uint32_t valid_lifetime, time_t& cltt) {
950
951
952

    // Copy across fields from MYSQL_TIME structure.
    struct tm expire_tm;
953
    memset(&expire_tm, 0, sizeof(expire_tm));
954
955
956
957
958
959
960

    expire_tm.tm_year = expire.year - 1900;
    expire_tm.tm_mon = expire.month - 1;
    expire_tm.tm_mday = expire.day;
    expire_tm.tm_hour = expire.hour;
    expire_tm.tm_min = expire.minute;
    expire_tm.tm_sec = expire.second;
961
    expire_tm.tm_isdst = -1;    // Let the system work out about DST 
962
963

    // Convert to local time
964
    cltt = mktime(&expire_tm) - valid_lifetime;
965
966
}

967

968

969
// Open the database using the parameters passed to the constructor.
970

971
972
973
974
void
MySqlLeaseMgr::openDatabase() {

    // Set up the values of the parameters
975
    const char* host = "localhost";
976
977
978
979
980
    string shost;
    try {
        shost = getParameter("host");
        host = shost.c_str();
    } catch (...) {
981
        // No host.  Fine, we'll use "localhost"
982
983
984
985
986
987
988
989
    }

    const char* user = NULL;
    string suser;
    try {
        suser = getParameter("user");
        user = suser.c_str();
    } catch (...) {
990
        // No user.  Fine, we'll use NULL
991
992
993
994
995
996
997
998
    }

    const char* password = NULL;
    string spassword;
    try {
        spassword = getParameter("password");
        password = spassword.c_str();
    } catch (...) {
999
        // No password.  Fine, we'll use NULL
1000
    }