Commit 43428694 authored by Stephen Morris's avatar Stephen Morris

[2342] Added updateLease6 capability

... where the elase is selected by address only.
parent a0f0141e
......@@ -84,6 +84,13 @@ public:
isc::Exception(file, line, what) {}
};
/// @brief Attempt to update lease that was not there
class NoSuchLease : public Exception {
public:
NoSuchLease(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// @brief specifies unique subnet identifier
/// @todo: Move this to subnet.h once ticket #2237 is merged
typedef uint32_t SubnetID;
......@@ -478,7 +485,8 @@ public:
///
/// @param lease4 The lease to be updated.
///
/// If no such lease is present, an exception will be thrown.
/// @exception NoSuchLease Attempt to update lease that did not exist.
/// @exception DbOperationError Update operation updated multiple leases.
virtual void updateLease6(const Lease6Ptr& lease6) = 0;
/// @brief Deletes a lease.
......
......@@ -50,20 +50,16 @@ namespace dhcp {
///
/// 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 - one array describes the
/// data being exchanged with the database. The other array describes the
/// WHERE clause of the statement.
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
/// structure is identical - it defines data values in the Lease6 structure.
///
/// The array describing the information exchanged is common between the
/// INSERT, UPDATE and SELECT statements, and this class handles the creation
/// of that array and the insertion/extraction of data into/from it.
/// This class handles the creation of that array. For maximum flexibility,
/// the data is appended to an array of MYSQL_BIND elemements, so allowing
/// additional elements to be prepended/appended to it.
///
/// Owing to the MySQL API, the process requires some intermediate variables
/// to hold things like length etc. This object holds the intermediate
/// variables and can:
/// 1. Build the MYSQL_BIND structures for a Lease6 object ready for passing
/// in to the MYSQL code.
/// 1. Copy information from the MYSQL_BIND structures into a Lease6 object.
/// variables as well.
class MySqlLease6Exchange {
public:
......@@ -76,21 +72,19 @@ public:
/// Fills in the MYSQL_BIND objects for the Lease6 passed to it.
///
/// @param lease Lease object to be added to the database
///
/// @return Pointer to MYSQL_BIND array holding the bind information.
/// This is a pointer to data internal to this object, and remains
/// valid only for as long as (1) this object is in existence and
/// (2) the lease object passed to it is in existence. The
/// caller should NOT delete it.
MYSQL_BIND* createBindForSend(const Lease6Ptr& lease) {
/// @param bindvec Vector of MySQL BIND objects: the elements describing the
/// lease are appended to this vector. The data added to the vector
/// only remain valid while both the lease and this object are valid.
void
createBindForSend(const Lease6Ptr& lease, std::vector<MYSQL_BIND>& bindvec) {
// Store lease object to ensure it remains valid.
lease_ = lease;
// Ensure bind array clear.
// Ensure bind_ array clear for constructing the MYSQL_BIND structures
// for this lease.
memset(bind_, 0, sizeof(bind_));
// address: varchar(40)
addr6_ = lease_->addr_.toText();
addr6_length_ = addr6_.size();
......@@ -108,7 +102,7 @@ public:
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
// lease_time: unsigned int
// valid lifetime: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&lease->valid_lft_);
bind_[2].is_unsigned = true_;
......@@ -119,6 +113,8 @@ public:
/// expiry time (expire). The relationship is given by:
//
// expire = cltt_ + valid_lft_
//
// @TODO Handle overflows
MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
expire_);
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
......@@ -156,7 +152,9 @@ public:
bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
bind_[8].is_unsigned = true_;
return(bind_);
// Add the data to the vector. Note the end element is one after the
// end of the array.
bindvec.insert(bindvec.end(), &bind_[0], &bind_[9]);
}
/// @brief Create BIND array to receive data
......@@ -165,12 +163,14 @@ public:
/// After data is successfully received, getLeaseData() is used to copy
/// it to a Lease6 object.
///
/// @return Pointer to MYSQL_BIND array for data reception. This array is
/// valid only for as long as this MySqlLease6Exchange object is
/// in existence.
MYSQL_BIND* createBindForReceive() {
/// @param bindvec Vector of MySQL BIND objects: the elements describing the
/// lease are appended to this vector. The data added to the vector
/// only remain valid while both the lease and this object are valid.
void createBindForReceive(std::vector<MYSQL_BIND>& bindvec) {
// Ensure bind array clear.
// Ensure both the array of MYSQL_BIND structures and the error array
// are clear.
memset(bind_, 0, sizeof(bind_));
memset(error_, 0, sizeof(error_));
......@@ -235,7 +235,9 @@ public:
bind_[8].is_unsigned = true_;
bind_[8].error = &error_[8];
return (bind_);
// Add the data to the vector. Note the end element is one after the
// end of the array.
bindvec.insert(bindvec.end(), &bind_[0], &bind_[9]);
}
/// @brief Copy Received Data into Lease6 Object
......@@ -253,7 +255,7 @@ public:
// Create the object to be returned.
Lease6Ptr result(new Lease6());
// Success - put the data in the lease object
// Put the data in the lease object
// The address buffer is declared larger than the buffer size passed
// to the access function so that we can always append a null byte.
......@@ -465,9 +467,18 @@ MySqlLeaseMgr::openDatabase() {
mysql_error(mysql_));
}
// Open the database. Use defaults for non-specified options.
// Open the database.
//
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
// the affected rows are the number of rows found that match the
// WHERE clause of the SQL statement, not the rows changed. The reason
// here is that MySQL apparently does not update a row if data has not
// changed and so the "affected rows" (retrievable from MySQL) is zero.
// This makes it hard to distinguish whether the UPDATE changed no rows
// because no row matching the WHERE clause was found, or because a
// row was found by no data was altered.
MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
0, NULL, 0);
0, NULL, CLIENT_FOUND_ROWS);
if (status != mysql_) {
isc_throw(DbOpenError, mysql_error(mysql_));
}
......@@ -523,6 +534,11 @@ MySqlLeaseMgr::prepareStatements() {
"expire, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
prepareStatement(UPDATE_LEASE6,
"UPDATE lease6 SET address = ?, duid = ?, "
"valid_lifetime = ?, expire = ?, subnet_id = ?, "
"pref_lifetime = ?, lease_type = ?, iaid = ?, "
"prefix_len = ? WHERE address = ?");
}
bool
......@@ -537,10 +553,11 @@ MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
// Create the MYSQL_BIND array for the lease
MySqlLease6Exchange exchange;
MYSQL_BIND* bind = exchange.createBindForSend(lease);
std::vector<MYSQL_BIND> bind;
exchange.createBindForSend(lease, bind);
// Bind the parameters to the statement
int status = mysql_stmt_bind_param(statements_[INSERT_LEASE6], bind);
int status = mysql_stmt_bind_param(statements_[INSERT_LEASE6], &bind[0]);
checkError(status, INSERT_LEASE6, "unable to bind parameters");
// Execute the statement
......@@ -621,14 +638,15 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
// Set up the SELECT clause
MySqlLease6Exchange exchange;
MYSQL_BIND* outbind = exchange.createBindForReceive();
std::vector<MYSQL_BIND> outbind;
exchange.createBindForReceive(outbind);
// Bind the input parameters to the statement
int status = mysql_stmt_bind_param(statements_[GET_LEASE6], inbind);
checkError(status, GET_LEASE6, "unable to bind WHERE clause parameter");
// Bind the output parameters to the statement
status = mysql_stmt_bind_result(statements_[GET_LEASE6], outbind);
status = mysql_stmt_bind_result(statements_[GET_LEASE6], &outbind[0]);
checkError(status, GET_LEASE6, "unable to bind SELECT caluse parameters");
// Execute the statement
......@@ -691,9 +709,45 @@ MySqlLeaseMgr::updateLease4(const Lease4Ptr& /* lease4 */) {
}
void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& /* lease6 */) {
isc_throw(NotImplemented, "MySqlLeaseMgr::updateLease6(const Lease6Ptr&) "
"not implemented yet");
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
// Create the MYSQL_BIND array for the data being updated
MySqlLease6Exchange exchange;
std::vector<MYSQL_BIND> bind;
exchange.createBindForSend(lease, bind);
// Set up the WHERE clause value
MYSQL_BIND where;
memset(&where, 0, sizeof(where));
std::string addr6 = lease->addr_.toText();
unsigned long addr6_length = addr6.size();
where.buffer_type = MYSQL_TYPE_STRING;
where.buffer = const_cast<char*>(addr6.c_str());
where.buffer_length = addr6_length;
where.length = &addr6_length;
bind.push_back(where);
// Bind the parameters to the statement
int status = mysql_stmt_bind_param(statements_[UPDATE_LEASE6], &bind[0]);
checkError(status, UPDATE_LEASE6, "unable to bind parameters");
// Execute
status = mysql_stmt_execute(statements_[UPDATE_LEASE6]);
checkError(status, UPDATE_LEASE6, "unable to execute");
// See how many rows were affected. The statement should only delete a
// single row.
int affected_rows = mysql_stmt_affected_rows(statements_[UPDATE_LEASE6]);
if (affected_rows == 0) {
isc_throw(NoSuchLease, "unable to update lease for address " <<
addr6 << " as it does not exist");
} else if (affected_rows > 1) {
// Should not happen - primary key constraint should only have selected
// one row.
isc_throw(DbOperationError, "apparently updated more than one lease "
"that had the address " << addr6);
}
}
bool
......
......@@ -208,7 +208,8 @@ public:
///
/// @param lease4 The lease to be updated.
///
/// If no such lease is present, an exception will be thrown.
/// @exception NoSuchLease Attempt to update lease that did not exist.
/// @exception DbOperationError Update operation updated multiple leases.
virtual void updateLease6(const Lease6Ptr& lease6);
/// @brief Deletes a lease.
......@@ -334,6 +335,7 @@ private:
GET_LEASE6, // Get lease 6 by address
GET_VERSION, // Obtain version number
INSERT_LEASE6, // Add entry to lease6 table
UPDATE_LEASE6, // Update a Lease6 entry
NUM_STATEMENTS // Number of statements
};
......
......@@ -30,6 +30,11 @@ using namespace std;
namespace {
// IPv6 addresseses
const char* ADDRESS_1 = "2001:db8::1";
const char* ADDRESS_2 = "2001:db8::2";
const char* ADDRESS_3 = "2001:db8::3";
// Connection strings. Assume:
// Database: keatest
// Username: keatest
......@@ -93,41 +98,36 @@ validConnectionString() {
VALID_USER, VALID_PASSWORD));
}
// Clear everything from the database tables
void
clearAll() {
// Initialise
MYSQL handle;
(void) mysql_init(&handle);
// Open database
(void) mysql_real_connect(&handle, "localhost", "keatest", "keatest",
"keatest", 0, NULL, 0);
// Clear the database
(void) mysql_query(&handle, "DELETE FROM lease4");
(void) mysql_query(&handle, "DELETE FROM lease6");
// ... and close
(void) mysql_close(&handle);
}
/// @brief Test Fixture Class
///
/// Opens the database prior to each test and closes it afterwards.
/// All pending transactions are deleted prior to closure.
// @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
///
/// Deletes everything from the database and opens it.
MySqlLeaseMgrTest() {
MySqlLeaseMgrTest() : L1_ADDRESS(ADDRESS_1), L2_ADDRESS(ADDRESS_2),
L3_ADDRESS(ADDRESS_3), L1_IOADDRESS(L1_ADDRESS),
L2_IOADDRESS(L2_ADDRESS), L3_IOADDRESS(L3_ADDRESS)
{
clearAll();
LeaseMgrFactory::create(validConnectionString());
lmptr_ = &(LeaseMgrFactory::instance());
}
/// @brief Destructor
///
/// Rolls back all pending transactions. The deletion of the
/// lmptr_ member variable will close the database. Then
/// reopen it and delete everything created by the test.
virtual ~MySqlLeaseMgrTest() {
lmptr_->rollback();
LeaseMgrFactory::destroy();
clearAll();
}
/// @brief Reopen the database
///
/// Closes the database and re-open it. Anything committed should be
......@@ -138,27 +138,126 @@ public:
lmptr_ = &(LeaseMgrFactory::instance());
}
/// @brief Destructor
/// @brief Clear everything from the database tables
///
/// Rolls back all pending transactions. The deletion of the
/// lmptr_ member variable will close the database. Then
/// reopen it and delete everything created by the test.
virtual ~MySqlLeaseMgrTest() {
lmptr_->rollback();
LeaseMgrFactory::destroy();
clearAll();
/// There is no error checking in this code, as this is just
/// extra checking that the database is clear before the text.
void
clearAll() {
// Initialise
MYSQL handle;
(void) mysql_init(&handle);
// Open database
(void) mysql_real_connect(&handle, "localhost", "keatest",
"keatest", "keatest", 0, NULL,
0);
// Clear the database
(void) mysql_query(&handle, "DELETE FROM lease4");
(void) mysql_query(&handle, "DELETE FROM lease6");
// ... and close
(void) mysql_close(&handle);
}
// @brief Initialize Lease6 Fields
//
// Returns a pointer to a Lease6 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 Lease6Ptr. This will not point to anything if the initialization
// failed (e.g. unknown address).
Lease6Ptr initializeLease6(std::string address) {
Lease6Ptr lease(new Lease6());
// Set the address of the lease
lease->addr_ = IOAddress(address);
// Initialize unused fields.
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 the other parameters
if (address == L1_ADDRESS) {
lease->type_ = Lease6::LEASE_IA_TA;
lease->prefixlen_ = 0;
lease->iaid_ = 42;
lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
lease->preferred_lft_ = 3600; // Preferred lifetime
lease->valid_lft_ = 3677; // Actual lifetime
lease->cltt_ = 123456; // Current time of day
lease->subnet_id_ = 73; // Arbitrary number
} else if (address == L2_ADDRESS) {
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 7;
lease->iaid_ = 89;
lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x3a)));
lease->preferred_lft_ = 1800; // Preferred lifetime
lease->valid_lft_ = 5412; // Actual lifetime
lease->cltt_ = 234567; // Current time of day
lease->subnet_id_ = 73; // Same as for L1_ADDRESS
} else if (address == L3_ADDRESS) {
lease->type_ = Lease6::LEASE_IA_NA;
lease->prefixlen_ = 28;
lease->iaid_ = 0xfffffffe;
vector<uint8_t> duid;
for (uint8_t i = 0; i < 128; ++i) {
duid.push_back(i + 5);
}
lease->duid_ = boost::shared_ptr<DUID>(new DUID(duid));
// 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->preferred_lft_ = 7200; // Preferred lifetime
lease->valid_lft_ = 7000; // Actual lifetime
lease->cltt_ = 234567; // Current time of day
lease->subnet_id_ = 37; // Different from L1 and L2
} else {
// Unknown address, return an empty pointer.
lease.reset();
}
return (lease);
}
// Member variables
LeaseMgr* lmptr_; // Pointer to the lease manager
string L1_ADDRESS; // String form of address 1
string L2_ADDRESS; // String form of address 2
string L3_ADDRESS; // String form of address 3
IOAddress L1_IOADDRESS; // IOAddress form of L1_ADDRESS
IOAddress L2_IOADDRESS; // IOAddress form of L2_ADDRESS
IOAddress L3_IOADDRESS; // IOAddress form of L3_ADDRESS
};
/// @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.
// @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) {
// Check that database opens correctly and tidy up. If it fails, print
......@@ -208,7 +307,7 @@ TEST(MySqlOpenTest, OpenDatabase) {
NoDatabaseName);
}
/// @brief Check conversion functions
// @brief Check conversion functions
TEST_F(MySqlLeaseMgrTest, CheckTimeConversion) {
const time_t cltt = time(NULL);
const uint32_t valid_lft = 86400; // 1 day
......@@ -239,7 +338,15 @@ TEST_F(MySqlLeaseMgrTest, CheckTimeConversion) {
EXPECT_EQ(cltt, converted_cltt);
}
/// @brief Check that getVersion() works
// @brief Check getName() returns correct database name
TEST_F(MySqlLeaseMgrTest, getName) {
EXPECT_EQ(std::string("keatest"), lmptr_->getName());
// @TODO: check for the negative
}
// @brief Check that getVersion() works
TEST_F(MySqlLeaseMgrTest, CheckVersion) {
// Check version
pair<uint32_t, uint32_t> version;
......@@ -265,81 +372,24 @@ detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
EXPECT_EQ(first->subnet_id_, second->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.
// @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->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
l1->preferred_lft_ = 3600; // Preferred lifetime
l1->valid_lft_ = 3677; // 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_PD;
l2->addr_ = L2_ADDRESS;
l2->prefixlen_ = 7;
l2->iaid_ = 89;
l2->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x3a)));
l2->preferred_lft_ = 1800; // Preferred lifetime
l2->valid_lft_ = 5412; // Actual lifetime
l2->cltt_ = 234567; // Current time of day
l2->subnet_id_ = l1->subnet_id_; // Same as l1
const IOAddress L3_ADDRESS(std::string("2001:db8::3"));
Lease6Ptr l3(new Lease6());
initializeUnusedLease6(l3);
l3->type_ = Lease6::LEASE_IA_NA;
l3->addr_ = L3_ADDRESS;
l3->prefixlen_ = 28;
l3->iaid_ = 0xfffffffe;
vector<uint8_t> duid;
for (uint8_t i = 0; i < 128; ++i) {
duid.push_back(i + 5);
}
l3->duid_ = boost::shared_ptr<DUID>(new DUID(duid));
// The times used in the next tests are deliberately restricted - we should
// be avle to cope with valid lifetimes up to 0xffffffff. However, this
// will lead to overflows.
// @TODO: test overflow conditions when code has been fixed
l3->preferred_lft_ = 7200; // Preferred lifetime
l3->valid_lft_ = 7000; // Actual lifetime
l3->cltt_ = 234567; // Current time of day
l3->subnet_id_ = l1->subnet_id_; // Same as l1
Lease6Ptr l1 = initializeLease6(L1_ADDRESS);
ASSERT_TRUE(l1);
Lease6Ptr l2 = initializeLease6(L2_ADDRESS);
ASSERT_TRUE(l2);
Lease6Ptr l3 = initializeLease6(L3_ADDRESS);
ASSERT_TRUE(l3);
// Sanity check that the leases are different
ASSERT_TRUE(*l1 != *l2);
......@@ -358,16 +408,15 @@ TEST_F(MySqlLeaseMgrTest, BasicLease6) {
// Reopen the database to ensure that they actually got stored.
reopen();
// check that the values returned are as expected.
l_returned = lmptr_->getLease6(L1_ADDRESS);
l_returned = lmptr_->getLease6(L1_IOADDRESS);
EXPECT_TRUE(l_returned);