Browse Source

[4277] Insert and fetch IPv4 Hosts plus tests work (without options)

src/lib/dhcpsrv/host_data_source_factory.cc
    HostDataSourceFactory::create() - now instantiates PgSqlHostDataSource

src/lib/dhcpsrv/pgsql_host_data_source.cc
    Enabled basic IPv4 host statements and methods

src/lib/dhcpsrv/tests
    New file: pgsql_host_data_source_unittest.cc

src/lib/dhcpsrv/tests/Makefile.am
    Added pgsql_host_data_source_unittest.cc
Thomas Markwalder 8 years ago
parent
commit
8047f97887

+ 9 - 3
src/lib/dhcpsrv/host_data_source_factory.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,10 @@
 #include <dhcpsrv/mysql_host_data_source.h>
 #endif
 
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_host_data_source.h>
+#endif
+
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -63,8 +67,10 @@ HostDataSourceFactory::create(const std::string& dbaccess) {
 
 #ifdef HAVE_PGSQL
     if (db_type == "postgresql") {
-        isc_throw(NotImplemented, "Sorry, PostgreSQL backend for host reservations "
-                  "is not implemented yet.");
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB)
+            .arg(DatabaseConnection::redactedAccessString(parameters));
+        getHostDataSourcePtr().reset(new PgSqlHostDataSource(parameters));
+        return;
     }
 #endif
 

+ 66 - 78
src/lib/dhcpsrv/pgsql_host_data_source.cc

@@ -165,11 +165,11 @@ public:
     ///        None of the fields in the host reservation are modified -
     ///        the host data is only read.
     ///
-    /// @return pointer to newly constructed bind_array containing the 
+    /// @return pointer to newly constructed bind_array containing the
     /// bound values extracted from host
     ///
     /// @throw DbOperationError if bind_array cannot be populated.
-    PsqlBindArrayPtr 
+    PsqlBindArrayPtr
     createBindForSend(const HostPtr& host) {
         if (!host) {
             isc_throw(BadValue, "createBindForSend:: host object is NULL");
@@ -178,7 +178,7 @@ public:
         // Store the host to ensure bound values remain in scope
         host_ = host;
 
-        // Bind the host data to the array 
+        // Bind the host data to the array
         PsqlBindArrayPtr bind_array(new PsqlBindArray());
         try {
             // host_id : is auto_incremented skip it
@@ -195,9 +195,9 @@ public:
             // dhcp6_subnet_id : INT NULL
             bind_array->add(host->getIPv6SubnetID());
 
-            // ipv4_address : BIGINT NULL 
+            // ipv4_address : BIGINT NULL
             bind_array->add(host->getIPv4Reservation());
- 
+
             // hostname : VARCHAR(255) NULL
             bind_array->bindString(host->getHostname());
 
@@ -230,7 +230,7 @@ public:
         // host_id INT NOT NULL
         // @todo going to have to deal with uint64_t versus INT etc...
         HostID host_id;
-        getColumnValue(r, row, HOST_ID_COL, host_id); 
+        getColumnValue(r, row, HOST_ID_COL, host_id);
 
         // dhcp_identifier : BYTEA NOT NULL
         uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
@@ -250,17 +250,17 @@ public:
             static_cast<Host::IdentifierType>(type);
 
         // dhcp4_subnet_id : INT NULL
-        uint32_t subnet_id; 
-        getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id); 
+        uint32_t subnet_id;
+        getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
         SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
 
         // dhcp6_subnet_id : INT NULL
-        getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id); 
+        getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
         SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
 
         // ipv4_address : BIGINT NULL
-        uint32_t addr4; 
-        getColumnValue(r, row, IPV4_ADDRESS_COL, addr4); 
+        uint32_t addr4;
+        getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
         isc::asiolink::IOAddress ipv4_reservation(addr4);
 
         // hostname : VARCHAR(255) NULL
@@ -280,13 +280,13 @@ public:
             host.reset(new Host(identifier_value, identifier_len,
                                 identifier_type, dhcp4_subnet_id,
                                 dhcp6_subnet_id, ipv4_reservation, hostname,
-                                dhcp4_client_classes, dhcp6_client_classes)); 
+                                dhcp4_client_classes, dhcp6_client_classes));
 
             host->setHostId(host_id);
         } catch (const isc::Exception& ex) {
             isc_throw(DbOperationError, "Could not create host: " << ex.what());
         }
-        
+
         return(host);
     };
 
@@ -1348,10 +1348,12 @@ public:
         GET_HOST_DHCPID,        // Gets hosts by host identifier
 #endif
         GET_HOST_ADDR,          // Gets hosts by IPv4 address
-#if 0
         GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+#if 0
         GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+#endif
         GET_HOST_SUBID_ADDR,    // Gets host by IPv4 SubnetID and IPv4 address
+#if 0
         GET_HOST_PREFIX,        // Gets host by IPv6 prefix
 #endif
         GET_VERSION,            // Obtain version number
@@ -1373,18 +1375,18 @@ public:
     /// @param bind Vector of MYSQL_BIND objects to be used when making the
     /// query.
     /// @param return_last_id flag indicating whether or not the insert
-    /// returns the primary key of from the row inserted via " RETURNING 
+    /// returns the primary key of from the row inserted via " RETURNING
     /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
     /// clause causes the INSERT to return a result set that should consist
     /// of a single row with one column, the value of the primary key.
     /// Defaults to false.
     ///
     /// @returns 0 if return_last_id is false, otherwise it returns the
-    /// the value in the result set in the first col of the first row. 
+    /// the value in the result set in the first col of the first row.
     ///
     /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
     uint64_t addStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
-                          PsqlBindArrayPtr& bind, 
+                          PsqlBindArrayPtr& bind,
                           const bool return_last_id = false);
 
 #if 0
@@ -1509,14 +1511,14 @@ public:
 /// retrieve hosts from the database.
 PgSqlTaggedStatement tagged_statements[] = {
     // Inserts a host into the 'hosts' table. Returns the inserted host id.
-    {PgSqlHostDataSourceImpl::INSERT_HOST, 
-     { OID_INT8, OID_BYTEA, OID_INT2, 
+    { 8, // PgSqlHostDataSourceImpl::INSERT_HOST,
+     { OID_BYTEA, OID_INT2,
        OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
-       OID_VARCHAR, OID_VARCHAR },
-     "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+       OID_VARCHAR, OID_VARCHAR }, "insert_host",
+     "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
         "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
         "dhcp4_client_classes, dhcp6_client_classes) "
-     "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING host_id"},
+     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"},
 
 #if 0
     // Inserts a single IPv6 reservation into 'reservations' table.
@@ -1567,8 +1569,8 @@ PgSqlTaggedStatement tagged_statements[] = {
     // Retrieves host information along with the DHCPv4 options associated with
     // it. Left joining the dhcp4_options table results in multiple rows being
     // returned for the same host. The host is retrieved by IPv4 address.
-    {PgSqlHostDataSourceImpl::GET_HOST_ADDR,
-     { OID_INT8 },
+    { 1, // PgSqlHostDataSourceImpl::GET_HOST_ADDR,
+     { OID_INT8 }, "get_host_addr",
      "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
             "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
             "h.dhcp4_client_classes, h.dhcp6_client_classes, "
@@ -1576,26 +1578,26 @@ PgSqlTaggedStatement tagged_statements[] = {
             "o.persistent "
       "FROM hosts AS h "
       "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
-      "WHERE ipv4_address = ? "
+      "WHERE ipv4_address = $1 "
       "ORDER BY h.host_id, o.option_id"},
-#if 0
 
     // Retrieves host information and DHCPv4 options using subnet identifier
     // and client's identifier. Left joining the dhcp4_options table results in
     // multiple rows being returned for the same host.
-    {PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
-            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
-                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
-                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
-                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent "
-            "FROM hosts AS h "
-            "LEFT JOIN dhcp4_options AS o "
-                "ON h.host_id = o.host_id "
-            "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
-            "   AND h.dhcp_identifier = ? "
-            "ORDER BY h.host_id, o.option_id"},
+    { 3, //PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+     { OID_INT4, OID_INT2, OID_BYTEA }, "get_host_subid4_dhcpid",
+     "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+        "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+        "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+        "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+        "o.persistent "
+     "FROM hosts AS h "
+     "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+     "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
+     "   AND h.dhcp_identifier = $3 "
+     "ORDER BY h.host_id, o.option_id"},
 
+#if 0
     // Retrieves host information, IPv6 reservations and DHCPv6 options
     // associated with a host. The number of rows returned is a multiplication
     // of number of IPv6 reservations and DHCPv6 options.
@@ -1616,22 +1618,24 @@ PgSqlTaggedStatement tagged_statements[] = {
             "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
                 "AND h.dhcp_identifier = ? "
             "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+#endif
 
     // Retrieves host information and DHCPv4 options for the host using subnet
     // identifier and IPv4 reservation. Left joining the dhcp4_options table
     // results in multiple rows being returned for the host. The number of
     // rows depends on the number of options defined for the host.
-    {PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
-            "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
-                "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
-                "h.dhcp4_client_classes, h.dhcp6_client_classes, "
-                "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent "
-            "FROM hosts AS h "
-            "LEFT JOIN dhcp4_options AS o "
-                "ON h.host_id = o.host_id "
-            "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
-            "ORDER BY h.host_id, o.option_id"},
+    { 2,  //PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+      { OID_INT4, OID_INT8 }, "get_host_subid_addr",
+       "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+            "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+            "h.dhcp4_client_classes, h.dhcp6_client_classes, "
+            "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+            "o.persistent "
+        "FROM hosts AS h "
+        "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+        "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
+        "ORDER BY h.host_id, o.option_id"},
+#if 0
 
     // Retrieves host information, IPv6 reservations and DHCPv6 options
     // associated with a host using prefix and prefix length. This query
@@ -1660,11 +1664,12 @@ PgSqlTaggedStatement tagged_statements[] = {
 #endif
 
     // Retrieves MySQL schema version.
-    {PgSqlHostDataSourceImpl::GET_VERSION, { OID_NONE },
-            "SELECT version, minor FROM schema_version"},
+    { 0, //PgSqlHostDataSourceImpl::GET_VERSION,
+     { OID_NONE }, "get_version",
+     "SELECT version, minor FROM schema_version"},
 
     // Marks the end of the statements table.
-    {PgSqlHostDataSourceImpl::NUM_STATEMENTS, { 0 }, NULL, NULL}
+    {0, { 0 }, NULL, NULL}
 };
 
 PgSqlHostDataSourceImpl::
@@ -1836,12 +1841,12 @@ getHost(const SubnetID& subnet_id,
     // Add the subnet id.
     bind_array->add(subnet_id);
 
-    // Add the identifier value.
-    bind_array->add(identifier_begin, identifier_len);
-
     // Add the Identifier type.
     bind_array->add(static_cast<uint8_t>(identifier_type));
 
+    // Add the identifier value.
+    bind_array->add(identifier_begin, identifier_len);
+
     ConstHostCollection collection;
     getHostCollection(stindex, bind_array, exchange, collection, true);
 
@@ -1858,7 +1863,7 @@ std::pair<uint32_t, uint32_t> PgSqlHostDataSourceImpl::getVersion() const {
               DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
 
     PgSqlResult r(PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0));
-    conn_.checkStatementError(r, tagged_statements[GET_VERSION]); 
+    conn_.checkStatementError(r, tagged_statements[GET_VERSION]);
 
     istringstream tmp;
     uint32_t version;
@@ -2023,16 +2028,6 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
     return (ConstHostPtr());
 }
 
-#if 1
-ConstHostPtr
-PgSqlHostDataSource::get4(const SubnetID&,
-                          const Host::IdentifierType&,
-                          const uint8_t*,
-                          const size_t) const {
-
-    return (HostPtr());
-}
-#else
 ConstHostPtr
 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
                           const Host::IdentifierType& identifier_type,
@@ -2040,18 +2035,11 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id,
                           const size_t identifier_len) const {
 
     return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
-                   identifier_len, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
-                   impl_->host_exchange_));
+                           identifier_len,
+                           PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+                           impl_->host_exchange_));
 }
-#endif
 
-#if 1
-ConstHostPtr
-PgSqlHostDataSource::get4(const SubnetID&,
-                          const asiolink::IOAddress&) const {
-    return(HostPtr());
-}
-#else
 ConstHostPtr
 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
                           const asiolink::IOAddress& address) const {
@@ -2066,7 +2054,8 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id,
 
     ConstHostCollection collection;
     impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
-                             inbind, impl_->host_exchange_, collection, true);
+                             bind_array, impl_->host_exchange_, collection,
+                             true);
 
     // Return single record if present, else clear the host.
     ConstHostPtr result;
@@ -2075,7 +2064,6 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id,
 
     return (result);
 }
-#endif
 
 #if 1
 ConstHostPtr

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -120,6 +120,7 @@ libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
 
 if HAVE_PGSQL
 libdhcpsrv_unittests_SOURCES += pgsql_exchange_unittest.cc
+libdhcpsrv_unittests_SOURCES += pgsql_host_data_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
 endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc

+ 474 - 0
src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc

@@ -0,0 +1,474 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/pgsql_connection.h>
+#include <dhcpsrv/pgsql_host_data_source.h>
+#include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/testutils/pgsql_schema.h>
+#include <dhcpsrv/host_data_source_factory.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+class PgSqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+    /// @brief Constructor
+    ///
+    /// Deletes everything from the database and opens it.
+    PgSqlHostDataSourceTest() {
+
+        // Ensure schema is the correct one.
+        destroyPgSQLSchema();
+        createPgSQLSchema();
+
+        // Connect to the database
+        try {
+            HostDataSourceFactory::create(validPgSQLConnectionString());
+        } 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;
+        }
+
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+
+    /// @brief Destructor
+    ///
+    /// Rolls back all pending transactions.  The deletion of myhdsptr_ will close
+    /// the database.  Then reopen it and delete everything created by the test.
+    virtual ~PgSqlHostDataSourceTest() {
+        hdsptr_->rollback();
+        HostDataSourceFactory::destroy();
+        destroyPgSQLSchema();
+    }
+
+    /// @brief Reopen the database
+    ///
+    /// Closes the database and re-open it.  Anything committed should be
+    /// visible.
+    ///
+    /// Parameter is ignored for PostgreSQL backend as the v4 and v6 leases share
+    /// the same database.
+    void reopen(Universe) {
+        HostDataSourceFactory::destroy();
+        HostDataSourceFactory::create(validPgSQLConnectionString());
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the PgSqlHostDataSource 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(PgSqlHostDataSource, OpenDatabase) {
+
+    // Schema needs to be created for the test to work.
+    destroyPgSQLSchema();
+    createPgSQLSchema();
+
+    // Check that lease manager open the database opens correctly and tidy up.
+    //  If it fails, print the error message.
+    try {
+        HostDataSourceFactory::create(validPgSQLConnectionString());
+        EXPECT_NO_THROW((void) HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::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 lease manager open the database opens correctly with a longer
+    // timeout.  If it fails, print the error message.
+    try {
+        string connection_string = validPgSQLConnectionString() + string(" ") +
+                                   string(VALID_TIMEOUT);
+        HostDataSourceFactory::create(connection_string);
+        EXPECT_NO_THROW((void) HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::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_FALSE(HostDataSourceFactory::getHostDataSourcePtr());
+
+    // Check that wrong specification of backend throws an exception.
+    // (This is really a check on LeaseMgrFactory, but is convenient to
+    // perform here.)
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        InvalidParameter);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        InvalidType);
+
+    // Check that invalid login data causes an exception.
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+        DbOpenError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+        DbInvalidTimeout);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+        DbInvalidTimeout);
+
+    // Check for missing parameters
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+        PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        NoDatabaseName);
+
+    // Tidy up after the test
+    destroyPgSQLSchema();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses hw address as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4HWAddr) {
+    testBasic4(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses client-id (DUID) as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4ClientId) {
+    testBasic4(Host::IDENT_DUID);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4HWaddr) {
+    testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4ClientId) {
+    testGetByIPv4(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get4ByHWaddr) {
+    testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// DUID.
+TEST_F(PgSqlHostDataSourceTest, get4ByDUID) {
+    testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// circuit id.
+TEST_F(PgSqlHostDataSourceTest, get4ByCircuitId) {
+    testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId1) {
+    testHWAddrNotClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId2) {
+    testClientIdNotHWAddr();
+}
+
+// Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDN) {
+    testHostname("foo.example.org", 1);
+}
+
+// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+// retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDN100) {
+    testHostname("foo.example.org", 100);
+}
+
+// Test verifies if a host without any hostname specified can be stored and 
+// later retrieved.
+TEST_F(PgSqlHostDataSourceTest, noHostname) {
+    testHostname("", 1);
+}
+
+// Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with hardware address X, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies if the hardware or client-id query can match client-id.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with client identifier Y, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+#if 0
+// Test verifies that host with IPv6 address and DUID can be added and
+// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+// Test verifies that host with IPv6 address and HWAddr can be added and
+// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithHWAddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+// Test verifies that host with IPv6 prefix and DUID can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+// Test verifies that host with IPv6 prefix and HWAddr can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithHWaddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get6ByHWaddr) {
+    testGet6ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(PgSqlHostDataSourceTest, get6ByClientId) {
+    testGet6ByClientId();
+}
+
+// Test verifies if a host reservation can be stored with both IPv6 address and
+// prefix.
+TEST_F(PgSqlHostDataSourceTest, addr6AndPrefix) {
+    testAddr6AndPrefix();
+}
+
+// Tests if host with multiple IPv6 reservations can be added and then
+// retrieved correctly. Test checks reservations comparing.
+TEST_F(PgSqlHostDataSourceTest, multipleReservations){
+    testMultipleReservations();
+}
+
+// Tests if compareIPv6Reservations() method treats same pool of reservations
+// but added in different order as equal.
+TEST_F(PgSqlHostDataSourceTest, multipleReservationsDifferentOrder){
+    testMultipleReservationsDifferentOrder();
+}
+
+// Test verifies if multiple client classes for IPv4 can be stored.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_multipleClientClasses4) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for IPv6 can be stored.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_multipleClientClasses6) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v6 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for both IPv4 and IPv6 can be stored.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_multipleClientClassesBoth) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 and v6 client-classes, retrieve
+    /// it and make sure that all client classes are retrieved properly. Also,
+    /// check that the classes are not confused.
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same hardware address). The test logic is as follows:
+// Insert 10 host reservations for a given physical host (the same
+// hardware address), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them all correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsHWAddr) {
+    testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same client identifier). The test logic is as follows:
+//
+// Insert 10 host reservations for a given physical host (the same
+// client-identifier), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsClientId) {
+    testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+// Test if host reservations made for different IPv6 subnets are handled correctly.
+// The test logic is as follows:
+//
+// Insert 10 host reservations for different subnets. Make sure that
+// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(PgSqlHostDataSourceTest, subnetId6) {
+    testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same DUID.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithDUID) {
+    testAddDuplicate6WithSameDUID();
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same HWAddr.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithHWAddr) {
+    testAddDuplicate6WithSameHWAddr();
+}
+
+// Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate4) {
+    testAddDuplicate4();
+}
+
+// This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations4) {
+    testOptionsReservations4(false);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations6) {
+    testOptionsReservations6(false);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations46) {
+    testOptionsReservations46(false);
+}
+
+// This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations4) {
+    testOptionsReservations4(true);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations6) {
+    testOptionsReservations6(true);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+// textual format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations46) {
+    testOptionsReservations46(true);
+}
+
+// This test checks transactional insertion of the host information
+// into the database. The failure to insert host information at
+// any stage should cause the whole transaction to be rolled back.
+TEST_F(PgSqlHostDataSourceTest, testAddRollback) {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // To test the transaction rollback mechanism we need to cause the
+    // insertion of host information to fail at some stage. The 'hosts'
+    // table should be updated correctly but the failure should occur
+    // when inserting reservations or options. The simplest way to
+    // achieve that is to simply drop one of the tables. To do so, we
+    // connect to the database and issue a DROP query.
+    PgSqlConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["user"] = "keatest";
+    params["password"] = "keatest";
+    PgSqlConnection conn(params);
+    ASSERT_NO_THROW(conn.openDatabase());
+    int status = mysql_query(conn.mysql_,
+                             "DROP TABLE IF EXISTS ipv6_reservations");
+    ASSERT_EQ(0, status) << mysql_error(conn.mysql_);
+
+    // Create a host with a reservation.
+    HostPtr host = initializeHost6("2001:db8:1::1", Host::IDENT_HWADDR, false);
+    // Let's assign some DHCPv4 subnet to the host, because we will use the
+    // DHCPv4 subnet to try to retrieve the host after failed insertion.
+    host->setIPv4SubnetID(SubnetID(4));
+
+    // There is no ipv6_reservations table, so the insertion should fail.
+    ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+    // Even though we have created a DHCPv6 host, we can't use get6()
+    // method to retrieve the host from the database, because the
+    // query would expect that the ipv6_reservations table is present.
+    // Therefore, the query would fail. Instead, we use the get4 method
+    // which uses the same client identifier, but doesn't attempt to
+    // retrieve the data from ipv6_reservations table. The query should
+    // pass but return no host because the (insert) transaction is expected
+    // to be rolled back.
+    ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+                                          host->getIdentifierType(),
+                                          &host->getIdentifier()[0],
+                                          host->getIdentifier().size());
+    EXPECT_FALSE(from_hds);
+}
+#endif
+
+}; // Of anonymous namespace