// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc; using namespace isc::asiolink; using namespace isc::dhcp; using namespace isc::dhcp::test; using namespace std; namespace { // This holds statements to create and destroy the schema. #include "schema_pgsql_copy.h" // Connection strings. // Database: keatest // Host: localhost // Username: keatest // Password: keatest const char* VALID_TYPE = "type=postgresql"; const char* INVALID_TYPE = "type=unknown"; const char* VALID_NAME = "name=keatest"; const char* INVALID_NAME = "name=invalidname"; const char* VALID_HOST = "host=localhost"; const char* INVALID_HOST = "host=invalidhost"; const char* VALID_USER = "user=keatest"; const char* INVALID_USER = "user=invaliduser"; const char* VALID_PASSWORD = "password=keatest"; const char* INVALID_PASSWORD = "password=invalid"; // Given a combination of strings above, produce a connection string. string connectionString(const char* type, const char* name, const char* host, const char* user, const char* password) { const string space = " "; string result = ""; if (type != NULL) { result += string(type); } if (name != NULL) { if (! result.empty()) { result += space; } result += string(name); } if (host != NULL) { if (! result.empty()) { result += space; } result += string(host); } if (user != NULL) { if (! result.empty()) { result += space; } result += string(user); } if (password != NULL) { if (! result.empty()) { result += space; } result += string(password); } return (result); } // Return valid connection string string validConnectionString() { return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)); } // @brief Clear everything from the database // // There is no error checking in this code: if something fails, one of the // tests will (should) fall over. void destroySchema() { // Open database PGconn * conn = 0; conn = PQconnectdb("host = 'localhost' user = 'keatest'" " password = 'keatest' dbname = 'keatest'"); PGresult * r; // Get rid of everything in it. for (int i = 0; destroy_statement[i] != NULL; ++i) { r = PQexec(conn, destroy_statement[i]); PQclear(r); } PQfinish(conn); } // @brief Create the Schema // // Creates all the tables in what is assumed to be an empty database. // // There is no error checking in this code: if it fails, one of the tests // will fall over. void createSchema() { // Open database PGconn * conn = 0; conn = PQconnectdb("host = 'localhost' user = 'keatest'" " password = 'keatest' dbname = 'keatest'"); PGresult * r; // Get rid of everything in it. for (int i = 0; create_statement[i] != NULL; ++i) { r = PQexec(conn, create_statement[i]); PQclear(r); } } /// @brief Test fixture class for testing PostgreSQL Lease Manager /// /// Opens the database prior to each test and closes it afterwards. /// All pending transactions are deleted prior to closure. class PgSqlLeaseMgrTest : public GenericLeaseMgrTest { public: /// @brief Constructor /// /// Deletes everything from the database and opens it. PgSqlLeaseMgrTest() { // Ensure schema is the correct one. destroySchema(); createSchema(); // Connect to the database try { LeaseMgrFactory::create(validConnectionString()); } catch (...) { std::cerr << "*** ERROR: unable to open database. The test\n" "*** environment is broken and must be fixed before\n" "*** the PostgreSQL tests will run correctly.\n" "*** The reason for the problem is described in the\n" "*** accompanying exception output.\n"; throw; } lmptr_ = &(LeaseMgrFactory::instance()); } /// @brief Destructor /// /// Rolls back all pending transactions. The deletion of lmptr_ will close /// the database. Then reopen it and delete everything created by the test. virtual ~PgSqlLeaseMgrTest() { lmptr_->rollback(); LeaseMgrFactory::destroy(); destroySchema(); } /// @brief Reopen the database /// /// Closes the database and re-open it. Anything committed should be /// visible. void reopen() { LeaseMgrFactory::destroy(); LeaseMgrFactory::create(validConnectionString()); lmptr_ = &(LeaseMgrFactory::instance()); } }; /// @brief Check that database can be opened /// /// This test checks if the PgSqlLeaseMgr can be instantiated. This happens /// only if the database can be opened. Note that this is not part of the /// PgSqlLeaseMgr test fixure set. This test checks that the database can be /// opened: the fixtures assume that and check basic operations. TEST(PgSqlOpenTest, OpenDatabase) { // Schema needs to be created for the test to work. destroySchema(); createSchema(); // Check that lease manager open the database opens correctly and tidy up. // If it fails, print the error message. try { LeaseMgrFactory::create(validConnectionString()); EXPECT_NO_THROW((void) LeaseMgrFactory::instance()); LeaseMgrFactory::destroy(); } catch (const isc::Exception& ex) { FAIL() << "*** ERROR: unable to open database, reason:\n" << " " << ex.what() << "\n" << "*** The test environment is broken and must be fixed\n" << "*** before the PostgreSQL tests will run correctly.\n"; } // Check that attempting to get an instance of the lease manager when // none is set throws an exception. EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager); // Check that wrong specification of backend throws an exception. // (This is really a check on LeaseMgrFactory, but is convenient to // perform here.) EXPECT_THROW(LeaseMgrFactory::create(connectionString( NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)), InvalidParameter); EXPECT_THROW(LeaseMgrFactory::create(connectionString( INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)), InvalidType); // Check that invalid login data causes an exception. EXPECT_THROW(LeaseMgrFactory::create(connectionString( VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)), DbOpenError); EXPECT_THROW(LeaseMgrFactory::create(connectionString( VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)), DbOpenError); EXPECT_THROW(LeaseMgrFactory::create(connectionString( VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)), DbOpenError); EXPECT_THROW(LeaseMgrFactory::create(connectionString( VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)), DbOpenError); // Check for missing parameters EXPECT_THROW(LeaseMgrFactory::create(connectionString( VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)), NoDatabaseName); // Tidy up after the test destroySchema(); } /// @brief Check the getType() method /// /// getType() returns a string giving the type of the backend, which should /// always be "postgresql". TEST_F(PgSqlLeaseMgrTest, getType) { EXPECT_EQ(std::string("postgresql"), lmptr_->getType()); } /// @brief Check getName() returns correct database name TEST_F(PgSqlLeaseMgrTest, getName) { EXPECT_EQ(std::string("keatest"), lmptr_->getName()); } /// @brief Check that getVersion() returns the expected version TEST_F(PgSqlLeaseMgrTest, checkVersion) { // Check version pair version; ASSERT_NO_THROW(version = lmptr_->getVersion()); EXPECT_EQ(PG_CURRENT_VERSION, version.first); EXPECT_EQ(PG_CURRENT_MINOR, version.second); } /// @brief Basic Lease4 Checks /// /// Checks that the addLease, getLease4 (by address) and deleteLease (with an /// IPv4 address) works. TEST_F(PgSqlLeaseMgrTest, basicLease4) { // Get the leases to be used for the test. vector 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]); ASSERT_TRUE(l_returned); detailCompareLease(leases[1], l_returned); l_returned = lmptr_->getLease4(ioaddress4_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); l_returned = lmptr_->getLease4(ioaddress4_[3]); ASSERT_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_->deleteLease(ioaddress4_[1])); l_returned = lmptr_->getLease4(ioaddress4_[1]); EXPECT_FALSE(l_returned); EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1])); // Check that the second address is still there. l_returned = lmptr_->getLease4(ioaddress4_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); } /// Checks that we are able to update an IPv4 lease in the database. TEST_F(PgSqlLeaseMgrTest, updateLease4) { testUpdateLease4(); } /// @brief Basic Lease4 Checks /// /// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id), /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id. /// (client-id is optional and may not be present) TEST_F(PgSqlLeaseMgrTest, lease4NullClientId) { // Get the leases to be used for the test. vector leases = createLeases4(); // Let's clear client-id pointers leases[1]->client_id_ = ClientIdPtr(); leases[2]->client_id_ = ClientIdPtr(); leases[3]->client_id_ = ClientIdPtr(); // 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]); ASSERT_TRUE(l_returned); detailCompareLease(leases[1], l_returned); l_returned = lmptr_->getLease4(ioaddress4_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); l_returned = lmptr_->getLease4(ioaddress4_[3]); ASSERT_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])); // Check that we can get the lease by HWAddr HWAddr tmp(leases[2]->hwaddr_, HTYPE_ETHER); Lease4Collection returned = lmptr_->getLease4(tmp); ASSERT_EQ(1, returned.size()); detailCompareLease(leases[2], *returned.begin()); l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); // Check that we can update the lease // Modify some fields in lease 1 (not the address) and update it. ++leases[1]->subnet_id_; leases[1]->valid_lft_ *= 2; lmptr_->updateLease4(leases[1]); // ... and check that the lease is indeed updated l_returned = lmptr_->getLease4(ioaddress4_[1]); ASSERT_TRUE(l_returned); detailCompareLease(leases[1], l_returned); // Delete a lease, check that it's gone, and that we can't delete it // a second time. EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1])); l_returned = lmptr_->getLease4(ioaddress4_[1]); EXPECT_FALSE(l_returned); EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1])); // Check that the second address is still there. l_returned = lmptr_->getLease4(ioaddress4_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); } /// @brief Basic Lease6 Checks /// /// Checks that the addLease, getLease6 (by address) and deleteLease (with an /// IPv6 address) works. TEST_F(PgSqlLeaseMgrTest, basicLease6) { // Get the leases to be used for the test. vector leases = createLeases6(); // 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(); Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]); ASSERT_TRUE(l_returned); detailCompareLease(leases[1], l_returned); l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); l_returned = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]); ASSERT_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_->deleteLease(ioaddress6_[1])); l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]); EXPECT_FALSE(l_returned); EXPECT_FALSE(lmptr_->deleteLease(ioaddress6_[1])); // Check that the second address is still there. l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]); ASSERT_TRUE(l_returned); detailCompareLease(leases[2], l_returned); } };