Parcourir la source

[2404] Basic Lease4 functionality working

Stephen Morris il y a 12 ans
Parent
commit
6107151348

+ 331 - 65
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -41,8 +41,10 @@ namespace {
 /// the insertion of a trailing null regardless of whether the data returned
 /// contains a trailing null (the documentation is not clear on this point).
 
-const size_t ADDRESS6_TEXT_MAX_LEN = 42;    // Max size of a IPv6 text buffer
-const size_t DUID_MAX_LEN = 128;            // Max size of a DUID
+const size_t ADDRESS6_TEXT_MAX_LEN = 42;    ///< Max size of a IPv6 text buffer
+const size_t DUID_MAX_LEN = 128;            ///< Max size of a DUID
+const size_t HWADDR_MAX_LEN = 128;          ///< Max size of a hardware address
+const size_t CLIENT_ID_MAX_LEN = 128;       ///< Max size of a client ID
 ///@}
 
 /// @brief MySQL Selection Statements
@@ -55,8 +57,15 @@ struct TaggedStatement {
 };
 
 TaggedStatement tagged_statements[] = {
+    {MySqlLeaseMgr::DELETE_LEASE4,
+                    "DELETE FROM lease4 WHERE address = ?"},
     {MySqlLeaseMgr::DELETE_LEASE6,
                     "DELETE FROM lease6 WHERE address = ?"},
+    {MySqlLeaseMgr::GET_LEASE4_ADDR,
+                    "SELECT address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id "
+                            "FROM lease4 "
+                            "WHERE address = ?"},
     {MySqlLeaseMgr::GET_LEASE6_ADDR,
                     "SELECT address, duid, valid_lifetime, "
                         "expire, subnet_id, pref_lifetime, "
@@ -77,6 +86,10 @@ TaggedStatement tagged_statements[] = {
                             "WHERE duid = ? AND iaid = ? AND subnet_id = ?"},
     {MySqlLeaseMgr::GET_VERSION,
                     "SELECT version, minor FROM schema_version"},
+    {MySqlLeaseMgr::INSERT_LEASE4,
+                    "INSERT INTO lease4(address, hwaddr, client_id, "
+                        "valid_lifetime, expire, subnet_id) "
+                            "VALUES (?, ?, ?, ?, ?, ?)"},
     {MySqlLeaseMgr::INSERT_LEASE6,
                     "INSERT INTO lease6(address, duid, valid_lifetime, "
                         "expire, subnet_id, pref_lifetime, "
@@ -99,6 +112,207 @@ namespace dhcp {
 
 
 
+/// @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
+/// structure is identical - it defines data values in the Lease4 structure.
+/// This class handles the creation of that array.
+///
+/// Owing to the MySQL API, the process requires some intermediate variables
+/// to hold things like length etc.  This object holds the intermediate
+/// variables as well.
+///
+/// @note There are no unit tests for this class.  It is tested indirectly
+/// in all MySqlLeaseMgr::xxx4() calls where it is used.
+
+class MySqlLease4Exchange {
+public:
+    /// @brief Constructor
+    ///
+    /// Apart from the initialization of false_ and true_, the initialization
+    /// of addr4_, hwaddr_length_, hwaddr_buffer_, client_id_length_ and
+    /// client_id_buffer_ are to satisfy cppcheck: none are really needed, as
+    /// all variables are initialized/set in the methods.
+    MySqlLease4Exchange() : addr4_(0), hwaddr_length_(0), client_id_length_(0),
+                            false_(0), true_(1) {
+        memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+        memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+    }
+
+    /// @brief Create MYSQL_BIND objects for Lease4 Pointer
+    ///
+    /// Fills in the MYSQL_BIND objects for the Lease4 passed to it.
+    ///
+    /// @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 Lease4Ptr& lease) {
+        // Store lease object to ensure it remains valid.
+        lease_ = lease;
+
+        // Ensure bind_ array clear for constructing the MYSQL_BIND structures
+        // for this lease.
+        memset(bind_, 0, sizeof(bind_));
+
+        // Address: uint32_t
+        // Address in the Lease structre is an IOAddress object.  Convert to
+        // an integer for storage.
+        addr4_ = static_cast<uint32_t>(lease_->addr_);
+        bind_[0].buffer_type = MYSQL_TYPE_LONG;
+        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+        bind_[0].is_unsigned = true_;
+
+        // hwaddr: varbinary
+        // 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_;
+
+        // client_id: varbinary
+        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_);
+        bind_[3].is_unsigned = true_;
+
+        // expire: timestamp
+        // 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_
+        //
+        // @TODO Handle overflows
+        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_);
+        bind_[5].is_unsigned = true_;
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[6]));
+    }
+
+    /// @brief Create BIND array to receive data
+    ///
+    /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+    /// After data is successfully received, getLeaseData() is used to copy
+    /// it to a Lease6 object.
+    ///
+    /// @return Vector of MySQL BIND objects.
+    std::vector<MYSQL_BIND> createBindForReceive() {
+
+        // Ensure both the array of MYSQL_BIND structures and the error array
+        // are clear.
+        memset(bind_, 0, sizeof(bind_));
+        memset(error_, 0, sizeof(error_));
+
+        // address:  uint32_t
+        bind_[0].buffer_type = MYSQL_TYPE_LONG;
+        bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+        bind_[0].is_unsigned = true_;
+        bind_[0].error = &error_[0];
+
+        // 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_;
+        bind_[1].error = &error_[1];
+
+        // 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_;
+        bind_[2].error = &error_[2];
+
+        // lease_time: unsigned int
+        bind_[3].buffer_type = MYSQL_TYPE_LONG;
+        bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
+        bind_[3].is_unsigned = true_;
+        bind_[3].error = &error_[3];
+
+        // expire: timestamp
+        bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+        bind_[4].buffer = reinterpret_cast<char*>(&expire_);
+        bind_[4].buffer_length = sizeof(expire_);
+        bind_[4].error = &error_[4];
+
+        // subnet_id: unsigned int
+        bind_[5].buffer_type = MYSQL_TYPE_LONG;
+        bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
+        bind_[5].is_unsigned = true_;
+        bind_[5].error = &error_[5];
+
+        // Add the data to the vector.  Note the end element is one after the
+        // end of the array.
+        return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[6]));
+    }
+
+    /// @brief Copy Received Data into Lease6 Object
+    ///
+    /// Called after the MYSQL_BIND array created by createBindForReceive()
+    /// has been used, this copies data from the internal member vairables
+    /// into a Lease4 object.
+    ///
+    /// @return Lease6Ptr Pointer to a Lease6 object holding the relevant
+    ///         data.
+    ///
+    /// @throw isc::BadValue Unable to convert Lease Type value in database
+    Lease4Ptr getLeaseData() {
+        // Convert times to units for the lease structure
+        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_)));
+    }
+
+private:
+    // Note: All array lengths are equal to the corresponding variable in the
+    // schema.
+    // Note: arrays are declared fixed length for speed of creation
+    uint32_t        addr4_;             ///< IPv4 address
+    MYSQL_BIND      bind_[9];           ///< Bind array
+    std::vector<uint8_t> hwaddr_;       ///< Hardware address
+    uint8_t         hwaddr_buffer_[HWADDR_MAX_LEN]; ///< Hardware address buffer
+    unsigned long   hwaddr_length_;     ///< Hardware address length
+    std::vector<uint8_t> client_id_;    ///< Client identification
+    uint8_t         client_id_buffer_[CLIENT_ID_MAX_LEN]; ///< Client ID buffer
+    unsigned long   client_id_length_;  ///< Client ID address length
+    my_bool         error_[6];          ///< For error reporting
+    MYSQL_TIME      expire_;            ///< Lease expiry time
+    const my_bool   false_;             ///< "false" for MySql
+    Lease4Ptr       lease_;             ///< Pointer to lease object
+    uint32_t        valid_lifetime_;    ///< Lease time
+    uint32_t        subnet_id_;         ///< Subnet identification
+    const my_bool   true_;              ///< "true_" for MySql
+};
+
+
+
 /// @brief Exchange MySQL and Lease6 Data
 ///
 /// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
@@ -177,7 +391,7 @@ public:
 
         // valid lifetime: unsigned int
         bind_[2].buffer_type = MYSQL_TYPE_LONG;
-        bind_[2].buffer = reinterpret_cast<char*>(&lease->valid_lft_);
+        bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
         bind_[2].is_unsigned = true_;
 
         // expire: timestamp
@@ -370,11 +584,12 @@ public:
 private:
     // Note: All array lengths are equal to the corresponding variable in the
     // schema.
+    // Note: arrays are declared fixed length for speed of creation
     std::string     addr6_;             ///< String form of address
     char            addr6_buffer_[ADDRESS6_TEXT_MAX_LEN];  ///< Character 
                                         ///< array form of V6 address
     unsigned long   addr6_length_;      ///< Length of the address
-    MYSQL_BIND      bind_[9];           ///< Static array for speed of access
+    MYSQL_BIND      bind_[9];           ///< Bind array
     std::vector<uint8_t> duid_;         ///< Client identification
     uint8_t         duid_buffer_[DUID_MAX_LEN]; ///< Buffer form of DUID
     unsigned long   duid_length_;       ///< Length of the DUID
@@ -457,8 +672,9 @@ MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
     // Prepare all statements likely to be used.
     prepareStatements();
 
-    // Create the exchange object for use in exchanging data between the
+    // Create the exchange objects for use in exchanging data between the
     // program and the database.
+    exchange4_.reset(new MySqlLease4Exchange());
     exchange6_.reset(new MySqlLease6Exchange());
 }
 
@@ -649,20 +865,10 @@ MySqlLeaseMgr::prepareStatements() {
 }
 
 
-bool
-MySqlLeaseMgr::addLease(const Lease4Ptr& /* lease */) {
-    isc_throw(NotImplemented, "MySqlLeaseMgr::addLease(const Lease4Ptr&) "
-              "not implemented yet");
-    return (false);
-}
-
+// Common add lease code
 
 bool
-MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
-    const StatementIndex stindex = INSERT_LEASE6;
-
-    // Create the MYSQL_BIND array for the lease
-    std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
+MySqlLeaseMgr::addLease(StatementIndex stindex, std::vector<MYSQL_BIND>& bind) {
 
     // Bind the parameters to the statement
     int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
@@ -685,19 +891,85 @@ MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
     return (true);
 }
 
+bool
+MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+    // Create the MYSQL_BIND array for the lease
+    std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
+
+    // ... and drop to common code.
+    return (addLease(INSERT_LEASE4, bind));
+}
+
+bool
+MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+    // Create the MYSQL_BIND array for the lease
+    std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
+
+    // ... and drop to common code.
+    return (addLease(INSERT_LEASE6, bind));
+}
+
+
+template <typename Exchange, typename LeasePtr>
+void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* inbind,
+                             Exchange& exchange, LeasePtr& result) const {
+
+    // Bind the input parameters to the statement and bind the output
+    // to fields in the exchange object, then execute the prepared statement.
+    bindAndExecute(stindex, exchange, inbind);
+
+    // Fetch the data and set up the "release" object to release associated
+    // resources when this method exits.
+    MySqlFreeResult fetch_release(statements_[stindex]);
+    int status = mysql_stmt_fetch(statements_[stindex]);
+
+    if (status == 0) {
+        try {
+            result = exchange->getLeaseData();
+        } catch (const isc::BadValue& ex) {
+            // Lease type is returned, to rethrow the exception with a bit
+            // more data.
+            isc_throw(BadValue, ex.what() << ". Statement is <" <<
+                      text_statements_[stindex] << ">");
+        }
+
+        // As the address is the primary key in the table, we can't return
+        // two rows, so we don't bother checking whether multiple rows have
+        // been returned.
+
+    } else if (status == 1) {
+        checkError(status, stindex, "unable to fetch results");
+
+    } else {
+        // @TODO Handle truncation
+        // We are ignoring truncation for now, so the only other result is
+        // no data was found.  In that case, we return a null Lease6 structure.
+        // This has already been set, so no action is needed.
+    }
+}
 
 Lease4Ptr
-MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */,
-                         SubnetID /* subnet_id */) const {
-    isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const IOAddress&, SubnetID) "
-              "not implemented yet");
-    return (Lease4Ptr());
+MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[1];
+    memset(inbind, 0, sizeof(inbind));
+
+    uint32_t addr4 = static_cast<uint32_t>(addr);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+    inbind[0].is_unsigned = static_cast<my_bool>(1);
+
+    Lease4Ptr result;
+    getLease(GET_LEASE4_ADDR, inbind, exchange4_, result);
+
+    return (result);
 }
 
 
 Lease4Ptr
-MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */) const {
-    isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const IOAddress&) "
+MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */,
+                         SubnetID /* subnet_id */) const {
+    isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const IOAddress&, SubnetID) "
               "not implemented yet");
     return (Lease4Ptr());
 }
@@ -737,20 +1009,22 @@ MySqlLeaseMgr::getLease4(const ClientId& /* clientid */,
 }
 
 
-// A convenience function used in the various getLease6() methods.  It binds
+// A convenience function used in the various getLease() methods.  It binds
 // the selection parameters to the prepared statement, and binds the variables
 // that will receive the data.  These are stored in the MySqlLease6Exchange
 // object associated with the lease manager and converted to a Lease6 object
 // when retrieved.
+template <typename Exchange>
 void
-MySqlLeaseMgr::bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const {
+MySqlLeaseMgr::bindAndExecute(StatementIndex stindex, Exchange& exchange,
+                              MYSQL_BIND* inbind) const {
 
     // Bind the input parameters to the statement
     int status = mysql_stmt_bind_param(statements_[stindex], inbind);
     checkError(status, stindex, "unable to bind WHERE clause parameter");
 
     // Set up the SELECT clause
-    std::vector<MYSQL_BIND> outbind = exchange6_->createBindForReceive();
+    std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
 
     // Bind the output parameters to the statement
     status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]);
@@ -764,8 +1038,6 @@ MySqlLeaseMgr::bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const
 
 Lease6Ptr
 MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
-    const StatementIndex stindex = GET_LEASE6_ADDR;
-
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
     memset(inbind, 0, sizeof(inbind));
@@ -780,39 +1052,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
     inbind[0].buffer_length = addr6_length;
     inbind[0].length = &addr6_length;
 
-    // Bind the input parameters to the statement and bind the output
-    // to fields in the exchange object, then execute the prepared statement.
-    bind6AndExecute(stindex, inbind);
-
-    // Fetch the data and set up the "release" object to release associated
-    // resources when this method exits.
-    MySqlFreeResult fetch_release(statements_[stindex]);
-    int status = mysql_stmt_fetch(statements_[stindex]);
-
     Lease6Ptr result;
-    if (status == 0) {
-        try {
-            result = exchange6_->getLeaseData();
-        } catch (const isc::BadValue& ex) {
-            // Lease type is returned, to rethrow the exception with a bit
-            // more data.
-            isc_throw(BadValue, ex.what() << ". Statement is <" <<
-                      text_statements_[stindex] << ">");
-        }
-
-        // As the address is the primary key in the table, we can't return
-        // two rows, so we don't bother checking whether multiple rows have
-        // been returned.
-
-    } else if (status == 1) {
-        checkError(status, stindex, "unable to fetch results");
-
-    } else {
-        // @TODO Handle truncation
-        // We are ignoring truncation for now, so the only other result is
-        // no data was found.  In that case, we return a null Lease6 structure.
-        // This has already been set, so no action is needed.
-    }
+    getLease(GET_LEASE6_ADDR, inbind, exchange6_, result);
 
     return (result);
 }
@@ -853,7 +1094,7 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
 
     // Bind the input parameters to the statement and bind the output
     // to fields in the exchange object, then execute the prepared statement.
-    bind6AndExecute(stindex, inbind);
+    bindAndExecute(stindex, exchange6_, inbind);
 
     // Ensure that all the lease information is retrieved in one go to avoid
     // overhead of going back and forth between client and server.
@@ -924,7 +1165,7 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
 
     // Bind the input parameters to the statement and bind the output
     // to fields in the exchange object, then execute the prepared statement.
-    bind6AndExecute(stindex, inbind);
+    bindAndExecute(stindex, exchange6_, inbind);
 
     // Fetch the data and set up the "release" object to release associated
     // resources when this method exits then retrieve the data.
@@ -1014,9 +1255,34 @@ MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
     }
 }
 
-
+// TODO: Replace by deleteLease
 bool
-MySqlLeaseMgr::deleteLease4(const isc::asiolink::IOAddress& /* addr */) {
+MySqlLeaseMgr::deleteLease4(const isc::asiolink::IOAddress& addr) {
+    const StatementIndex stindex = DELETE_LEASE4;
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[1];
+    memset(inbind, 0, sizeof(inbind));
+
+    uint32_t addr4 = static_cast<uint32_t>(addr);
+
+    // See the earlier description of the use of "const_cast" when accessing
+    // the address for an explanation of the reason.
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+    inbind[0].is_unsigned = my_bool(1);
+
+    // Bind the input parameters to the statement
+    int status = mysql_stmt_bind_param(statements_[stindex], inbind);
+    checkError(status, stindex, "unable to bind WHERE clause parameter");
+
+    // Execute
+    status = mysql_stmt_execute(statements_[stindex]);
+    checkError(status, stindex, "unable to execute");
+
+    // See how many rows were affected.  Note that the statement may delete
+    // multiple rows.
+    return (mysql_stmt_affected_rows(statements_[stindex]) > 0);
     isc_throw(NotImplemented, "MySqlLeaseMgr::deleteLease4(const IOAddress&) "
               "not implemented yet");
     return (false);

+ 48 - 4
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -31,8 +31,9 @@ const uint32_t CURRENT_VERSION_VERSION = 0;
 const uint32_t CURRENT_VERSION_MINOR = 1;
 
 
-// Forward declaration of the Lease6 exchange object.  This class is defined
+// Forward declaration of the Lease exchange objects.  This class is defined
 // in the .cc file.
+class MySqlLease4Exchange;
 class MySqlLease6Exchange;
 
 
@@ -226,6 +227,10 @@ public:
 
     /// @brief Deletes an IPv4 lease.
     ///
+    /// @todo Merge with deleteLease6: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
@@ -233,6 +238,10 @@ public:
 
     /// @brief Deletes an IPv6 lease.
     ///
+    /// @todo Merge with deleteLease4: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv6 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
@@ -343,11 +352,14 @@ public:
     ///
     /// The contents of the enum are indexes into the list of SQL statements
     enum StatementIndex {
+        DELETE_LEASE4,              // Delete from lease4 by address
         DELETE_LEASE6,              // Delete from lease6 by address
+        GET_LEASE4_ADDR,            // Get lease4 by address
         GET_LEASE6_ADDR,            // Get lease6 by address
         GET_LEASE6_DUID_IAID,       // Get lease6 by DUID and IAID
         GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and Subnet ID
         GET_VERSION,                // Obtain version number
+        INSERT_LEASE4,              // Add entry to lease4 table
         INSERT_LEASE6,              // Add entry to lease6 table
         UPDATE_LEASE6,              // Update a Lease6 entry
         NUM_STATEMENTS              // Number of statements
@@ -389,6 +401,34 @@ private:
     /// @throw DbOpenError Error opening the database
     void openDatabase();
 
+    /// @brief Add Lease Common Code
+    ///
+    /// This method performs the common actions for both flavours of the
+    /// addLease method.
+    ///
+    /// @param stindex Index of statemnent being executed
+    /// @param bind MYSQL_BIND array that has been created for the type
+    ///        of lease in question.
+    ///
+    /// @return true if the lease was added, false if it was not added because
+    ///         a lease with that address already exists in the database.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    bool addLease(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+    /// @brief Get Lease Common Code
+    ///
+    /// This method performs the common actions for the getLease methods.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param inbind MYSQL_BIND array for input parameters
+    /// @param exchange Exchange object to use
+    /// @param lease Lease object returned
+    template <typename Exchange, typename LeasePtr>
+    void getLease(StatementIndex stindex, MYSQL_BIND* inbind,
+                  Exchange& exchange, LeasePtr& result) const;
+
     /// @brief Binds Parameters and Executes
     ///
     /// This method abstracts a lot of common processing from the getXxxx()
@@ -398,16 +438,19 @@ private:
     /// statement.
     ///
     /// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
-    /// method on the exchange6 object.
+    /// method on the appropriate exchange object.
     ///
     /// @param stindex Index of prepared statement to be executed
+    /// @param exchange Exchange object to use
     /// @param inbind Array of MYSQL_BIND objects representing the parameters.
     ///        (Note that the number is determined by the number of parameters
     ///        in the statement.)
     ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    void bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const;
+    template <typename Exchange>
+    void bindAndExecute(StatementIndex stindex, Exchange& exchange,
+                         MYSQL_BIND* inbind) const;
 
     /// @brief Check Error and Throw Exception
     ///
@@ -437,10 +480,11 @@ private:
     /// object as its contents may change in "const" calls, while the rest
     /// of this object does not.  (At alternative would be to declare it as
     /// "mutable".)
-    boost::scoped_ptr<MySqlLease6Exchange> exchange6_;
     MYSQL*              mysql_;                 ///< MySQL context object
     std::vector<std::string> text_statements_;  ///< Raw text of statements
     std::vector<MYSQL_STMT*> statements_;       ///< Prepared statements
+    boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
+    boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
 };
 
 }; // end of isc::dhcp namespace

+ 229 - 12
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

@@ -35,7 +35,12 @@ namespace {
 // Creation of the schema
 #include "schema_copy.h"
 
-// IPv6 addresseses
+// IPv4 and IPv6 addresseses
+const char* ADDRESS4[] = {
+    "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+    "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7",
+    NULL
+};
 const char* ADDRESS6[] = {
     "2001:db8::0", "2001:db8::1", "2001:db8::2", "2001:db8::3",
     "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7",
@@ -167,6 +172,13 @@ public:
     // Deletes everything from the database and opens it.
     MySqlLeaseMgrTest() {
         // Initialize address strings and IOAddresses
+        for (int i = 0; ADDRESS4[i] != NULL; ++i) {
+            string addr(ADDRESS4[i]);
+            straddress4_.push_back(addr);
+            IOAddress ioaddr(addr);
+            ioaddress4_.push_back(ioaddr);
+        }
+
         for (int i = 0; ADDRESS6[i] != NULL; ++i) {
             string addr(ADDRESS6[i]);
             straddress6_.push_back(addr);
@@ -176,6 +188,7 @@ public:
 
         destroySchema();
         createSchema();
+
         try {
             LeaseMgrFactory::create(validConnectionString());
         } catch (...) {
@@ -210,6 +223,119 @@ public:
         lmptr_ = &(LeaseMgrFactory::instance());
     }
 
+    // @brief Initialize Lease4 Fields
+    //
+    // Returns a pointer to a Lease4 structure.  Different values are put
+    // in the lease according to the address passed.
+    //
+    // This is just a convenience function for the test methods.
+    //
+    // @param address Address to use for the initialization
+    //
+    // @return Lease4Ptr.  This will not point to anything if the initialization
+    //         failed (e.g. unknown address).
+    Lease4Ptr initializeLease4(std::string address) {
+        Lease4Ptr lease(new Lease4());
+
+        // Set the address of the lease
+        lease->addr_ = IOAddress(address);
+
+        // Initialize unused fields.
+        lease->ext_ = 0;                            // Not saved
+        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
+
+        // Set other parameters.  For historical reasons, address 0 is not used.
+        if (address == straddress4_[0]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x08);
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x77)));
+            lease->valid_lft_ = 8677;      // Actual lifetime
+            lease->cltt_ = 168256;         // Current time of day
+            lease->subnet_id_ = 23;        // Arbitrary number
+
+        } else if (address == straddress4_[1]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x19);
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x42)));
+            lease->valid_lft_ = 3677;      // Actual lifetime
+            lease->cltt_ = 123456;         // Current time of day
+            lease->subnet_id_ = 73;        // Arbitrary number
+
+        } else if (address == straddress4_[2]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x2a);
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x3a)));
+            lease->valid_lft_ = 5412;      // Actual lifetime
+            lease->cltt_ = 234567;         // Current time of day
+            lease->subnet_id_ = 73;        // Same as for straddress4_1
+
+        } else if (address == straddress4_[3]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x3b);
+            vector<uint8_t> clientid;
+            for (uint8_t i = 31; i < 126; ++i) {
+                clientid.push_back(i);
+            }
+            lease->client_id_ = boost::shared_ptr<ClientId>
+                (new ClientId(clientid));
+
+            // The times used in the next tests are deliberately restricted - we
+            // should be able to cope with valid lifetimes up to 0xffffffff.
+            //  However, this will lead to overflows.
+            // @TODO: test overflow conditions when code has been fixed
+            lease->valid_lft_ = 7000;      // Actual lifetime
+            lease->cltt_ = 234567;         // Current time of day
+            lease->subnet_id_ = 37;        // Different from L1 and L2
+
+        } else if (address == straddress4_[4]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x4c);
+            // Same ClientId as straddr4_[1]
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x42)));
+            lease->valid_lft_ = 7736;      // Actual lifetime
+            lease->cltt_ = 222456;         // Current time of day
+            lease->subnet_id_ = 75;        // Arbitrary number
+
+        } else if (address == straddress4_[5]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x5d);
+            // Same ClientId and IAID as straddress4_1
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x42)));
+            lease->valid_lft_ = 7832;      // Actual lifetime
+            lease->cltt_ = 227476;         // Current time of day
+            lease->subnet_id_ = 175;       // Arbitrary number
+
+        } else if (address == straddress4_[6]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x6e);
+            // Same ClientId as straddress4_1
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0x42)));
+            lease->valid_lft_ = 1832;      // Actual lifetime
+            lease->cltt_ = 627476;         // Current time of day
+            lease->subnet_id_ = 112;       // Arbitrary number
+
+        } else if (address == straddress4_[7]) {
+            lease->hwaddr_ = vector<uint8_t>(6, 0x7f);
+            lease->client_id_ = boost::shared_ptr<ClientId>(
+                new ClientId(vector<uint8_t>(8, 0xe5)));
+            lease->valid_lft_ = 7975;      // Actual lifetime
+            lease->cltt_ = 213876;         // Current time of day
+            lease->subnet_id_ = 19;        // Arbitrary number
+
+        } else {
+            // Unknown address, return an empty pointer.
+            lease.reset();
+
+        }
+
+        return (lease);
+    }
+
     // @brief Initialize Lease6 Fields
     //
     // Returns a pointer to a Lease6 structure.  Different values are put
@@ -339,19 +465,15 @@ public:
         return (lease);
     }
 
-    // @brief Creates Leases for the test
+    // @brief Check Leases Present and Different
     //
-    // Creates all leases for the test and checks that they are different.
+    // Checks a vector of lease pointers and ensures that all the leases
+    // they point to are present and different.  If not, a GTest assertion
+    // will fail.
     //
-    // @return vector<Lease6Ptr> Vector of pointers to leases
-    vector<Lease6Ptr> createLeases6() {
-
-        // Create leases for each address
-        vector<Lease6Ptr> leases;
-        for (int i = 0; i < straddress6_.size(); ++i) {
-            leases.push_back(initializeLease6(straddress6_[i]));
-        }
-        EXPECT_EQ(8, leases.size());
+    // @param leases Vector of pointers to leases
+    template <typename T>
+    void checkLeasesDifferent(const std::vector<T> leases) const {
 
         // Check they were created
         for (int i = 0; i < leases.size(); ++i) {
@@ -364,6 +486,44 @@ public:
                 EXPECT_TRUE(leases[i] != leases[j]);
             }
         }
+    }
+
+    // @brief Creates Leases for the test
+    //
+    // Creates all leases for the test and checks that they are different.
+    //
+    // @return vector<Lease4Ptr> Vector of pointers to leases
+    vector<Lease4Ptr> createLeases4() {
+
+        // Create leases for each address
+        vector<Lease4Ptr> leases;
+        for (int i = 0; i < straddress4_.size(); ++i) {
+            leases.push_back(initializeLease4(straddress4_[i]));
+        }
+        EXPECT_EQ(8, leases.size());
+
+        // Check all were created and that they are different.
+        checkLeasesDifferent(leases);
+
+        return (leases);
+    }
+
+    // @brief Creates Leases for the test
+    //
+    // Creates all leases for the test and checks that they are different.
+    //
+    // @return vector<Lease6Ptr> Vector of pointers to leases
+    vector<Lease6Ptr> createLeases6() {
+
+        // Create leases for each address
+        vector<Lease6Ptr> leases;
+        for (int i = 0; i < straddress6_.size(); ++i) {
+            leases.push_back(initializeLease6(straddress6_[i]));
+        }
+        EXPECT_EQ(8, leases.size());
+
+        // Check all were created and that they are different.
+        checkLeasesDifferent(leases);
 
         return (leases);
     }
@@ -373,6 +533,8 @@ public:
 
     LeaseMgr*   lmptr_;             // Pointer to the lease manager
 
+    vector<string>  straddress4_;   // String forms of IPv4 addresses
+    vector<IOAddress> ioaddress4_;  // IOAddress forms of IPv4 addresses
     vector<string>  straddress6_;   // String forms of IPv6 addresses
     vector<IOAddress> ioaddress6_;  // IOAddress forms of IPv6 addresses
 };
@@ -542,6 +704,61 @@ detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
 }
 
 
+// @brief Basic Lease Checks
+//
+// Checks that the add/get/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
+// Lease4Collection.
+//
+// @param leases Vector of leases used in the tests
+// @param ioaddress Vector of IOAddresses used in the tests
+
+TEST_F(MySqlLeaseMgrTest, basicLease4) {
+    // Get the leases to be used for the test.
+    vector<Lease4Ptr> leases = createLeases4();
+
+    // Start the tests.  Add three leases to the database, read them back and
+    // check they are what we think they are.
+    EXPECT_TRUE(lmptr_->addLease(leases[1]));
+    EXPECT_TRUE(lmptr_->addLease(leases[2]));
+    EXPECT_TRUE(lmptr_->addLease(leases[3]));
+    lmptr_->commit();
+
+    // Reopen the database to ensure that they actually got stored.
+    reopen();
+
+    Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    EXPECT_TRUE(l_returned);
+    detailCompareLease(leases[1], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    EXPECT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+
+    l_returned = lmptr_->getLease4(ioaddress4_[3]);
+    EXPECT_TRUE(l_returned);
+    detailCompareLease(leases[3], l_returned);
+
+    // Check that we can't add a second lease with the same address
+    EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+    // Delete a lease, check that it's gone, and that we can't delete it
+    // a second time.
+    EXPECT_TRUE(lmptr_->deleteLease4(ioaddress4_[1]));
+    l_returned = lmptr_->getLease4(ioaddress4_[1]);
+    EXPECT_FALSE(l_returned);
+    EXPECT_FALSE(lmptr_->deleteLease4(ioaddress4_[1]));
+
+    // Check that the second address is still there.
+    l_returned = lmptr_->getLease4(ioaddress4_[2]);
+    EXPECT_TRUE(l_returned);
+    detailCompareLease(leases[2], l_returned);
+}
+
+
 // @brief Check individual Lease6 methods
 //
 // Checks that the add/get/delete works.  All are done within one