Browse Source

[2342] Now have addLease and getLease6 methods apparently working

Stephen Morris 12 years ago
parent
commit
93ffcbb3ec

+ 46 - 9
src/lib/dhcp/lease_mgr.h

@@ -160,6 +160,11 @@ struct Lease4 {
     std::string comments_;
 
     /// @todo: Add DHCPv4 failover related fields here
+
+    /// @brief Constructor
+    ///
+    /// Initialize fields that don't have a default constructor.
+    Lease4() : addr_(0) {}
 };
 
 /// @brief Pointer to a Lease4 structure.
@@ -271,6 +276,11 @@ struct Lease6 {
     std::string comments_;
 
     /// @todo: Add DHCPv6 failover related fields here
+
+    /// @brief Constructor
+    ///
+    /// Initialize fields that don't have a default constructor.
+    Lease6() : addr_(0) {}
 };
 
 /// @brief Pointer to a Lease6 structure.
@@ -309,12 +319,22 @@ public:
     /// @brief Adds an IPv4 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease4Ptr lease) = 0;
+    ///
+    /// @result true if the lease was added, false if not (because a lease
+    ///         with the same address was already there).
+    ///
+    /// @exception DbOperationError Database function failed
+    virtual bool addLease(const Lease4Ptr& lease) = 0;
 
     /// @brief Adds an IPv6 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease6Ptr lease) = 0;
+    ///
+    /// @result true if the lease was added, false if not (because a lease
+    ///         with the same address was already there).
+    ///
+    /// @exception DbOperationError Database function failed
+    virtual bool addLease(const Lease6Ptr& lease) = 0;
 
     /// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
     ///
@@ -326,7 +346,7 @@ public:
     /// @param subnet_id ID of the subnet the lease must belong to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
                                 SubnetID subnet_id) const = 0;
 
     /// @brief Returns an IPv4 lease for specified IPv4 address
@@ -342,7 +362,7 @@ public:
     /// @param subnet_id ID of the subnet the lease must belong to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const = 0;
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const = 0;
 
     /// @brief Returns existing IPv4 leases for specified hardware address.
     ///
@@ -402,7 +422,7 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const = 0;
+    virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const = 0;
 
     /// @brief Returns existing IPv6 leases for a given DUID+IA combination
     ///
@@ -433,28 +453,28 @@ public:
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease4(Lease4Ptr lease4) = 0;
+    virtual void updateLease4(const Lease4Ptr& lease4) = 0;
 
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease6(Lease6Ptr lease6) = 0;
+    virtual void updateLease6(const Lease6Ptr& lease6) = 0;
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    virtual bool deleteLease4(uint32_t addr) = 0;
+    virtual bool deleteLease4(const isc::asiolink::IOAddress& addr) = 0;
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    virtual bool deleteLease6(isc::asiolink::IOAddress addr) = 0;
+    virtual bool deleteLease6(const isc::asiolink::IOAddress& addr) = 0;
 
     /// @brief Returns backend name.
     ///
@@ -482,10 +502,27 @@ public:
     /// Also if B>C, some database upgrade procedure may be triggered
     virtual std::pair<uint32_t, uint32_t> getVersion() const = 0;
 
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @exception DbOperationError if the commit failed.
+    virtual void commit() = 0;
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @exception DbOperationError if the rollback failed.
+    virtual void rollback() = 0;
+
     /// @todo: Add host management here
     /// As host reservation is outside of scope for 2012, support for hosts
     /// is currently postponed.
 
+
 protected:
     /// @brief returns value of the parameter
     std::string getParameter(const std::string& name) const;

+ 329 - 17
src/lib/dhcp/mysql_lease_mgr.cc

@@ -16,14 +16,64 @@
 #include <iomanip>
 #include <string>
 #include <config.h>
-
+#include <time.h>
 #include <dhcp/mysql_lease_mgr.h>
+#include <asiolink/io_address.h>
 
 using namespace std;
 
 namespace isc {
 namespace dhcp {
 
+// 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
+// timezone for retrieval.  This means that the external interface - cltt -
+// must be local time.
+
+void
+MySqlLeaseMgr::convertFromLeaseTime(time_t cltt, uint32_t valid_lft,
+                                    MYSQL_TIME& expire, uint32_t& lease_time) {
+
+    // Calculate expiry time and convert to various date/time fields.
+    time_t expire_time = cltt + valid_lft;
+    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;
+    expire.second_part = 0;                    // No fractional seconds
+    expire.neg = static_cast<my_bool>(0);      // Not negative
+
+    // Set the lease time.
+    lease_time = valid_lft;
+}
+
+void
+MySqlLeaseMgr::convertToLeaseTime(const MYSQL_TIME& expire, uint32_t lease_time,
+                                  time_t& cltt, uint32_t& valid_lft) {
+    valid_lft = lease_time;
+
+    // Copy across fields from MYSQL_TIME structure.
+    struct tm expire_tm;
+
+    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;
+
+    // Convert to local time
+    cltt = mktime(&expire_tm) - valid_lft;
+}
+
 void
 MySqlLeaseMgr::openDatabase() {
 
@@ -111,8 +161,18 @@ MySqlLeaseMgr::prepareStatements() {
     raw_statements_.resize(NUM_STATEMENTS, std::string(""));
 
     // Now allocate the statements
-    prepareStatement(SELECT_VERSION,
+    prepareStatement(GET_LEASE6,
+                     "SELECT hwaddr, client_id, "
+                         "lease_time, expire, subnet_id, pref_lifetime, "
+                         "lease_type, iaid, prefix_len "
+                         "FROM lease6 WHERE address = ?");
+    prepareStatement(GET_VERSION,
                      "SELECT version, minor FROM schema_version");
+    prepareStatement(INSERT_LEASE6,
+                     "INSERT INTO lease6(address, hwaddr, client_id, "
+                         "lease_time, expire, subnet_id, pref_lifetime, "
+                         "lease_type, iaid, prefix_len) "
+                         "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
 }
 
 
@@ -152,23 +212,122 @@ MySqlLeaseMgr::~MySqlLeaseMgr() {
 }
 
 bool
-MySqlLeaseMgr::addLease(Lease4Ptr /* lease */) {
+MySqlLeaseMgr::addLease(const Lease4Ptr& /* lease */) {
+
     return (false);
 }
 
 bool
-MySqlLeaseMgr::addLease(Lease6Ptr /* lease */) {
-    return (false);
+MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+    my_bool MLM_FALSE = 0;
+    MYSQL_BIND bind[10];
+    memset(bind, 0, sizeof(bind));
+
+    // address: varchar(40)
+    std::string addr6 = lease->addr_.toText();
+    unsigned long addr6_length = addr6.size();
+
+    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;
+    bind[0].is_null = &MLM_FALSE;
+
+    // hwaddr: binary(20)
+    unsigned long 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;
+    bind[1].is_null = &MLM_FALSE;
+
+    // client_id: varchar(128)
+    vector<uint8_t> clientid = lease->duid_->getDuid();
+    unsigned long clientid_length = clientid.size();
+
+    bind[2].buffer_type = MYSQL_TYPE_BLOB;
+    bind[2].buffer = reinterpret_cast<char*>(&(clientid[0]));
+    bind[2].buffer_length = clientid_length;
+    bind[2].length = &clientid_length;
+    bind[2].is_null = &MLM_FALSE;
+
+    // The lease structure holds the client last transmission time (cltt_)
+    // and the valid lifetime (valid_lft_).  For convenience, the data stored
+    // in the database is expiry time (expire) and lease time (lease+time).
+    // The relationship is given by:
+    //
+    // lease_time - valid_lft_
+    // expire = cltt_ + valid_lft_
+    MYSQL_TIME mysql_expire;
+    uint32_t lease_time;
+    convertFromLeaseTime(lease->cltt_, lease->valid_lft_,
+                         mysql_expire, lease_time);
+
+    // lease_time: unsigned int
+    bind[3].buffer_type = MYSQL_TYPE_LONG;
+    bind[3].buffer = reinterpret_cast<char*>(&lease_time);
+    bind[3].is_unsigned = 1;
+    bind[3].is_null = &MLM_FALSE;
+
+    // expire: timestamp
+    bind[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+    bind[4].buffer = reinterpret_cast<char*>(&mysql_expire);
+    bind[4].buffer_length = sizeof(mysql_expire);
+    bind[4].is_null = &MLM_FALSE;
+
+    // subnet_id: unsigned int
+    bind[5].buffer_type = MYSQL_TYPE_LONG;
+    bind[5].buffer = reinterpret_cast<char*>(&(lease->subnet_id_));
+    bind[5].is_unsigned = static_cast<my_bool>(1);
+    bind[5].is_null = &MLM_FALSE;
+
+    // pref_lifetime: unsigned int
+    bind[6].buffer_type = MYSQL_TYPE_LONG;
+    bind[6].buffer = reinterpret_cast<char*>(&(lease->preferred_lft_));
+    bind[6].is_unsigned = static_cast<my_bool>(1);
+    bind[6].is_null = &MLM_FALSE;
+
+    // lease_type: tinyint
+    uint8_t lease_type = lease->type_;  // Needed for int -> uint8_t conversion
+    bind[7].buffer_type = MYSQL_TYPE_TINY;
+    bind[7].buffer = reinterpret_cast<char*>(&lease_type);
+    bind[7].is_unsigned = static_cast<my_bool>(1);
+    bind[7].is_null = &MLM_FALSE;
+
+    // iaid: unsigned int
+    bind[8].buffer_type = MYSQL_TYPE_LONG;
+    bind[8].buffer = reinterpret_cast<char*>(&(lease->iaid_));
+    bind[8].is_unsigned = static_cast<my_bool>(1);
+    bind[8].is_null = &MLM_FALSE;
+
+    // prefix_len: unsigned tinyint
+    bind[9].buffer_type = MYSQL_TYPE_TINY;
+    bind[9].buffer = reinterpret_cast<char*>(&(lease->prefixlen_));
+    bind[9].is_unsigned = static_cast<my_bool>(1);
+    bind[9].is_null = &MLM_FALSE;
+
+    // Bind the parameters to the statement
+    my_bool status = mysql_stmt_bind_param(statements_[INSERT_LEASE6], bind);
+    checkError(status, INSERT_LEASE6, "unable to bind parameters");
+
+    // Execute the statement
+    status = mysql_stmt_execute(statements_[INSERT_LEASE6]);
+    checkError(status, INSERT_LEASE6, "unable to execute");
+
+    // ... and find out whether a row as inserted.
+    return (mysql_stmt_affected_rows(statements_[INSERT_LEASE6]) == 1);
 }
 
 Lease4Ptr
-MySqlLeaseMgr::getLease4(isc::asiolink::IOAddress /* addr */,
+MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */,
                          SubnetID /* subnet_id */) const {
     return (Lease4Ptr());
 }
 
 Lease4Ptr
-MySqlLeaseMgr::getLease4(isc::asiolink::IOAddress /* addr */) const {
+MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */) const {
     return (Lease4Ptr());
 }
 
@@ -195,8 +354,147 @@ MySqlLeaseMgr::getLease4(const ClientId& /* clientid */,
 }
 
 Lease6Ptr
-MySqlLeaseMgr::getLease6(isc::asiolink::IOAddress /* addr */) const {
-    return (Lease6Ptr());
+MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+    my_bool MLM_FALSE = 0;    // MySqlLeaseMgr false
+    my_bool MLM_TRUE = 1;     // MySqlLeaseMgr true
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[1];
+    memset(inbind, 0, sizeof(inbind));
+
+    std::string addr6 = addr.toText();
+    unsigned long addr6_length = addr6.size();
+
+    inbind[0].buffer_type = MYSQL_TYPE_STRING;
+    inbind[0].buffer = const_cast<char*>(addr6.c_str());
+    inbind[0].buffer_length = addr6_length;
+    inbind[0].length = &addr6_length;
+    inbind[0].is_null = &MLM_FALSE;
+
+    // Bind the parameters to the statement
+    my_bool status = mysql_stmt_bind_param(statements_[GET_LEASE6], inbind);
+    checkError(status, GET_LEASE6, "unable to bind WHERE clause parameter");
+
+    // Output values
+    MYSQL_BIND outbind[9];
+    memset(outbind, 0, sizeof(outbind));
+
+    // address:  Not obtained - because of the WHERE clause, it will always be
+    // the same as the input parameter.
+
+    // hwaddr: varbinary(20)
+    uint8_t hwaddr[20];
+    unsigned long hwaddr_length;
+
+    outbind[0].buffer_type = MYSQL_TYPE_BLOB;
+    outbind[0].buffer = reinterpret_cast<char*>(hwaddr);
+    outbind[0].buffer_length = sizeof(hwaddr);
+    outbind[0].length = &hwaddr_length;
+
+    // client_id: varbinary(128)
+    uint8_t clientid[128];
+    unsigned long clientid_length;
+
+    outbind[1].buffer_type = MYSQL_TYPE_BLOB;
+    outbind[1].buffer = reinterpret_cast<char*>(clientid);
+    outbind[1].buffer_length = sizeof(clientid);
+    outbind[1].length = &clientid_length;
+
+    // lease_time: unsigned int
+    unsigned lease_time;
+    outbind[2].buffer_type = MYSQL_TYPE_LONG;
+    outbind[2].buffer = reinterpret_cast<char*>(&lease_time);
+    outbind[2].is_unsigned = MLM_TRUE;
+
+    // expire: timestamp
+    MYSQL_TIME mysql_expire;
+    outbind[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
+    outbind[3].buffer = reinterpret_cast<char*>(&mysql_expire);
+    outbind[3].buffer_length = sizeof(mysql_expire);
+
+    // subnet_id: unsigned int
+    unsigned subnet_id;
+    outbind[4].buffer_type = MYSQL_TYPE_LONG;
+    outbind[4].buffer = reinterpret_cast<char*>(&subnet_id);
+    outbind[4].is_unsigned = MLM_TRUE;
+
+    // pref_lifetime: unsigned int
+    unsigned pref_lifetime;
+    outbind[5].buffer_type = MYSQL_TYPE_LONG;
+    outbind[5].buffer = reinterpret_cast<char*>(&pref_lifetime);
+    outbind[5].is_unsigned = MLM_TRUE;
+
+    // lease_type: tinyint
+    uint8_t lease_type;
+    outbind[6].buffer_type = MYSQL_TYPE_TINY;
+    outbind[6].buffer = reinterpret_cast<char*>(&lease_type);
+    outbind[6].is_unsigned = MLM_TRUE;
+
+    // iaid: unsigned int
+    unsigned iaid;
+    outbind[7].buffer_type = MYSQL_TYPE_LONG;
+    outbind[7].buffer = reinterpret_cast<char*>(&iaid);
+    outbind[7].is_unsigned = MLM_TRUE;
+
+    // prefix_len: unsigned tinyint
+    uint8_t prefixlen;
+    outbind[8].buffer_type = MYSQL_TYPE_TINY;
+    outbind[8].buffer = reinterpret_cast<char*>(&prefixlen);
+    outbind[8].is_unsigned = MLM_TRUE;
+
+    // Bind the parameters to the statement
+    status = mysql_stmt_bind_result(statements_[GET_LEASE6], outbind);
+    checkError(status, GET_LEASE6, "unable to bind SELECT caluse parameters");
+
+    // Execute the statement
+    status = mysql_stmt_execute(statements_[GET_LEASE6]);
+    checkError(status, GET_LEASE6, "unable to execute");
+
+    // Fetch the data.
+    Lease6Ptr result(new Lease6());
+    status = mysql_stmt_fetch(statements_[GET_LEASE6]);
+    if (status == 0) {
+        // Success - put the data in the lease object
+        result->addr_ = addr;
+        result->hwaddr_ = vector<uint8_t>(&hwaddr[0], &hwaddr[hwaddr_length]);
+        result->duid_.reset(new DUID(clientid, clientid_length));
+        convertToLeaseTime(mysql_expire, lease_time,
+                           result->cltt_, result->valid_lft_);
+        result->subnet_id_ = subnet_id;
+        result->preferred_lft_ = pref_lifetime;
+        switch (lease_type) {
+            case Lease6::LEASE_IA_NA:
+                result->type_ = Lease6::LEASE_IA_NA;
+                break;
+
+            case Lease6::LEASE_IA_TA:
+                result->type_ = Lease6::LEASE_IA_TA;
+                break;
+
+            case Lease6::LEASE_IA_PD:
+                result->type_ = Lease6::LEASE_IA_PD;
+                break;
+
+            default:
+                isc_throw(BadValue, "invalid lease type returned for <" <<
+                          raw_statements_[GET_LEASE6] << ">");
+        }
+        result->iaid_ = iaid;
+        result->prefixlen_ = prefixlen;
+
+        // As the address is the primary key in the table, we can't return
+        // two rows, so we don't bother checking.
+
+    } else if (status == 1) {
+        checkError(status, GET_LEASE6, "unable to fetch results");
+
+    } else {
+    //     We are ignoring truncation for now, so the only other result is
+    //     no data was found.  In that case, we returrn a null Lease6 structure.
+    //     This has already been set, so ther action is a no-op.
+    }
+
+    return (result);
 }
 
 Lease6Collection
@@ -211,20 +509,20 @@ MySqlLeaseMgr::getLease6(const DUID& /* duid */, uint32_t /* iaid */,
 }
 
 void
-MySqlLeaseMgr::updateLease4(Lease4Ptr /* lease4 */) {
+MySqlLeaseMgr::updateLease4(const Lease4Ptr& /* lease4 */) {
 }
 
 void
-MySqlLeaseMgr::updateLease6(Lease6Ptr /* lease6 */) {
+MySqlLeaseMgr::updateLease6(const Lease6Ptr& /* lease6 */) {
 }
 
 bool
-MySqlLeaseMgr::deleteLease4(uint32_t /* addr */) {
+MySqlLeaseMgr::deleteLease4(const isc::asiolink::IOAddress& /* addr */) {
     return (false);
 }
 
 bool
-MySqlLeaseMgr::deleteLease6(isc::asiolink::IOAddress /* addr */) {
+MySqlLeaseMgr::deleteLease6(const isc::asiolink::IOAddress& /* addr */) {
     return (false);
 }
 
@@ -244,10 +542,10 @@ MySqlLeaseMgr::getVersion() const {
     uint32_t    minor;      // Minor version number
 
     // Execute the prepared statement
-    int status = mysql_stmt_execute(statements_[SELECT_VERSION]);
+    int status = mysql_stmt_execute(statements_[GET_VERSION]);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to execute <"
-                  << raw_statements_[SELECT_VERSION] << "> - reason: " <<
+                  << raw_statements_[GET_VERSION] << "> - reason: " <<
                   mysql_error(mysql_));
     }
 
@@ -265,14 +563,14 @@ MySqlLeaseMgr::getVersion() const {
     bind[1].buffer = &minor;
     bind[1].buffer_length = sizeof(minor);
 
-    status = mysql_stmt_bind_result(statements_[SELECT_VERSION], bind);
+    status = mysql_stmt_bind_result(statements_[GET_VERSION], bind);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to bind result set: " <<
                   mysql_error(mysql_));
     }
 
     // Get the result
-    status = mysql_stmt_fetch(statements_[SELECT_VERSION]);
+    status = mysql_stmt_fetch(statements_[GET_VERSION]);
     if (status != 0) {
         isc_throw(DbOperationError, "unable to obtain result set: " <<
                   mysql_error(mysql_));
@@ -281,5 +579,19 @@ MySqlLeaseMgr::getVersion() const {
     return (std::make_pair(major, minor));
 }
 
+void
+MySqlLeaseMgr::commit() {
+    if (mysql_commit(mysql_) != 0) {
+        isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
+    }
+}
+
+void
+MySqlLeaseMgr::rollback() {
+    if (mysql_rollback(mysql_) != 0) {
+        isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
+    }
+}
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 118 - 11
src/lib/dhcp/mysql_lease_mgr.h

@@ -15,6 +15,7 @@
 #ifndef __MYSQL_LEASE_MGR_H
 #define __MYSQL_LEASE_MGR_H
 
+#include <time.h>
 #include <mysql.h>
 #include <dhcp/lease_mgr.h>
 
@@ -51,12 +52,22 @@ public:
     /// @brief Adds an IPv4 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease4Ptr lease);
+    ///
+    /// @result true if the lease was added, false if not (because a lease
+    ///         with the same address was already there).
+    ///
+    /// @exception DbOperationError Database function failed
+    virtual bool addLease(const Lease4Ptr& lease);
 
     /// @brief Adds an IPv6 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease6Ptr lease);
+    ///
+    /// @result true if the lease was added, false if not (because a lease
+    ///         with the same address was already there).
+    ///
+    /// @exception DbOperationError Database function failed
+    virtual bool addLease(const Lease6Ptr& lease);
 
     /// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
     ///
@@ -68,7 +79,7 @@ public:
     /// @param subnet_id ID of the subnet the lease must belong to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
                                 SubnetID subnet_id) const;
 
     /// @brief Returns an IPv4 lease for specified IPv4 address
@@ -84,7 +95,7 @@ public:
     /// @param subnet_id ID of the subnet the lease must belong to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address.
     ///
@@ -144,7 +155,7 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
+    virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
 
     /// @brief Returns existing IPv6 leases for a given DUID+IA combination
     ///
@@ -175,28 +186,28 @@ public:
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease4(Lease4Ptr lease4);
+    virtual void updateLease4(const Lease4Ptr& lease4);
 
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    virtual void updateLease6(Lease6Ptr lease6);
+    virtual void updateLease6(const Lease6Ptr& lease6);
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    virtual bool deleteLease4(uint32_t addr);
+    virtual bool deleteLease4(const isc::asiolink::IOAddress& addr);
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    virtual bool deleteLease6(isc::asiolink::IOAddress addr);
+    virtual bool deleteLease6(const isc::asiolink::IOAddress& addr);
 
     /// @brief Returns backend name.
     ///
@@ -224,13 +235,89 @@ public:
     /// Also if B>C, some database upgrade procedure may be triggered
     virtual std::pair<uint32_t, uint32_t> getVersion() const;
 
+    /// @brief Commit Transactions
+    ///
+    /// Commits all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @exception DbOperationError if the commit failed.
+    virtual void commit();
+
+
+    /// @brief Rollback Transactions
+    ///
+    /// Rolls back all pending database operations.  On databases that don't
+    /// support transactions, this is a no-op.
+    ///
+    /// @exception DbOperationError if the rollback failed.
+    virtual void rollback();
+
+    ///@{
+    /// The following methods are used to convert between times and time
+    /// intervals stored in the server in the Lease object, and the times
+    /// stored in the database.  The reason for the difference is because
+    /// in the DHCP server, the cltt (Client Time Since Last Transmission)
+    /// is the natural data: in the lease file - which may be read by the
+    /// user - it is the expiry time of the lease.
+
+    /// @brief Convert Lease Time to Database Times
+    ///
+    /// Within the DHCP servers, times are stored as cltt (client last transmit
+    /// time) and valid_lft (valid lifetime).  In the database, the information
+    /// is stored as lease_time (lease time) and expire (time of expiry of the
+    /// lease).  They are related by the equations:
+    ///
+    /// lease_time = valid_lft
+    /// expire = cltt + valid_lft
+    ///
+    /// This method converts from the times in the lease object into times
+    /// able to be added to the database.
+    ///
+    /// @param cltt Client last transmit time
+    /// @param valid_lft Valid lifetime
+    /// @param expire Reference to MYSQL_TIME object where the expiry time of
+    ///        the lease will be put.
+    /// @param lease_time Reference to the time_t object where the lease time
+    ///         will be put.
+    static
+    void convertFromLeaseTime(time_t cltt, uint32_t valid_lft,
+                               MYSQL_TIME& expire, uint32_t& lease_time);
+
+    /// @brief Convert Database Time to Lease Times
+    ///
+    /// Within the database, time is stored as lease_time (lease time) and
+    /// expire (time of expiry of the lease).  In the DHCP server, the
+    /// information is stored as cltt (client last transmit time) and
+    /// valid_lft (valid lifetime).  These arr related by the equations:
+    ///
+    /// valid_lft = lease_time
+    /// cltt = expire - lease_time
+    ///
+    /// This method converts from the times in the database into times
+    /// able to be inserted into the lease object.
+    ///
+    /// @param expire Reference to MYSQL_TIME object from where the expiry
+    ///        time of the lease is taken.
+    /// @param lease_time lifetime of the lease.
+    /// @param cltt Reference to location where client last transmit time
+    ///        is put.
+    /// @param valid_lft Reference to location where valid lifetime is put.
+    static
+    void convertToLeaseTime(const MYSQL_TIME& expire, uint32_t lease_time,
+                            time_t& cltt, uint32_t& valid_lft);
+
+    ///@}
+
+
 private:
     /// @brief Enum of Statements
     ///
     /// This is provided to set indexes into a list of prepared statements.
     enum StatementIndex {
-        SELECT_VERSION,                 // Obtain version number
-        NUM_STATEMENTS                  // Number of statements
+        GET_LEASE6,
+        GET_VERSION,        // Obtain version number
+        INSERT_LEASE6,      // Add entry to lease6 table
+        NUM_STATEMENTS      // Number of statements
     };
 
     /// @brief Prepare Single Statement
@@ -262,6 +349,26 @@ private:
     /// @exception DbOpenError Error opening the database
     void openDatabase();
 
+    /// @brief Check Error and Throw Exception
+    ///
+    /// Virtually all MySQL functions return a status which, if non-zero,
+    /// indicates an error.  This inline function conceals a lot of error
+    /// checking/exception-throwing code.
+    ///
+    /// @param status Status code: non-zero implies an error
+    /// @param index Index of statement that caused the error
+    /// @param what High-level description of the error
+    ///
+    /// @exception DbOperationError Error doing a database operation
+    inline void checkError(my_bool status, StatementIndex index,
+                           const char* what) const {
+        if (status != 0) {
+            isc_throw(DbOperationError, what << " for <" <<
+                      raw_statements_[index] << ">, reason: " <<
+                      mysql_error(mysql_));
+        }
+    }
+
     // Members
     MYSQL*              mysql_;                 ///< MySQL context object
     std::vector<std::string> raw_statements_;   ///< Raw text of statements

+ 26 - 18
src/lib/dhcp/tests/lease_mgr_unittest.cc

@@ -49,26 +49,26 @@ public:
     /// @brief Adds an IPv4 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease4Ptr lease);
+    virtual bool addLease(const Lease4Ptr& lease);
 
     /// @brief Adds an IPv6 lease.
     ///
     /// @param lease lease to be added
-    virtual bool addLease(Lease6Ptr lease);
+    virtual bool addLease(const Lease6Ptr& lease);
 
     /// @brief Returns existing IPv4 lease for specified IPv4 address.
     ///
     /// @param addr address of the searched lease
     ///
     /// @return a collection of leases
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
 
     /// @brief Returns existing IPv4 lease for specific address and subnet
     /// @param addr address of the searched lease
     /// @param subnet_id ID of the subnet the lease must belong to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+    virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
                                 SubnetID subnet_id) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address.
@@ -118,7 +118,7 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
-    Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
+    Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
 
     /// @brief Returns existing IPv6 lease for a given DUID+IA combination
     ///
@@ -142,28 +142,28 @@ public:
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    void updateLease4(Lease4Ptr lease4);
+    void updateLease4(const Lease4Ptr& lease4);
 
     /// @brief Updates IPv4 lease.
     ///
     /// @param lease4 The lease to be updated.
     ///
     /// If no such lease is present, an exception will be thrown.
-    void updateLease6(Lease6Ptr lease6);
+    void updateLease6(const Lease6Ptr& lease6);
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    bool deleteLease4(uint32_t addr);
+    bool deleteLease4(const isc::asiolink::IOAddress& addr);
 
     /// @brief Deletes a lease.
     ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
-    bool deleteLease6(isc::asiolink::IOAddress addr);
+    bool deleteLease6(const isc::asiolink::IOAddress& addr);
 
     /// @brief Returns backend name.
     ///
@@ -180,6 +180,14 @@ public:
         return (make_pair(uint32_t(0), uint32_t(0)));
     }
 
+    /// @brief Commit transactions
+    void commit() {
+    }
+
+    /// @brief Rollback transactions
+    void rollback() {
+    }
+
     using LeaseMgr::getParameter;
 
 protected:
@@ -194,15 +202,15 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const LeaseMgr::ParameterMap& parameters)
 Memfile_LeaseMgr::~Memfile_LeaseMgr() {
 }
 
-bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease4>) {
+bool Memfile_LeaseMgr::addLease(const boost::shared_ptr<isc::dhcp::Lease4>&) {
     return (false);
 }
 
-bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease6>) {
+bool Memfile_LeaseMgr::addLease(const boost::shared_ptr<isc::dhcp::Lease6>&) {
     return (false);
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
     return (Lease4Ptr());
 }
 
@@ -210,7 +218,7 @@ Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
     return (Lease4Collection());
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress & ,
                                       SubnetID) const {
     return (Lease4Ptr());
 }
@@ -230,7 +238,7 @@ Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
     return (Lease4Collection());
 }
 
-Lease6Ptr Memfile_LeaseMgr::getLease6(isc::asiolink::IOAddress) const {
+Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress&) const {
     return (Lease6Ptr());
 }
 
@@ -243,18 +251,18 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
     return (Lease6Ptr());
 }
 
-void Memfile_LeaseMgr::updateLease4(Lease4Ptr ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr&) {
 }
 
-void Memfile_LeaseMgr::updateLease6(Lease6Ptr ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr&) {
 
 }
 
-bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
+bool Memfile_LeaseMgr::deleteLease4(const isc::asiolink::IOAddress&) {
     return (false);
 }
 
-bool Memfile_LeaseMgr::deleteLease6(isc::asiolink::IOAddress ) {
+bool Memfile_LeaseMgr::deleteLease6(const isc::asiolink::IOAddress&) {
     return (false);
 }
 

+ 179 - 21
src/lib/dhcp/tests/mysql_lease_mgr_unittest.cc

@@ -16,6 +16,7 @@
 #include <iostream>
 #include <sstream>
 #include <utility>
+#include <string>
 #include <gtest/gtest.h>
 
 #include <asiolink/io_address.h>
@@ -28,12 +29,6 @@ using namespace isc::dhcp;
 using namespace std;
 
 namespace {
-// empty class for now, but may be extended later.
-class MySqlLeaseMgrTest : public ::testing::Test {
-public:
-    MySqlLeaseMgrTest() {
-    }
-};
 
 // Connection strings
 const char* VALID_TYPE = "type=mysql";
@@ -61,13 +56,42 @@ string validConnectionString() {
                              VALID_USER, VALID_PASSWORD));
 }
 
+/// @brief Test Fixture Class
+///
+/// Opens the database prior to each test and closes it afterwards.
+/// All pending transactions are deleted prior to closure.
+
+class MySqlLeaseMgrTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    ///
+    /// Open the database.
+
+    MySqlLeaseMgrTest() {
+        lmptr_ = LeaseMgrFactory::create(validConnectionString());
+    }
+
+    /// @brief Destructor
+    ///
+    /// Rolls back all pending transactions.  The deletion of the
+    /// lmptr_ member variable will close the database.
+
+    virtual ~MySqlLeaseMgrTest() {
+        //lmptr_->rollback();
+    }
+
+    LeaseMgrPtr lmptr_;     // Pointer to the lease manager
+};
+
 
-// This test checks if the MySqlLeaseMgr can be instantiated.  This happens
-// only if:
-// a) The database can be opened
-// b) It finds the schema_version table
-// c) The schema version matches that in the Lease Manager code
-TEST_F(MySqlLeaseMgrTest, OpenDatabase) {
+/// @brief Check that Database Can Be Opened
+///
+/// This test checks if the MySqlLeaseMgr can be instantiated.  This happens
+/// only if the database can be opened.  Note that this is not part of the
+/// MySqlLeaseMgr test fixure set.  This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+
+TEST(MySqlOpenTest, OpenDatabase) {
     LeaseMgrPtr lmptr;
 
     // Check that wrong specification of backend throws an exception.
@@ -92,22 +116,156 @@ TEST_F(MySqlLeaseMgrTest, OpenDatabase) {
         DbOpenError);
 
     // Check that database opens correctly and that version is as expected
-    ASSERT_NO_THROW(lmptr = LeaseMgrFactory::create(connectionString(
-        VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
+    ASSERT_NO_THROW(lmptr = LeaseMgrFactory::create(validConnectionString()));
     ASSERT_TRUE(lmptr);
 }
 
-TEST_F(MySqlLeaseMgrTest, CheckVersion) {
-    // Open database
-    LeaseMgrPtr lmptr;
-    ASSERT_NO_THROW(lmptr = LeaseMgrFactory::create(connectionString(
-        VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
-    ASSERT_TRUE(lmptr);
+/// @brief Check conversion functions
+TEST_F(MySqlLeaseMgrTest, CheckTimeConversion) {
+    const time_t cltt = time(NULL);
+    const uint32_t valid_lft = 86400;       // 1 day
+    MYSQL_TIME expire;
+    uint32_t lease_time;
+
+    MySqlLeaseMgr::convertFromLeaseTime(cltt, valid_lft, expire, lease_time);
+    EXPECT_EQ(valid_lft, lease_time);
+    EXPECT_LE(2012, expire.year);       // Code was written in 2012
+    EXPECT_EQ(0, expire.second_part);
+    EXPECT_EQ(0, expire.neg);
 
+    // Convert back
+    time_t converted_cltt = 0;
+    uint32_t converted_valid_lft = 0;
+    MySqlLeaseMgr::convertToLeaseTime(expire, lease_time, converted_cltt,
+                                      converted_valid_lft);
+    EXPECT_EQ(cltt, converted_cltt);
+    EXPECT_EQ(valid_lft, converted_valid_lft);
+}
+
+/// @brief Check that getVersion() works
+TEST_F(MySqlLeaseMgrTest, CheckVersion) {
     // Check version
-    pair<uint32_t, uint32_t> version = lmptr->getVersion();
+    pair<uint32_t, uint32_t> version;
+    ASSERT_NO_THROW(version = lmptr_->getVersion());
     EXPECT_EQ(0, version.first);
     EXPECT_EQ(1, version.second);
 }
 
+
+/// @brief Compare Lease4 Structure
+bool
+compareLease6(const Lease6Ptr& l1, const Lease6Ptr& l2) {
+    return (
+        l1->type_ == l2->type_ &&
+        l1->addr_ == l2->addr_ &&
+        l1->prefixlen_ == l2->prefixlen_ &&
+        l1->iaid_ == l2->iaid_ &&
+        l1->hwaddr_ == l2->hwaddr_ &&
+        *l1->duid_ == *l2->duid_ &&
+        l1->preferred_lft_ == l2->preferred_lft_ &&
+        l1->valid_lft_ == l2->valid_lft_ &&
+        l1->cltt_ == l2->cltt_ &&
+        l1->subnet_id_ == l2->subnet_id_
+        );
+}
+
+void
+detailCompareLease6(const Lease6Ptr& l1, const Lease6Ptr& l2) {
+    EXPECT_EQ(l1->type_, l2->type_);
+    EXPECT_EQ(l1->addr_, l2->addr_);
+    EXPECT_EQ(l1->prefixlen_, l2->prefixlen_);
+    EXPECT_EQ(l1->iaid_, l2->iaid_);
+    EXPECT_TRUE(l1->hwaddr_ == l2->hwaddr_);
+    EXPECT_TRUE(*l1->duid_ == *l2->duid_);
+    EXPECT_EQ(l1->preferred_lft_, l2->preferred_lft_);
+    EXPECT_EQ(l1->valid_lft_, l2->valid_lft_);
+    EXPECT_EQ(l1->cltt_, l2->cltt_);
+    EXPECT_EQ(l1->subnet_id_, l2->subnet_id_);
+}
+
+/// @brief Initialize Lease
+///
+/// Initializes the unused fields in a lease to known values for
+/// testing purposes.
+void initializeUnusedLease6(Lease6Ptr& lease) {
+    lease->t1_ = 0;                             // Not saved
+    lease->t2_ = 0;                             // Not saved
+    lease->fixed_ = false;                      // Unused
+    lease->hostname_ = std::string("");         // Unused
+    lease->fqdn_fwd_ = false;                   // Unused
+    lease->fqdn_rev_ = false;                   // Unused
+    lease->comments_ = std::string("");         // Unused
+}
+
+/// @brief Check individual Lease6 methods
+///
+/// Checks that the add/update/delete works.  All are done within one
+/// test so that "rollback" can be used to remove trace of the tests
+/// from the database.
+///
+/// Tests where a collection of leases can be returned are in the test
+/// Lease6Collection.
+TEST_F(MySqlLeaseMgrTest, BasicLease6) {
+
+    // Define the leases being used for testing.
+    const IOAddress L1_ADDRESS(std::string("2001:db8::1"));
+    Lease6Ptr l1(new Lease6());
+    initializeUnusedLease6(l1);
+
+    l1->type_ = Lease6::LEASE_IA_TA;
+    l1->addr_ = L1_ADDRESS;
+    l1->prefixlen_ = 0;
+    l1->iaid_ = 42;
+    l1->hwaddr_ = std::vector<uint8_t>(6, 0x42);     // Six hex 42's
+    l1->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+    l1->preferred_lft_ = 3600;  // Preferred lifetime
+    l1->valid_lft_ = 3600;      // Actual lifetime
+    l1->cltt_ = 123456;         // Current time of day
+    l1->subnet_id_ = 73;        // Arbitrary number
+
+    const IOAddress L2_ADDRESS(std::string("2001:db8::2"));
+    Lease6Ptr l2(new Lease6());
+    initializeUnusedLease6(l2);
+
+    l2->type_ = Lease6::LEASE_IA_TA;
+    l2->addr_ = L1_ADDRESS;
+    l2->prefixlen_ = 0;
+    l2->iaid_ = 89;
+    l2->hwaddr_ = std::vector<uint8_t>(6, 0xf43);     // Six hex 42's
+    l2->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x3a)));
+    l2->preferred_lft_ = 1800;  // Preferred lifetime
+    l2->valid_lft_ = 5400;      // Actual lifetime
+    l2->cltt_ = 234567;         // Current time of day
+    l2->subnet_id_ = l1->subnet_id_;    // Same as l1
+
+    // Sanity check that the leases are different
+    ASSERT_FALSE(compareLease6(l1, l2));
+
+    // Start the tests.  Add the first lease to the database.  Then read it
+    // back to see whether it is what we think it is.
+    Lease6Ptr l_returned;
+
+    ASSERT_TRUE(lmptr_->addLease(l1));
+    l_returned = lmptr_->getLease6(L1_ADDRESS);
+    EXPECT_TRUE(l_returned);
+    detailCompareLease6(l1, l_returned);
+/*
+    // Delete the lease and check that it has been deleted.
+    EXPECT_TRUE(lmptr_->deleteLease6(L1_ADDRESS));
+    l_returned = lmptr_->getLease6(L1_ADDRESS);
+    EXPECT_FALSE(l_returned);
+
+    // Add the address again and check that we can't add it a second time
+    ASSERT_TRUE(lmptr_->addLease(l1));
+    ASSERT_FALSE(lmptr_->addLease(l1));
+
+    // Add the second lease
+    ASSERT_TRUE(lmptr_->addLease(l2));
+
+    // Finally, delete the lease and check we can't delete it again.
+    EXPECT_TRUE(lmptr_->deleteLease6(L1_ADDRESS));
+    EXPECT_FALSE(lmptr_->deleteLease6(L1_ADDRESS));
+    */
+}
+
 }; // end of anonymous namespace