# ... and at the shell level, so Makefile.am can take action depending on this.
AM_CONDITIONAL(HAVE_MYSQL, test "$MYSQL_CONFIG" != "")
+pg_config="no"
+AC_ARG_WITH([dhcp-pgsql],
+ AC_HELP_STRING([--with-dhcp-pgsql=PATH],
+ [path to the PostgreSQL 'pg_config' script]),
+ [pg_config="$withval"])
+
+if test "${pg_config}" = "yes" ; then
+ PG_CONFIG="/usr/bin/pg_config"
+elif test "${pg_config}" != "no" ; then
+ PG_CONFIG="${withval}"
+fi
+
+if test "$PG_CONFIG" != "" ; then
+ if test -d "$PG_CONFIG" -o ! -x "$PG_CONFIG" ; then
+ AC_MSG_ERROR([--with-dhcp-pgsql should point to a pg_config program])
+ fi
+
+ PGSQL_CPPFLAGS=`$PG_CONFIG --cppflags`
+ PGSQL_INCLUDEDIR=`$PG_CONFIG --includedir`
+ PGSQL_CPPFLAGS="$PGSQL_CPPFLAGS -I$PGSQL_INCLUDEDIR"
+ PGSQL_LIBS=`$PG_CONFIG --ldflags`
+ PGSQL_LIBS="$PGSQL_LIBS -lpq"
+ PGSQL_VERSION=`$PG_CONFIG --version`
+
+ AC_SUBST(PGSQL_CPPFLAGS)
+ AC_SUBST(PGSQL_LIBS)
+
+ # Check that a simple program using PostgreSQL functions can compile and link.
+ CPPFLAGS_SAVED="$CPPFLAGS"
+ LIBS_SAVED="$LIBS"
+
+ CPPFLAGS="$PGSQL_CPPFLAGS $CPPFLAGS"
+ LIBS="$PGSQL_LIBS $LIBS"
+
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM([#include <libpq-fe.h>],
+ [PGconn * c = PQconnectdb("dbname = 'postgres'");
+ PQfinish(c);])],
+ [AC_MSG_RESULT([checking for PostgreSQL headers and library... yes])],
+ [AC_MSG_RESULT([checking for PostgreSQL headers and library... no])
+ AC_MSG_ERROR([Needs PostgreSQL library])]
+ )
+
+ CPPFLAGS=$CPPFLAGS_SAVED
+ LIBS=$LIBS_SAVED
+
+ # Note that PostgreSQL is present in the config.h file
+ AC_DEFINE([HAVE_PGSQL], [1], [PostgreSQL is present])
+fi
+
+# ... and at the shell level, so Makefile.am can take action depending on this.
+AM_CONDITIONAL(HAVE_PGSQL, test "$PG_CONFIG" != "")
# Check for log4cplus
log4cplus_path="yes"
MYSQL_CPPFLAGS: ${MYSQL_CPPFLAGS}
MYSQL_LIBS: ${MYSQL_LIBS}
END
+else
+cat >> config.report << END
+
+MySQL: no
+END
+fi
+
+if test "$PGSQL_CPPFLAGS" != "" ; then
+cat >> config.report << END
+
+PostgreSQL:
+ PGSQL_VERSION: ${PGSQL_VERSION}
+ PGSQL_CPPFLAGS: ${PGSQL_CPPFLAGS}
+ PGSQL_LIBS: ${PGSQL_LIBS}
+END
+else
+cat >> config.report << END
+
+PostgreSQL: no
+END
fi
if test "$enable_gtest" != "no"; then
if HAVE_MYSQL
AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
endif
+if HAVE_PGSQL
+AM_CPPFLAGS += $(PGSQL_CPPFLAGS)
+endif
AM_CXXFLAGS = $(B10_CXXFLAGS)
if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
+if HAVE_PGSQL
+libb10_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
+endif
libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
if HAVE_MYSQL
libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
endif
+if HAVE_PGSQL
+libb10_dhcpsrv_la_LDFLAGS += $(PGSQL_LIBS)
+endif
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
EXTRA_DIST = dhcpsrv_messages.mes
# Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
-dist_pkgdata_DATA = dhcpdb_create.mysql
+EXTRA_DIST += dhcpdb_create.mysql dhcpdb_create.pgsql database_backends.dox libdhcpsrv.dox
+dist_pkgdata_DATA = dhcpdb_create.mysql dhcpdb_create.pgsql
install-data-local:
$(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
The unit tests are run automatically when "make check" is executed (providing
that BIND 10 has been build with the \--with-dhcp-mysql switch (see the installation
section in the <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND 10 Guide</a>).
+
+ @subsection dhcp-pgsql-unittest PostgreSQL unit-tests
+
+ Conceptually, the steps required to run PostgreSQL unit-tests are the same as
+ in MySQL. First, a database called <i>keatest</i> must be created. A database
+ user, also called <i>keatest</i> (that will be allowed to log in using password
+ <i>keatest</i>) must be created and given full privileges in that database. The
+ unit tests create the schema in the database before each test and delete it
+ afterwards.
+
+ PostgreSQL set up differs from system to system. Please consult your OS-specific
+ PostgreSQL documentation. The remainder of that section uses Ubuntu 13.10 x64 as
+ example. On Ubuntu, after installing PostgreSQL (with <tt>sudo apt-get install
+ postgresql</tt>), it is installed as user <i>postgres</i>. To create new databases
+ or add new users, initial commands must be issued as user postgres:
+
+@verbatim
+$ sudo -u postgres psql postgres
+[sudo] password for thomson:
+psql (9.1.12)
+Type "help" for help.
+postgres=# CREATE USER keatest WITH PASSWORD 'keatest';
+CREATE ROLE
+postgres=# CREATE DATABASE keatest;
+CREATE DATABASE
+postgres=# GRANT ALL PRIVILEGES ON DATABASE keatest TO keatest;
+GRANT
+postgres=# \q
+@endverbatim
+
+ Now we are back to our regular, unprivileged user. Try to log into the newly
+ created database using keatest credentials:
+@verbatim
+$ psql -d keatest -U keatest
+$ psql keatest -U keatest -W
+Password for user keatest:
+psql (9.1.12)
+Type "help" for help.
+
+keatest=>
+@endverbatim
+
+ If instead of seeing keatest=> prompt, your login will be refused with error
+ code about failed peer or indent authentication, it means that PostgreSQL is
+ configured to check unix username and reject login attepts if PostgreSQL names
+ are different. To alter that, PostgreSQL configuration must be changed.
+ Alternatively, you may set up your environment, so the tests would be run from
+ unix account keatest. <tt>/etc/postgresql/9.1/main/pg_hba.conf</tt> config file
+ had to betweaked. It may be in a different location in your system. The following
+ lines:
+
+@verbatim
+local all all peer
+host all all 127.0.0.1/32 md5
+host all all ::1/128 md5
+@endverbatim
+
+ were replaced with:
+
+@verbatim
+local all all password
+host all all 127.0.0.1/32 password
+host all all ::1/128 password
+@endverbatim
+
+ Please consult your PostgreSQL user manual before applying those changes as
+ those changes may expose your other databases that you run on the same system.
+ In general case, it is a poor idea to run anything of value on a system
+ that runs tests. Use caution!
+
+ The unit tests are run automatically when "make check" is executed (providing
+ that BIND 10 has been build with the \--with-dhcp-pgsql switch (see the installation
+ section in the <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10 Guide</a>).
+
*/
// b. Check if the 'type; keyword known and throw an exception if not.
string dbtype = type_ptr->second;
- if ((dbtype != "memfile") && (dbtype != "mysql")) {
+ if ((dbtype != "memfile") && (dbtype != "mysql") && (dbtype != "postgresql")) {
isc_throw(BadValue, "unknown backend database type: " << dbtype);
}
--- /dev/null
+-- Copyright (C) 2012-2013 Internet Systems Consortium.
+
+-- Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+-- DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+-- INTERNET SYSTEMS CONSORTIUM 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.
+
+-- This is the BIND 10 DHCP schema specification for PostgreSQL.
+
+-- The schema is reasonably portable (with the exception of the engine
+-- specification, which is MySQL-specific). Minor changes might be needed for
+-- other databases.
+
+-- To create the schema, either type the command:
+
+-- psql -U <user> -W <password> <database> < dhcpdb_create.pgsql
+
+-- ... at the command prompt, or log in to the PostgreSQL database and at the "postgres=#"
+-- prompt, issue the command:
+
+-- @dhcpdb_create.pgsql
+
+
+-- Holds the IPv4 leases.
+CREATE TABLE lease4 (
+ address BIGINT PRIMARY KEY NOT NULL, -- IPv4 address
+ hwaddr BYTEA, -- Hardware address
+ client_id BYTEA, -- Client ID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP, -- Expiration time of the lease
+ subnet_id BIGINT -- Subnet identification
+ );
+
+
+-- Create search indexes for lease4 table
+-- index by hwaddr and subnet_id
+CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id);
+
+-- index by client_id and subnet_id
+CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id);
+
+-- Holds the IPv6 leases.
+-- N.B. The use of a VARCHAR for the address is temporary for development:
+-- it will eventually be replaced by BINARY(16).
+CREATE TABLE lease6 (
+ address VARCHAR(39) PRIMARY KEY NOT NULL, -- IPv6 address
+ duid BYTEA, -- DUID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP, -- Expiration time of the lease
+ subnet_id BIGINT, -- Subnet identification
+ pref_lifetime BIGINT, -- Preferred lifetime
+ lease_type SMALLINT, -- Lease type (see lease6_types
+ -- table for possible values)
+ iaid INT, -- See Section 10 of RFC 3315
+ prefix_len SMALLINT -- For IA_PD only
+ );
+
+-- Create search indexes for lease4 table
+-- index by iaid, subnet_id, and duid
+CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid);
+
+-- ... and a definition of lease6 types. This table is a convenience for
+-- users of the database - if they want to view the lease table and use the
+-- type names, they can join this table with the lease6 table
+CREATE TABLE lease6_types (
+ lease_type SMALLINT PRIMARY KEY NOT NULL, -- Lease type code.
+ name VARCHAR(5) -- Name of the lease type
+ );
+START TRANSACTION;
+INSERT INTO lease6_types VALUES (0, 'IA_NA'); -- Non-temporary v6 addresses
+INSERT INTO lease6_types VALUES (1, 'IA_TA'); -- Temporary v6 addresses
+INSERT INTO lease6_types VALUES (2, 'IA_PD'); -- Prefix delegations
+COMMIT;
+
+-- Finally, the version of the schema. We start at 0.1 during development.
+-- This table is only modified during schema upgrades. For historical reasons
+-- (related to the names of the columns in the BIND 10 DNS database file), the
+-- first column is called "version" and not "major".
+
+-- NOTE: this MUST be kept in step with src/lib/dhcpsrv/tests/schema_copy.h,
+-- which defines the schema for the unit tests. If you are updating
+-- the version number, the schema has changed: please ensure that
+-- schema_copy.h has been updated as well.
+CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL, -- Major version number
+ minor INT -- Minor version number
+ );
+START TRANSACTION;
+INSERT INTO schema_version VALUES (1, 0);
+COMMIT;
+
+-- Notes:
+
+-- Indexes
+-- =======
+-- It is likely that additional indexes will be needed. However, the
+-- increase in lookup performance from these will come at the expense
+-- of a decrease in performance during insert operations due to the need
+-- to update the indexes. For this reason, the need for additional indexes
+-- will be determined by experiment during performance tests.
+
+-- The most likely additional indexes will cover the following columns:
+
+-- expire
+-- To speed up the deletion of expired leases from the database.
+
+-- hwaddr and client_id
+-- For lease stability: if a client requests a new lease, try to find an
+-- existing or recently expired lease for it so that it can keep using the
+-- same IP address.
+
+-- Field Sizes
+-- ===========
+-- If any of the VARxxx field sizes are altered, the lengths in the MySQL
+-- backend source file (mysql_lease_mgr.cc) must be correspondingly changed.
+
+-- Portability
+-- ===========
+-- Some columns contain binary data so are stored as BYTEA instead of
+-- VARCHAR. This may be non-portable between databases: in this case, the
+-- definition should be changed to VARCHAR.
A debug message issued when the server is attempting to update IPv6
lease from the MySQL database for the specified address.
+% DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the PostgreSQL backend database.
+
+% DHCPSRV_PGSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the PostgreSQL backend database.
+
+% DHCPSRV_PGSQL_COMMIT committing to MySQL database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the PostgreSQL settings,
+the committal may not include a write to disk.
+
+% DHCPSRV_PGSQL_DB opening PostgreSQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a PostgreSQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_PGSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_GET_ADDR6 obtaining IPv6 lease for address %1 (lease type %2)
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the PostgreSQL database for a client with the specified
+client identification.
+
+% DHCPSRV_PGSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the PostgreSQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_PGSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2, lease type %3
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the PostgreSQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_PGSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the PostgreSQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_PGSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_PGSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_PGSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_ROLLBACK rolling back PostgreSQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_PGSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the PostgreSQL database for the specified address.
+
% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
This is an error message, logged when an attempt has been made to access
a database backend, but where no 'type' keyword has been included in
#ifdef HAVE_MYSQL
#include <dhcpsrv/mysql_lease_mgr.h>
#endif
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#endif
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
+#endif
+#ifdef HAVE_PGSQL
+ if (parameters[type] == string("postgresql")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
+ getLeaseMgrPtr().reset(new PgSqlLeaseMgr(parameters));
+ return;
+ }
#endif
if (parameters[type] == string("memfile")) {
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_DB).arg(redacted);
--- /dev/null
+// Copyright (C) 2012 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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+
+#include <boost/static_assert.hpp>
+
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <time.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace {
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
+
+struct TaggedStatement {
+ PgSqlLeaseMgr::StatementIndex index;
+ int nbparams;
+ const Oid types[10];
+ const char * name;
+ const char * text;
+};
+
+TaggedStatement tagged_statements[] = {
+ {PgSqlLeaseMgr::DELETE_LEASE4, 1,
+ { 20 },
+ "delete_lease4",
+ "DELETE FROM lease4 WHERE address = $1"},
+ {PgSqlLeaseMgr::DELETE_LEASE6, 1,
+ { 1043 },
+ "delete_lease6",
+ "DELETE FROM lease6 WHERE address = $1"},
+ {PgSqlLeaseMgr::GET_LEASE4_ADDR, 1,
+ { 20 },
+ "get_lease4_addr",
+ "DECLARE get_lease4_addr CURSOR FOR "
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire), subnet_id "
+ "FROM lease4 "
+ "WHERE address = $1"},
+ {PgSqlLeaseMgr::GET_LEASE4_CLIENTID, 1,
+ { 17 },
+ "get_lease4_clientid",
+ "DECLARE get_lease4_clientid CURSOR FOR "
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire), subnet_id "
+ "FROM lease4 "
+ "WHERE client_id = $1"},
+ {PgSqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID, 2,
+ { 17, 20 },
+ "get_lease4_clientid_subid",
+ "DECLARE get_lease4_clientid_subid CURSOR FOR "
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id "
+ "FROM lease4 "
+ "WHERE client_id = $1 AND subnet_id = $2"},
+ {PgSqlLeaseMgr::GET_LEASE4_HWADDR, 1,
+ { 17 },
+ "get_lease4_hwaddr",
+ "DECLARE get_lease4_hwaddr CURSOR FOR "
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id "
+ "FROM lease4 "
+ "WHERE hwaddr = $1"},
+ {PgSqlLeaseMgr::GET_LEASE4_HWADDR_SUBID, 2,
+ { 17, 20 },
+ "get_lease4_hwaddr_subid",
+ "DECLARE get_lease4_hwaddr_subid CURSOR FOR "
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id "
+ "FROM lease4 "
+ "WHERE hwaddr = $1 AND subnet_id = $2"},
+ {PgSqlLeaseMgr::GET_LEASE6_ADDR, 1,
+ { 1043 },
+ "get_lease6_addr",
+ "DECLARE get_lease6_addr CURSOR FOR "
+ "SELECT address, duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len "
+ "FROM lease6 "
+ "WHERE address = $1"},
+ {PgSqlLeaseMgr::GET_LEASE6_DUID_IAID, 2,
+ { 17, 20 },
+ "get_lease6_duid_iaid",
+ "DECLARE get_lease6_duid_iaid CURSOR FOR "
+ "SELECT address, duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len "
+ "FROM lease6 "
+ "WHERE duid = $1 AND iaid = $2"},
+ {PgSqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID, 3,
+ { 17, 20, 20 },
+ "get_lease6_duid_iaid_subid",
+ "DECLARE get_lease6_duid_iaid_subid CURSOR FOR "
+ "SELECT address, duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len "
+ "FROM lease6 "
+ "WHERE duid = $1 AND iaid = $2 AND subnet_id = $3"},
+ {PgSqlLeaseMgr::GET_VERSION, 0,
+ { 0 },
+ "get_version",
+ "DECLARE get_version CURSOR FOR "
+ "SELECT version, minor FROM schema_version"},
+ {PgSqlLeaseMgr::INSERT_LEASE4, 6,
+ { 20, 17, 17, 20, 1114, 20 },
+ "insert_lease4",
+ "INSERT INTO lease4(address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6)"},
+ {PgSqlLeaseMgr::INSERT_LEASE6, 9,
+ { 1043, 17, 20, 1114, 20,
+ 20, 21, 20, 21 },
+ "insert_lease6",
+ "INSERT INTO lease6(address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"},
+ {PgSqlLeaseMgr::UPDATE_LEASE4, 7,
+ { 20, 17, 17, 20, 1114, 20, 20 },
+ "update_lease4",
+ "UPDATE lease4 SET address = $1, hwaddr = $2, "
+ "client_id = $3, valid_lifetime = $4, expire = $5, "
+ "subnet_id = $6 "
+ "WHERE address = $7"},
+ {PgSqlLeaseMgr::UPDATE_LEASE6, 10,
+ { 1043, 17, 20, 1114, 20,
+ 20, 21, 20, 21, 1043 },
+ "update_lease6",
+ "UPDATE lease6 SET address = $1, duid = $2, "
+ "valid_lifetime = $3, expire = $4, subnet_id = $5, "
+ "pref_lifetime = $6, lease_type = $7, iaid = $8, "
+ "prefix_len = $9 "
+ "WHERE address = $10"},
+ // End of list sentinel
+ {PgSqlLeaseMgr::NUM_STATEMENTS, 0, { 0 }, NULL, NULL}
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+
+class PgSqlLeaseExchange {
+protected:
+ bindparams params;
+ void
+ convertToTimestamp(const time_t & expire_,
+ char buffer_[20]) {
+ struct tm tinfo;
+ localtime_r(&expire_, &tinfo);
+ strftime(buffer_, 20, "%Y-%m-%d %H:%M:%S", &tinfo);
+ }
+};
+
+class PgSqlLease4Exchange : public PgSqlLeaseExchange {
+ static const size_t LEASE_COLUMNS = 6;
+public:
+ PgSqlLease4Exchange() : addr4_(0) {
+ memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+ memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+
+ // Set the column names (for error messages)
+ columns_[0] = "address";
+ columns_[1] = "hwaddr";
+ columns_[2] = "client_id";
+ columns_[3] = "valid_lifetime";
+ columns_[4] = "expire";
+ columns_[5] = "subnet_id";
+
+ BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
+
+ params.reserve(LEASE_COLUMNS);
+ }
+
+ bindparams
+ createBindForSend(const Lease4Ptr& lease) {
+ lease_ = lease;
+ params.clear();
+ ostringstream tmp;
+
+ tmp << lease_->addr_;
+ PgSqlParam paddr4 = { .value = tmp.str() };
+
+ params.push_back(paddr4);
+ tmp.str("");
+ tmp.clear();
+
+ uint8_t* data = const_cast<uint8_t *>(&(lease_->hwaddr_[0]));
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(data), .isbinary = 1, .binarylen = static_cast<int>(lease_->hwaddr_.size()) };
+ params.push_back(pdest);
+
+ if(lease_->client_id_) {
+ vector<uint8_t> client_data = lease_->client_id_->getClientId();
+ PgSqlParam pclient_dest = { .value = reinterpret_cast<char *>(&client_data[0]), .isbinary = 1, .binarylen = static_cast<int>(lease_->client_id_->getClientId().size()) };
+ params.push_back(pclient_dest);
+ } else {
+ params.push_back(PgSqlParam());
+ }
+
+ string valid_lft_str;
+ tmp << static_cast<unsigned long>(lease_->valid_lft_);
+ PgSqlParam pvalid_lft = { .value = tmp.str() };
+ params.push_back(pvalid_lft);
+ tmp.str("");
+ tmp.clear();
+ time_t expire_ = lease_->valid_lft_ + lease_->cltt_;
+ char buffer_[20] = { 0 };
+ convertToTimestamp(expire_, buffer_);
+ PgSqlParam pbuffer = { .value = buffer_ };
+ params.push_back(pbuffer);
+ string subnet_id_str;
+ tmp << static_cast<unsigned long>(lease_->subnet_id_);
+ PgSqlParam psubnet_id = { .value = tmp.str() };
+ params.push_back(psubnet_id);
+
+ BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
+
+ return (params);
+ }
+
+ Lease4Ptr
+ convertFromDatabase(PGresult *& r, int line) {
+ const char * addr4_str = PQgetvalue(r, line, 0);
+ unsigned char * hwaddr_str = PQunescapeBytea(
+ (const unsigned char *)
+ PQgetvalue(r, line, 1),
+ & hwaddr_length_);
+ unsigned char * client_id_str = PQunescapeBytea(
+ (const unsigned char *)
+ PQgetvalue(r, line, 2),
+ & client_id_length_);
+ const char * valid_lifetime_str = PQgetvalue(r, line, 3);
+ const char * expire_str = PQgetvalue(r, line, 4);
+ const char * subnet_id_str = PQgetvalue(r, line, 5);
+ unsigned long valid_lifetime, expire, subnet_id;
+
+ istringstream tmp;
+ tmp.str(addr4_str);
+ tmp >> addr4_;
+ tmp.str("");
+ tmp.clear();
+
+ memcpy(hwaddr_buffer_, hwaddr_str, hwaddr_length_);
+ memcpy(client_id_buffer_, client_id_str, client_id_length_);
+
+ PQfreemem(client_id_str);
+ PQfreemem(hwaddr_str);
+
+ tmp.str(valid_lifetime_str);
+ tmp >> valid_lifetime;
+ tmp.str("");
+ tmp.clear();
+ valid_lifetime_ = static_cast<uint32_t>(valid_lifetime);
+
+ tmp.str(expire_str);
+ tmp >> expire;
+ tmp.str("");
+ tmp.clear();
+ expire_ = static_cast<uint32_t>(expire);
+
+ tmp.str(subnet_id_str);
+ tmp >> subnet_id;
+ subnet_id_ = static_cast<uint32_t>(subnet_id);
+
+ time_t cltt = expire_ - valid_lifetime_;
+
+ return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
+ client_id_buffer_, client_id_length_,
+ valid_lifetime_, 0, 0, cltt, subnet_id_)));
+ }
+
+private:
+ uint32_t addr4_;
+ string columns_[LEASE_COLUMNS];
+ vector<uint8_t> hwaddr_;
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
+ uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+ Lease4Ptr lease_;
+ time_t expire_;
+ uint32_t subnet_id_;
+ uint32_t valid_lifetime_;
+ unsigned long hwaddr_length_;
+ unsigned long client_id_length_;
+};
+
+class PgSqlLease6Exchange : public PgSqlLeaseExchange {
+ static const size_t LEASE_COLUMNS = 9;
+public:
+ PgSqlLease6Exchange() {
+ memset(duid_buffer_, 0, sizeof(duid_buffer_));
+ // Set the column names (for error messages)
+ columns_[0] = "address";
+ columns_[1] = "duid";
+ columns_[2] = "valid_lifetime";
+ columns_[3] = "expire";
+ columns_[4] = "subnet_id";
+ columns_[5] = "pref_lifetime";
+ columns_[6] = "lease_type";
+ columns_[7] = "iaid";
+ columns_[8] = "prefix_len";
+ BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
+
+ params.reserve(LEASE_COLUMNS);
+ }
+
+ bindparams
+ createBindForSend(const Lease6Ptr& lease) {
+ lease_ = lease;
+ params.clear();
+ ostringstream tmp;
+
+ PgSqlParam paddr6 = { .value = lease_->addr_.toText() };
+
+ params.push_back(paddr6);
+ vector<uint8_t> duid_data = lease_->duid_->getDuid();
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(&duid_data[0])
+ , .isbinary = 1, .binarylen = static_cast<int>(lease_->duid_->getDuid().size()) };
+ params.push_back(pdest);
+
+ string valid_lft_str;
+ tmp << static_cast<unsigned long>(lease_->valid_lft_);
+ PgSqlParam pvalid_lft = { .value = tmp.str() };
+ params.push_back(pvalid_lft);
+ tmp.str("");
+ tmp.clear();
+
+ time_t expire_ = lease_->valid_lft_ + lease_->cltt_;
+ char buffer_[20] = { 0 };
+ convertToTimestamp(expire_, buffer_);
+ PgSqlParam pbuffer = { .value = buffer_ };
+ params.push_back(pbuffer);
+
+ tmp << static_cast<unsigned long>(lease_->subnet_id_);
+ PgSqlParam psubnet_id = { .value = tmp.str() };
+ params.push_back(psubnet_id);
+ tmp.str("");
+ tmp.clear();
+
+ tmp << static_cast<unsigned long>(lease_->preferred_lft_);
+ PgSqlParam preferred_lft = { .value = tmp.str() };
+ params.push_back(preferred_lft);
+ tmp.str("");
+ tmp.clear();
+
+ tmp << static_cast<unsigned int>(lease_->type_);
+ PgSqlParam type = { .value = tmp.str() };
+ params.push_back(type);
+ tmp.str("");
+ tmp.clear();
+
+ tmp << static_cast<unsigned long>(lease_->iaid_);
+ PgSqlParam iaid = { .value = tmp.str() };
+ params.push_back(iaid);
+ tmp.str("");
+ tmp.clear();
+
+ tmp << static_cast<unsigned int>(lease_->prefixlen_);
+ PgSqlParam prefixlen = { .value = tmp.str() };
+ params.push_back(prefixlen);
+
+ BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
+
+ return (params);
+ }
+
+ Lease6Ptr
+ convertFromDatabase(PGresult * r, int line) {
+ const char * addr6_str = PQgetvalue(r, line, 0);
+ unsigned char * duid_str = PQunescapeBytea(
+ (const unsigned char *)
+ PQgetvalue(r, line, 1),
+ & duid_length_);
+ const char * valid_lifetime_str = PQgetvalue(r, line, 2);
+ const char * expire_str = PQgetvalue(r, line, 3);
+ const char * subnet_id_str = PQgetvalue(r, line, 4);
+ const char * pref_lifetime_str = PQgetvalue(r, line, 5);
+ const char * lease_type_str = PQgetvalue(r, line, 6);
+ const char * iaid_str = PQgetvalue(r, line, 7);
+ const char * prefixlen_str = PQgetvalue(r, line, 8);
+ unsigned int lease_type, prefixlen;
+ unsigned long valid_lifetime, expire, subnet_id, pref_lifetime, iaid;
+
+ istringstream tmp;
+
+ addr6_ = addr6_str;
+ isc::asiolink::IOAddress addr(addr6_);
+
+ memcpy(duid_buffer_, duid_str, duid_length_);
+
+ PQfreemem(duid_str);
+
+ tmp.str(valid_lifetime_str);
+ tmp >> valid_lifetime;
+ tmp.str("");
+ tmp.clear();
+ valid_lifetime_ = static_cast<uint32_t>(valid_lifetime);
+
+ tmp.str(expire_str);
+ tmp >> expire;
+ tmp.str("");
+ tmp.clear();
+ expire_ = static_cast<uint32_t>(expire);
+
+ tmp.str(subnet_id_str);
+ tmp >> subnet_id;
+ tmp.str("");
+ tmp.clear();
+ subnet_id_ = static_cast<uint32_t>(subnet_id);
+
+ tmp.str(pref_lifetime_str);
+ tmp >> pref_lifetime;
+ tmp.str("");
+ tmp.clear();
+ pref_lifetime_ = static_cast<uint32_t>(pref_lifetime);
+
+ tmp.str(lease_type_str);
+ tmp >> lease_type;
+ tmp.str("");
+ tmp.clear();
+
+ tmp.str(iaid_str);
+ tmp >> iaid;
+ tmp.str("");
+ tmp.clear();
+ iaid_ = static_cast<uint32_t>(iaid);
+
+ tmp.str(prefixlen_str);
+ tmp >> prefixlen;
+ prefixlen_ = static_cast<uint8_t>(prefixlen);
+
+ Lease6::Type type = Lease6::TYPE_NA;
+ switch (lease_type) {
+ case Lease6::TYPE_NA:
+ type = Lease6::TYPE_NA;
+ break;
+
+ case Lease6::TYPE_TA:
+ type = Lease6::TYPE_TA;
+ break;
+
+ case Lease6::TYPE_PD:
+ type = Lease6::TYPE_PD;
+ break;
+
+ default:
+ isc_throw(BadValue, "invalid lease type returned (" <<
+ lease_type_ << ") for lease with address " <<
+ addr6_ << ". Only 0, 1, or 2 are allowed.");
+ }
+
+ // Set up DUID,
+ DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));
+
+ Lease6Ptr result(new Lease6(type, addr, duid_ptr, iaid_,
+ pref_lifetime_, valid_lifetime_, 0, 0,
+ subnet_id_, prefixlen_));
+
+ time_t cltt = expire_ - valid_lifetime_;
+ result->cltt_ = cltt;
+
+ return (result);
+ }
+
+private:
+ string addr6_;
+ // char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
+ string columns_[LEASE_COLUMNS];
+ time_t expire_;
+ vector<uint8_t> duid_;
+ uint8_t duid_buffer_[DUID::MAX_DUID_LEN];
+ uint32_t iaid_;
+ Lease6Ptr lease_;
+ uint8_t lease_type_;
+ uint8_t prefixlen_;
+ uint32_t pref_lifetime_;
+ uint32_t subnet_id_;
+ uint32_t valid_lifetime_;
+ unsigned long duid_length_;
+};
+
+PgSqlLeaseMgr::PgSqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
+ : LeaseMgr(parameters){
+ status = NULL;
+ openDatabase();
+ prepareStatements();
+
+ exchange4_.reset(new PgSqlLease4Exchange());
+ exchange6_.reset(new PgSqlLease6Exchange());
+}
+
+PgSqlLeaseMgr::~PgSqlLeaseMgr() {
+ if(status) {
+ // Attempt to deallocate prepared queries set previously with DEALLOCATE query
+ // No internal libpq function for that, no errors checking as well
+ PGresult * r;
+ for(int i = 0; tagged_statements[i].text != NULL; ++ i) {
+ string deallocate = "DEALLOCATE \"";
+ deallocate += tagged_statements[i].name;
+ deallocate += "\"";
+ r = PQexec(status, deallocate.c_str());
+ PQclear(r);
+ }
+
+ PQfinish(status);
+ }
+}
+
+void PgSqlLeaseMgr::prepareStatements() {
+ statements_.clear();
+ statements_.resize(NUM_STATEMENTS, PgSqlStatementBind());
+
+ PGresult * r = PQexec(status, "SET AUTOCOMMIT TO OFF");
+ PQclear(r);
+
+ for(int i = 0; tagged_statements[i].text != NULL; ++ i) {
+ string checkstatement = "SELECT * FROM pg_prepared_statements "
+ "WHERE name = '";
+ checkstatement += tagged_statements[i].name;
+ checkstatement += "'";
+
+ r = PQexec(status, checkstatement.c_str());
+
+ if(!PQntuples(r)) {
+ PQclear(r);
+ r = PQexec(status, "BEGIN");
+ PQclear(r);
+ // Prepare all statements queries with all known fields datatype
+ // No need to remind them when called
+ r = PQprepare(status, tagged_statements[i].name,
+ tagged_statements[i].text,
+ tagged_statements[i].nbparams,
+ tagged_statements[i].types);
+
+ if(PQresultStatus(r) != PGRES_COMMAND_OK) {
+ PQclear(r);
+ isc_throw(DbOperationError, "unable to prepare PostgreSQL statement <" <<
+ tagged_statements[i].text << ">, reason: " << PQerrorMessage(status));
+ }
+
+ PQclear(r);
+
+ r = PQexec(status, "END");
+ PQclear(r);
+
+ statements_[i].stmt_name = tagged_statements[i].name;
+ statements_[i].stmt_nbparams = tagged_statements[i].nbparams;
+ } else {
+ PQclear(r);
+ }
+ }
+
+ r = PQexec(status, "SET AUTOCOMMIT TO ON");
+ PQclear(r);
+}
+
+void
+PgSqlLeaseMgr::openDatabase() {
+ string dbconnparameters;
+ string shost = "localhost";
+ try {
+ shost = getParameter("host");
+ } catch(...) {
+ // No host. Fine, we'll use "localhost"
+ }
+
+ dbconnparameters += "host = '" + shost + "'" ;
+
+ string suser;
+ try {
+ suser = getParameter("user");
+ dbconnparameters += " user = '" + suser + "'";
+ } catch(...) {
+ // No user. Fine, we'll use NULL
+ }
+
+ string spassword;
+ try {
+ spassword = getParameter("password");
+ dbconnparameters += " password = '" + spassword + "'";
+ } catch(...) {
+ // No password. Fine, we'll use NULL
+ }
+
+ string sname;
+ try {
+ sname= getParameter("name");
+ dbconnparameters += " dbname = '" + sname + "'";
+ } catch(...) {
+ // No database name. Throw a "NoName" exception
+ isc_throw(NoDatabaseName, "must specified a name for the database");
+ }
+
+ status = PQconnectdb(dbconnparameters.c_str());
+ if(status == NULL || PQstatus(status) != CONNECTION_OK) {
+ isc_throw(DbOpenError, PQerrorMessage(status));
+ }
+}
+
+bool
+PgSqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
+ bindparams & params) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_ADD_ADDR4).arg(statements_[stindex].stmt_name);
+
+ vector<const char *> params_;
+ vector<int> lengths_;
+ vector<int> formats_;
+ convertToQuery(params, params_, lengths_, formats_);
+
+ PGresult * r = PQexecPrepared(status, statements_[stindex].stmt_name,
+ statements_[stindex].stmt_nbparams,
+ ¶ms_[0], &lengths_[0], &formats_[0], 0);
+
+ int s = PQresultStatus(r);
+ if (s != PGRES_COMMAND_OK) {
+ const char * errorMsg = PQerrorMessage(status);
+ PQclear(r);
+
+ if(!strncmp(errorMsg, "ERROR: duplicate key",
+ sizeof("ERROR: duplicate key") - 1)) {
+ return (false);
+ }
+
+ isc_throw(DbOperationError, "unable to INSERT for <" <<
+ statements_[stindex].stmt_name << ">, " <<
+ errorMsg);
+ }
+
+ PQclear(r);
+
+ return (true);
+}
+
+bool
+PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_ADD_ADDR4).arg(lease->addr_.toText());
+ bindparams params = exchange4_->createBindForSend(lease);
+
+ return (addLeaseCommon(INSERT_LEASE4, params));
+}
+
+bool
+PgSqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_ADD_ADDR6).arg(lease->addr_.toText());
+ bindparams params = exchange6_->createBindForSend(lease);
+
+ return (addLeaseCommon(INSERT_LEASE6, params));
+}
+
+template <typename Exchange, typename LeaseCollection>
+void PgSqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
+ bindparams & params,
+ Exchange& exchange,
+ LeaseCollection& result,
+ bool single) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_ADDR4).arg(statements_[stindex].stmt_name);
+
+ vector<const char *> params_;
+ vector<int> lengths_;
+ vector<int> formats_;
+ convertToQuery(params, params_, lengths_, formats_);
+
+ PGresult * r = PQexec(status, "SET AUTOCOMMIT TO OFF");
+ PQclear(r);
+
+ r = PQexec(status, "BEGIN");
+ PQclear(r);
+
+ r = PQexecPrepared(status, statements_[stindex].stmt_name,
+ statements_[stindex].stmt_nbparams,
+ ¶ms_[0], &lengths_[0], &formats_[0], 0);
+
+ checkError(r, stindex, "unable to bind WHERE clause parameter");
+ PQclear(r);
+
+ string fetchall = "FETCH ALL in ";
+ fetchall += statements_[stindex].stmt_name;
+
+ r = PQexec(status, fetchall.c_str());
+ checkError(r, stindex, "unable to FETCH ALL");
+
+ int lines = PQntuples(r);
+
+ if(single && lines > 1) {
+ PQclear(r);
+ r = PQexec(status, "END");
+ PQclear(r);
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << statements_[stindex].stmt_name);
+ }
+
+ for(int i = 0; i < lines; ++ i) {
+ result.push_back(exchange->convertFromDatabase(r, i));
+ }
+
+ PQclear(r);
+
+ string closecursor = "CLOSE ";
+ closecursor += statements_[stindex].stmt_name;
+ r = PQexec(status, closecursor.c_str());
+ PQclear(r);
+
+ r = PQexec(status, "END");
+ PQclear(r);
+
+ r = PQexec(status, "SET AUTOCOMMIT TO ON");
+ PQclear(r);
+}
+
+void
+PgSqlLeaseMgr::getLease(StatementIndex stindex, bindparams & params,
+ Lease4Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" paraeter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease4Collection collection;
+ getLeaseCollection(stindex, params, exchange4_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+void
+PgSqlLeaseMgr::getLease(StatementIndex stindex, bindparams & params,
+ Lease6Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" paraeter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease6Collection collection;
+ getLeaseCollection(stindex, params, exchange6_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_ADDR4).arg(addr.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+ ostringstream tmp;
+ string baddr;
+
+ tmp << addr;
+ PgSqlParam addr4 = { .value = tmp.str() };
+
+ inparams.push_back(addr4);
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_ADDR, inparams, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_HWADDR).arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+
+ uint8_t* data = const_cast<uint8_t *>(&hwaddr.hwaddr_[0]);
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(data)
+ , .isbinary = 1, .binarylen = static_cast<int>(hwaddr.hwaddr_.size()) };
+ inparams.push_back(pdest);
+
+ // Get the data
+ Lease4Collection result;
+ getLeaseCollection(GET_LEASE4_HWADDR, inparams, result);
+
+ return (result);
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+ ostringstream tmp;
+
+ uint8_t* data = const_cast<uint8_t *>(&hwaddr.hwaddr_[0]);
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(data)
+ , .isbinary = 1, .binarylen = static_cast<int>(hwaddr.hwaddr_.size()) };
+ inparams.push_back(pdest);
+
+ tmp << static_cast<unsigned long>(subnet_id);
+ PgSqlParam psubnet_id = { .value = tmp.str() };
+
+ inparams.push_back(psubnet_id);
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_HWADDR_SUBID, inparams, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_CLIENTID).arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+
+ vector<uint8_t> client_data = clientid.getClientId();
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(&client_data[0])
+ , .isbinary = 1, .binarylen = static_cast<int>(clientid.getClientId().size()) };
+ inparams.push_back(pdest);
+
+ // Get the data
+ Lease4Collection result;
+ getLeaseCollection(GET_LEASE4_CLIENTID, inparams, result);
+
+ return (result);
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id).arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+ ostringstream tmp;
+
+ vector<uint8_t> client_data = clientid.getClientId();
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(&client_data[0])
+ , .isbinary = 1, .binarylen = static_cast<int>(clientid.getClientId().size()) };
+ inparams.push_back(pdest);
+
+ tmp << static_cast<unsigned long>(subnet_id);
+ PgSqlParam psubnet_id = { .value = tmp.str() };
+ inparams.push_back(psubnet_id);
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_CLIENTID_SUBID, inparams, result);
+
+ return (result);
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const ClientId& /*client_id*/, const HWAddr& /*hwaddr*/,
+ SubnetID /*subnet_id*/) const {
+ /// @todo
+
+ Lease4Ptr result;
+ return (result);
+}
+
+Lease6Ptr
+PgSqlLeaseMgr::getLease6(Lease::Type lease_type,
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_ADDR6)
+ .arg(addr.toText()).arg(lease_type);
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+
+ PgSqlParam addr6 = { .value = addr.toText() };
+
+ /// @todo: use type param
+
+ inparams.push_back(addr6);
+
+ Lease6Ptr result;
+ getLease(GET_LEASE6_ADDR, inparams, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(Lease::Type type, const DUID& duid, uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_IAID_DUID)
+ .arg(iaid).arg(duid.toText()).arg(type);
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+ ostringstream tmp;
+ vector<uint8_t> duid_data = duid.getDuid();
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(&duid_data[0])
+ , .isbinary = 1, .binarylen = static_cast<int>(duid.getDuid().size()) };
+ inparams.push_back(pdest);
+
+ /// @todo: use type
+
+ // IAID
+ tmp << static_cast<unsigned long>(iaid);
+ PgSqlParam piaid = { .value = tmp.str() };
+ inparams.push_back(piaid);
+
+ // ... and get the data
+ Lease6Collection result;
+ getLeaseCollection(GET_LEASE6_DUID_IAID, inparams, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid, uint32_t iaid,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText()).arg(lease_type);
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+ ostringstream tmp;
+
+ /// @todo: use type
+
+ // See the earlier description of the use of "const_cast" when accessing
+ // the DUID for an explanation of the reason.
+ vector<uint8_t> duid_data = duid.getDuid();
+ PgSqlParam pdest = { .value = reinterpret_cast<char *>(&duid_data[0])
+ , .isbinary = 1, .binarylen = static_cast<int>(duid.getDuid().size()) };
+ inparams.push_back(pdest);
+
+ // IAID
+ tmp << static_cast<unsigned long>(iaid);
+ PgSqlParam piaid = { .value = tmp.str() };
+ inparams.push_back(piaid);
+ tmp.str("");
+ tmp.clear();
+
+ // Subnet ID
+ tmp << static_cast<unsigned long>(subnet_id);
+ PgSqlParam psubnet_id = { .value = tmp.str() };
+ inparams.push_back(psubnet_id);
+ tmp.str("");
+ tmp.clear();
+
+ Lease6Collection result;
+ getLeaseCollection(GET_LEASE6_DUID_IAID_SUBID, inparams, result);
+
+ return (result);
+}
+
+template <typename LeasePtr>
+void
+PgSqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, bindparams & params,
+ const LeasePtr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_ADD_ADDR4).arg(statements_[stindex].stmt_name);
+
+ vector<const char *> params_;
+ vector<int> lengths_;
+ vector<int> formats_;
+ convertToQuery(params, params_, lengths_, formats_);
+
+ PGresult * r = PQexecPrepared(status, statements_[stindex].stmt_name,
+ statements_[stindex].stmt_nbparams,
+ ¶ms_[0], &lengths_[0], &formats_[0], 0);
+ checkError(r, stindex, "unable to bind UPDATE parameters");
+ int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
+
+ PQclear(r);
+
+ if (!affected_rows) {
+ isc_throw(NoSuchLease, "unable to update lease for address " <<
+ lease->addr_.toText() << " 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 " << lease->addr_.toText());
+ }
+}
+
+void
+PgSqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE4;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
+ // Create the MYSQL_BIND array for the data being updated
+ ostringstream tmp;
+ bindparams params = exchange4_->createBindForSend(lease);
+
+ // Set up the WHERE clause and append it to the MYSQL_BIND array
+ tmp << lease->addr_;
+ PgSqlParam addr4 = { .value = tmp.str() };
+ params.push_back(addr4);
+
+ // Drop to common update code
+ updateLeaseCommon(stindex, params, lease);
+}
+
+void
+PgSqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE6;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
+ // Create the MYSQL_BIND array for the data being updated
+ bindparams params = exchange6_->createBindForSend(lease);
+
+ // Set up the WHERE clause and append it to the MYSQL_BIND array
+ PgSqlParam addr6 = { .value = lease->addr_.toText() };
+ params.push_back(addr6);
+
+ // Drop to common update code
+ updateLeaseCommon(stindex, params, lease);
+}
+
+bool
+PgSqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, bindparams & params) {
+ vector<const char *> params_;
+ vector<int> lengths_;
+ vector<int> formats_;
+ convertToQuery(params, params_, lengths_, formats_);
+
+ PGresult * r = PQexecPrepared(status, statements_[stindex].stmt_name,
+ statements_[stindex].stmt_nbparams,
+ ¶ms_[0], &lengths_[0], &formats_[0], 0);
+ checkError(r, stindex, "unable to bind WHERE clause parameter");
+ int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
+ PQclear(r);
+
+ return (affected_rows > 0);
+}
+
+bool
+PgSqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_DELETE_ADDR).arg(addr.toText());
+
+ // Set up the WHERE clause value
+ bindparams inparams;
+
+ if (addr.isV4()) {
+ ostringstream tmp;
+ tmp << addr;
+ PgSqlParam addr4 = { .value = tmp.str() };
+
+ inparams.push_back(addr4);
+
+ return (deleteLeaseCommon(DELETE_LEASE4, inparams));
+
+ } else {
+ PgSqlParam addr6 = { .value = addr.toText() };
+
+ inparams.push_back(addr6);
+
+ return (deleteLeaseCommon(DELETE_LEASE6, inparams));
+ }
+}
+
+string
+PgSqlLeaseMgr::getName() const {
+ string name = "";
+ try {
+ name = getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+string
+PgSqlLeaseMgr::getDescription() const {
+ return (string("PostgreSQL Database"));
+}
+
+pair<uint32_t, uint32_t>
+PgSqlLeaseMgr::getVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_VERSION).arg("get_version");
+
+ PGresult * r = PQexec(status, "SET AUTOCOMMIT TO OFF");
+ PQclear(r);
+
+ r = PQexec(status, "BEGIN");
+ PQclear(r);
+
+ r = PQexecPrepared(status, "get_version",
+ 0,
+ NULL, NULL, NULL, 0);
+
+ PQclear(r);
+
+ r = PQexec(status, "FETCH ALL in get_version");
+
+ const char * version_str = PQgetvalue(r, 0, 0);
+ const char * minor_str = PQgetvalue(r, 0, 1);
+
+ istringstream tmp;
+
+ uint32_t version;
+ tmp.str(version_str);
+ tmp >> version;
+ tmp.str("");
+ tmp.clear();
+
+ uint32_t minor;
+ tmp.str(minor_str);
+ tmp >> minor;
+
+ PQclear(r);
+ r = PQexec(status, "CLOSE get_version");
+ PQclear(r);
+
+ r = PQexec(status, "END");
+ PQclear(r);
+
+ r = PQexec(status, "SET AUTOCOMMIT TO ON");
+ PQclear(r);
+
+ return make_pair<uint32_t, uint32_t>(version, minor);
+}
+
+void
+PgSqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
+ PGresult * r = PQexec(status, "COMMIT");
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ isc_throw(DbOperationError, "commit failed: " << PQerrorMessage(status));
+ }
+
+ PQclear(r);
+}
+
+void
+PgSqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
+ PGresult * r = PQexec(status, "ROLLBACK");
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ isc_throw(DbOperationError, "rollback failed: " << PQerrorMessage(status));
+ }
+
+ PQclear(r);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
--- /dev/null
+// Copyright (C) 2013-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.
+
+#ifndef PGSQL_LEASE_MGR_H
+#define PGSQL_LEASE_MGR_H
+
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/lease_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/utility.hpp>
+#include <libpq-fe.h>
+
+namespace isc {
+namespace dhcp {
+
+struct PgSqlParam {
+ std::string value;
+ int isbinary;
+ int binarylen;
+};
+
+typedef std::vector<PgSqlParam> bindparams;
+
+struct PgSqlStatementBind {
+ const char * stmt_name;
+ int stmt_nbparams;
+};
+
+class PgSqlLease4Exchange;
+class PgSqlLease6Exchange;
+
+const uint32_t PG_CURRENT_VERSION = 1;
+const uint32_t PG_CURRENT_MINOR = 0;
+
+class PgSqlLeaseMgr : public LeaseMgr {
+public:
+ PgSqlLeaseMgr(const ParameterMap& parameters);
+ virtual ~PgSqlLeaseMgr();
+ virtual bool addLease(const Lease4Ptr& lease);
+ virtual bool addLease(const Lease6Ptr& lease);
+
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
+ SubnetID subnet_id) const;
+ virtual Lease4Collection getLease4(const ClientId& clientid) const;
+ virtual Lease4Ptr getLease4(const ClientId& client_id, const HWAddr& hwaddr,
+ SubnetID subnet_id) const;
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const;
+
+ virtual Lease6Ptr getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const;
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid) const;
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const;
+
+ virtual void updateLease4(const Lease4Ptr& lease4);
+ virtual void updateLease6(const Lease6Ptr& lease6);
+ virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
+ virtual std::string getType() const {
+ return (std::string("postgresql"));
+ }
+ virtual std::string getName() const;
+ virtual std::string getDescription() const;
+ virtual std::pair<uint32_t, uint32_t> getVersion() const;
+ virtual void commit();
+ virtual void rollback();
+ enum StatementIndex {
+ DELETE_LEASE4, // Delete from lease4 by address
+ DELETE_LEASE6, // Delete from lease6 by address
+ GET_LEASE4_ADDR, // Get lease4 by address
+ GET_LEASE4_CLIENTID, // Get lease4 by client ID
+ GET_LEASE4_CLIENTID_SUBID, // Get lease4 by client ID & subnet ID
+ GET_LEASE4_HWADDR, // Get lease4 by HW address
+ GET_LEASE4_HWADDR_SUBID, // Get lease4 by HW address & subnet ID
+ 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_LEASE4, // Update a Lease4 entry
+ UPDATE_LEASE6, // Update a Lease6 entry
+ NUM_STATEMENTS // Number of statements
+ };
+private:
+ void prepareStatements();
+ void openDatabase();
+ bool addLeaseCommon(StatementIndex stindex, bindparams & params);
+ template <typename Exchange, typename LeaseCollection>
+ void getLeaseCollection(StatementIndex stindex, bindparams & params,
+ Exchange& exchange, LeaseCollection& result,
+ bool single = false) const;
+ void getLeaseCollection(StatementIndex stindex, bindparams & params,
+ Lease4Collection& result) const {
+ getLeaseCollection(stindex, params, exchange4_, result);
+ }
+ void getLeaseCollection(StatementIndex stindex, bindparams & params,
+ Lease6Collection& result) const {
+ getLeaseCollection(stindex, params, exchange6_, result);
+ }
+ inline void checkError(PGresult * r, StatementIndex index,
+ const char* what) const {
+ int s = PQresultStatus(r);
+ if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
+ PQclear(r);
+
+ isc_throw(DbOperationError, what << " for <" <<
+ statements_[index].stmt_name << ">, " <<
+ PQerrorMessage(status));
+ }
+ }
+ inline void convertToQuery(bindparams & params,
+ std::vector<const char *>& params_,
+ std::vector<int>& lengths_,
+ std::vector<int>& formats_) const {
+ params_.reserve(params.size());
+ lengths_.reserve(params.size());
+ formats_.reserve(params.size());
+
+ for(bindparams::const_iterator it = params.begin(); it != params.end();
+ ++ it) {
+ params_.push_back((* it).value.c_str());
+ lengths_.push_back((* it).binarylen);
+ formats_.push_back((* it).isbinary);
+ }
+ }
+ void getLease(StatementIndex stindex, bindparams & params,
+ Lease4Ptr& result) const;
+ void getLease(StatementIndex stindex, bindparams & params,
+ Lease6Ptr& result) const;
+ template <typename LeasePtr>
+ void updateLeaseCommon(StatementIndex stindex, bindparams & params,
+ const LeasePtr& lease);
+ bool deleteLeaseCommon(StatementIndex stindex, bindparams & params);
+
+ boost::scoped_ptr<PgSqlLease4Exchange> exchange4_;
+ boost::scoped_ptr<PgSqlLease6Exchange> exchange6_;
+ std::vector<PgSqlStatementBind> statements_;
+ PGconn * status;
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PGSQL_LEASE_MGR_H
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
+endif
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
-libdhcpsrv_unittests_SOURCES += schema_copy.h
+libdhcpsrv_unittests_SOURCES += schema_mysql_copy.h
+libdhcpsrv_unittests_SOURCES += schema_pgsql_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_CPPFLAGS += $(MYSQL_CPPFLAGS)
endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_CPPFLAGS += $(PGSQL_CPPFLAGS)
+endif
libdhcpsrv_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
if HAVE_MYSQL
libdhcpsrv_unittests_LDFLAGS += $(MYSQL_LIBS)
endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
if USE_CLANGPP
--- /dev/null
+// 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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <exceptions/exceptions.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 {
+
+// 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<uint32_t, uint32_t> 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<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]);
+ 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);
+}
+
+/// @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<Lease4Ptr> 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<Lease6Ptr> 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);
+}
+
+};
--- /dev/null
+// 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.
+
+#ifndef SCHEMA_COPY_H
+#define SCHEMA_COPY_H
+
+namespace {
+
+// What follows is a set of statements that creates a copy of the schema
+// in the test database. It is used by the PostgreSQL unit test prior to each
+// test.
+//
+// Each SQL statement is a single string. The statements are not terminated
+// by semicolons, and the strings must end with a comma. The final line
+// statement must be NULL (not in quotes)
+
+// NOTE: This file mirrors the schema in src/lib/dhcpsrv/dhcpdb_create.pgsql.
+// If this file is altered, please ensure that any change is compatible
+// with the schema in dhcpdb_create.pgsql.
+
+// Deletion of existing tables.
+
+const char* destroy_statement[] = {
+ "DROP TABLE lease4",
+ "DROP TABLE lease6",
+ "DROP TABLE lease6_types",
+ "DROP TABLE schema_version",
+ NULL
+};
+
+// Creation of the new tables.
+
+const char* create_statement[] = {
+ "START TRANSACTION",
+ "CREATE TABLE lease4 ("
+ "address BIGINT PRIMARY KEY NOT NULL,"
+ "hwaddr BYTEA,"
+ "client_id BYTEA,"
+ "valid_lifetime BIGINT,"
+ "expire TIMESTAMP,"
+ "subnet_id BIGINT"
+ ")",
+
+ "CREATE TABLE lease6 ("
+ "address VARCHAR(39) PRIMARY KEY NOT NULL,"
+ "duid BYTEA,"
+ "valid_lifetime BIGINT,"
+ "expire TIMESTAMP,"
+ "subnet_id BIGINT,"
+ "pref_lifetime BIGINT,"
+ "lease_type SMALLINT,"
+ "iaid BIGINT,"
+ "prefix_len SMALLINT"
+ ")",
+
+ "CREATE TABLE lease6_types ("
+ "lease_type SMALLINT PRIMARY KEY NOT NULL,"
+ "name VARCHAR(5)"
+ ")",
+
+ "INSERT INTO lease6_types VALUES (0, 'IA_NA')",
+ "INSERT INTO lease6_types VALUES (1, 'IA_TA')",
+ "INSERT INTO lease6_types VALUES (2, 'IA_PD')",
+
+ "CREATE TABLE schema_version ("
+ "version INT PRIMARY KEY NOT NULL,"
+ "minor INT"
+ ")",
+
+ "INSERT INTO schema_version VALUES (1, 0)",
+ "COMMIT",
+
+ NULL
+};
+
+}; // Anonymous namespace
+
+#endif // SCHEMA_COPY_H