diff options
Diffstat (limited to 'src/lib/dhcpsrv/pgsql_lease_mgr.cc')
-rw-r--r-- | src/lib/dhcpsrv/pgsql_lease_mgr.cc | 3302 |
1 files changed, 3302 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc new file mode 100644 index 0000000..28c4109 --- /dev/null +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -0,0 +1,3302 @@ +// Copyright (C) 2014-2023 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/addr_utilities.h> +#include <dhcp/duid.h> +#include <dhcp/hwaddr.h> +#include <dhcpsrv/cfg_db_access.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/dhcpsrv_exceptions.h> +#include <dhcpsrv/lease_mgr_factory.h> +#include <dhcpsrv/pgsql_lease_mgr.h> +#include <dhcpsrv/timer_mgr.h> +#include <util/multi_threading_mgr.h> + +#include <boost/make_shared.hpp> +#include <boost/static_assert.hpp> + +#include <iomanip> +#include <limits> +#include <sstream> +#include <string> +#include <time.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::db; +using namespace isc::dhcp; +using namespace isc::data; +using namespace isc::util; +using namespace std; + +namespace { + +/// @brief Catalog of all the SQL statements currently supported. Note +/// that the order columns appear in statement body must match the order they +/// that the occur in the table. This does not apply to the where clause. +PgSqlTaggedStatement tagged_statements[] = { + // DELETE_LEASE4 + { 2, { OID_INT8, OID_TIMESTAMP }, + "delete_lease4", + "DELETE FROM lease4 WHERE address = $1 AND expire = $2" }, + + // DELETE_LEASE4_STATE_EXPIRED + { 2, { OID_INT8, OID_TIMESTAMP }, + "delete_lease4_state_expired", + "DELETE FROM lease4 " + "WHERE state = $1 AND expire < $2" }, + + // DELETE_LEASE6 + { 2, { OID_VARCHAR, OID_TIMESTAMP }, + "delete_lease6", + "DELETE FROM lease6 WHERE address = cast($1 as inet) AND expire = $2"}, + + // DELETE_LEASE6_STATE_EXPIRED + { 2, { OID_INT8, OID_TIMESTAMP }, + "delete_lease6_state_expired", + "DELETE FROM lease6 " + "WHERE state = $1 AND expire < $2" }, + + // GET_LEASE4 + { 0, { OID_NONE }, + "get_lease4", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4" }, + + // GET_LEASE4_ADDR + { 1, { OID_INT8 }, + "get_lease4_addr", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE address = $1" }, + + // GET_LEASE4_CLIENTID + { 1, { OID_BYTEA }, + "get_lease4_clientid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE client_id = $1" }, + + // GET_LEASE4_CLIENTID_SUBID + { 2, { OID_BYTEA, OID_INT8 }, + "get_lease4_clientid_subid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE client_id = $1 AND subnet_id = $2" }, + + // GET_LEASE4_HWADDR + { 1, { OID_BYTEA }, + "get_lease4_hwaddr", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE hwaddr = $1" }, + + // GET_LEASE4_HWADDR_SUBID + { 2, { OID_BYTEA, OID_INT8 }, + "get_lease4_hwaddr_subid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE hwaddr = $1 AND subnet_id = $2" }, + + // GET_LEASE4_PAGE + { 2, { OID_INT8, OID_INT8 }, + "get_lease4_page", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE address > $1 " + "ORDER BY address " + "LIMIT $2" }, + + // GET_LEASE4_UCTX_PAGE + { 2, { OID_INT8, OID_INT8 }, + "get_lease4_uctx_page", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE address > $1 AND user_context IS NOT NULL " + "ORDER BY address " + "LIMIT $2" }, + + // GET_LEASE4_SUBID + { 1, { OID_INT8 }, + "get_lease4_subid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE subnet_id = $1" }, + + // GET_LEASE4_HOSTNAME + { 1, { OID_VARCHAR }, + "get_lease4_hostname", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE lower(hostname) = $1" }, + + // GET_LEASE4_EXPIRE + { 3, { OID_INT8, OID_TIMESTAMP, OID_INT8 }, + "get_lease4_expire", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE state != $1 AND valid_lifetime != 4294967295 AND expire < $2 " + "ORDER BY expire " + "LIMIT $3" }, + + // GET_LEASE4_RELAYID + { 3, { OID_BYTEA, OID_INT8, OID_INT8 }, + "get_lease4_relayid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE relay_id = $1 and address > $2 " + "ORDER BY address " + "LIMIT $3" }, + + // GET_LEASE4_RELAYID_QST + { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_relayid_qst", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE relay_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) >= $3 " + "ORDER BY address " + "LIMIT $4" }, + + // GET_LEASE4_RELAYID_QSET + { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_relayid_qset", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE relay_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) >= $3 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) <= $4 " + "ORDER BY address " + "LIMIT $5" }, + + // GET_LEASE4_RELAYID_QET + { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_relayid_qet", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE relay_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) <= $3 " + "ORDER BY address " + "LIMIT $4" }, + + // GET_LEASE4_REMOTEID + { 3, { OID_BYTEA, OID_INT8, OID_INT8 }, + "get_lease4_remoteid", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE remote_id = $1 and address > $2 " + "ORDER BY address " + "LIMIT $3" }, + + // GET_LEASE4_REMOTEID_QST + { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_remoteid_qst", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE remote_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) >= $3 " + "ORDER BY address " + "LIMIT $4" }, + + // GET_LEASE4_REMOTEID_QSET + { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_remoteid_qset", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE remote_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) >= $3 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) <= $4 " + "ORDER BY address " + "LIMIT $5" }, + + // GET_LEASE4_REMOTEID_QET + { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 }, + "get_lease4_remoteid_qet", + "SELECT address, hwaddr, client_id, " + "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, " + "fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id " + "FROM lease4 " + "WHERE remote_id = $1 and address > $2 " + "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 " + "THEN 0 ELSE valid_lifetime END) <= $3 " + "ORDER BY address " + "LIMIT $4" }, + + // GET_LEASE6 + { 0, { OID_NONE }, + "get_lease6", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "ORDER BY address "}, + + // GET_LEASE6_ADDR + { 2, { OID_VARCHAR, OID_INT2 }, + "get_lease6_addr", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE address = cast($1 as inet) AND lease_type = $2"}, + + // GET_LEASE6_DUID_IAID + { 3, { OID_BYTEA, OID_INT8, OID_INT2 }, + "get_lease6_duid_iaid", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE duid = $1 AND iaid = $2 AND lease_type = $3" }, + + // GET_LEASE6_DUID_IAID_SUBID + { 4, { OID_INT2, OID_BYTEA, OID_INT8, OID_INT8 }, + "get_lease6_duid_iaid_subid", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE lease_type = $1 " + "AND duid = $2 AND iaid = $3 AND subnet_id = $4" }, + + // GET_LEASE6_PAGE + { 2, { OID_VARCHAR, OID_INT8 }, + "get_lease6_page", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE address > cast($1 as inet) " + "ORDER BY address " + "LIMIT $2"}, + + // GET_LEASE6_UCTX_PAGE + { 2, { OID_VARCHAR, OID_INT8 }, + "get_lease6_uctx_page", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE address > cast($1 as inet) AND user_context IS NOT NULL " + "ORDER BY address " + "LIMIT $2" }, + + // GET_LEASE6_SUBID + { 1, { OID_INT8 }, + "get_lease6_subid", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE subnet_id = $1" }, + + // GET_LEASE6_DUID + { 1, { OID_BYTEA }, + "get_lease6_duid", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE duid = $1" }, + + // GET_LEASE6_HOSTNAME + { 1, { OID_VARCHAR }, + "get_lease6_hostname", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE lower(hostname) = $1" }, + + // GET_LEASE6_EXPIRE + { 3, { OID_INT8, OID_TIMESTAMP, OID_INT8 }, + "get_lease6_expire", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, " + "fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE state != $1 AND valid_lifetime != 4294967295 AND expire < $2 " + "ORDER BY expire " + "LIMIT $3" }, + + // GET_LEASE6_LINK + { 3, { OID_VARCHAR, OID_VARCHAR, OID_INT8 }, + "get_lease6_link", + "SELECT host(address), duid, valid_lifetime, " + "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id " + "FROM lease6 " + "WHERE address BETWEEN cast($1 as inet) and cast($2 as inet) " + "ORDER BY address " + "LIMIT $3" }, + + // INSERT_LEASE4 + { 14, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, + OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA, + OID_BYTEA, OID_INT8 }, + "insert_lease4", + "INSERT INTO lease4(address, hwaddr, client_id, " + "valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, " + "state, user_context, relay_id, remote_id, pool_id) " + "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)" }, + + // INSERT_LEASE6 + { 18, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, + OID_INT8, OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL, + OID_VARCHAR, OID_BYTEA, OID_INT2, OID_INT2, OID_INT8, OID_TEXT, + OID_INT8}, + "insert_lease6", + "INSERT INTO lease6(address, duid, valid_lifetime, " + "expire, subnet_id, pref_lifetime, " + "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " + "hwaddr, hwtype, hwaddr_source, " + "state, user_context, pool_id) " + "VALUES (cast($1 as inet), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)" }, + + // UPDATE_LEASE4 + { 16, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, + OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA, + OID_BYTEA, OID_INT8, OID_INT8, OID_TIMESTAMP }, + "update_lease4", + "UPDATE lease4 SET address = $1, hwaddr = $2, " + "client_id = $3, valid_lifetime = $4, expire = $5, " + "subnet_id = $6, fqdn_fwd = $7, fqdn_rev = $8, hostname = $9, " + "state = $10, user_context = $11, relay_id = $12, remote_id = $13, pool_id = $14 " + "WHERE address = $15 AND expire = $16" }, + + // UPDATE_LEASE6 + { 20, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, OID_INT8, + OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL, OID_VARCHAR, + OID_BYTEA, OID_INT2, OID_INT2, + OID_INT8, OID_TEXT, OID_INT8, OID_VARCHAR, OID_TIMESTAMP }, + "update_lease6", + "UPDATE lease6 SET address = cast($1 as inet), duid = $2, " + "valid_lifetime = $3, expire = $4, subnet_id = $5, " + "pref_lifetime = $6, lease_type = $7, iaid = $8, " + "prefix_len = $9, fqdn_fwd = $10, fqdn_rev = $11, hostname = $12, " + "hwaddr = $13, hwtype = $14, hwaddr_source = $15, " + "state = $16, user_context = $17, pool_id = $18 " + "WHERE address = cast($19 as inet) AND expire = $20" }, + + // ALL_LEASE4_STATS + { 0, { OID_NONE }, + "all_lease4_stats", + "SELECT subnet_id, state, leases as state_count" + " FROM lease4_stat ORDER BY subnet_id, state" }, + + // SUBNET_LEASE4_STATS + { 1, { OID_INT8 }, + "subnet_lease4_stats", + "SELECT subnet_id, state, leases as state_count" + " FROM lease4_stat " + " WHERE subnet_id = $1 " + " ORDER BY state" }, + + // SUBNET_RANGE_LEASE4_STATS + { 2, { OID_INT8, OID_INT8 }, + "subnet_range_lease4_stats", + "SELECT subnet_id, state, leases as state_count" + " FROM lease4_stat " + " WHERE subnet_id >= $1 and subnet_id <= $2 " + " ORDER BY subnet_id, state" }, + + // ALL_POOL_LEASE4_STATS + { 0, { OID_NONE }, + "all_pool_lease4_stats", + "SELECT subnet_id, pool_id, state, leases as state_count" + " FROM lease4_pool_stat ORDER BY subnet_id, pool_id, state" }, + + // ALL_LEASE6_STATS, + { 0, { OID_NONE }, + "all_lease6_stats", + "SELECT subnet_id, lease_type, state, leases as state_count" + " FROM lease6_stat ORDER BY subnet_id, lease_type, state" }, + + // SUBNET_LEASE6_STATS + { 1, { OID_INT8 }, + "subnet_lease6_stats", + "SELECT subnet_id, lease_type, state, leases as state_count" + " FROM lease6_stat " + " WHERE subnet_id = $1 " + " ORDER BY lease_type, state" }, + + // SUBNET_RANGE_LEASE6_STATS + { 2, { OID_INT8, OID_INT8 }, + "subnet_range_lease6_stats", + "SELECT subnet_id, lease_type, state, leases as state_count" + " FROM lease6_stat " + " WHERE subnet_id >= $1 and subnet_id <= $2 " + " ORDER BY subnet_id, lease_type, state" }, + + // ALL_POOL_LEASE6_STATS, + { 0, { OID_NONE }, + "all_pool_lease6_stats", + "SELECT subnet_id, pool_id, lease_type, state, leases as state_count" + " FROM lease6_pool_stat ORDER BY subnet_id, pool_id, lease_type, state" }, + + // CHECK_LEASE4_LIMITS + { 1, { OID_TEXT }, + "check_lease4_limits", + "SELECT checkLease4Limits($1)" }, + + // CHECK_LEASE6_LIMITS + { 1, { OID_TEXT }, + "check_lease6_limits", + "SELECT checkLease6Limits($1)" }, + + // IS_JSON_SUPPORTED + { 0, { OID_NONE }, + "is_json_supported", + "SELECT isJsonSupported()" }, + + // GET_LEASE4_COUNT_BY_CLASS + { 1, { OID_VARCHAR }, + "get_lease4_count_by_class", + "SELECT leases " + "FROM lease4_stat_by_client_class " + "WHERE client_class = $1" }, + + // GET_LEASE6_COUNT_BY_CLASS + { 2, { OID_VARCHAR, OID_INT2 }, + "get_lease6_count_by_class", + "SELECT leases " + "FROM lease6_stat_by_client_class " + "WHERE client_class = $1 AND lease_type = $2" }, + + // End of list sentinel + { 0, { 0 }, NULL, NULL } +}; + +} // namespace + +namespace isc { +namespace dhcp { + +/// @brief Base class for marshalling leases to and from PostgreSQL. +/// +/// Provides the common functionality to set up binding information between +/// lease objects in the program and their database representation in the +/// database. +class PgSqlLeaseExchange : public PgSqlExchange { +public: + + PgSqlLeaseExchange() + : addr_str_(""), hwaddr_length_(0), hwaddr_(hwaddr_length_), + valid_lifetime_(0), valid_lifetime_str_(""), expire_(0), + expire_str_(""), subnet_id_(0), subnet_id_str_(""), pool_id_(0), + pool_id_str_(""), cltt_(0), fqdn_fwd_(false), fqdn_rev_(false), + hostname_(""), state_str_(""), user_context_(""), addr_bin_(16) { + } + + virtual ~PgSqlLeaseExchange() {} + +protected: + + /// @brief Common Instance members used for binding and conversion + //@{ + std::string addr_str_; + size_t hwaddr_length_; + std::vector<uint8_t> hwaddr_; + uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN]; + uint32_t valid_lifetime_; + std::string valid_lifetime_str_; + time_t expire_; + std::string expire_str_; + uint32_t subnet_id_; + std::string subnet_id_str_; + uint32_t pool_id_; + std::string pool_id_str_; + time_t cltt_; + bool fqdn_fwd_; + bool fqdn_rev_; + std::string hostname_; + std::string state_str_; + std::string user_context_; + std::vector<uint8_t> addr_bin_; + //@} +}; + +/// @brief Supports exchanging IPv4 leases with PostgreSQL. +class PgSqlLease4Exchange : public PgSqlLeaseExchange { +private: + + /// @brief Column numbers for each column in the Lease4 table. + /// These are used for both retrieving data and for looking up + /// column labels for logging. Note that their numeric order + /// MUST match that of the column order in the Lease4 table. + //@{ + static const size_t ADDRESS_COL = 0; + static const size_t HWADDR_COL = 1; + static const size_t CLIENT_ID_COL = 2; + static const size_t VALID_LIFETIME_COL = 3; + static const size_t EXPIRE_COL = 4; + static const size_t SUBNET_ID_COL = 5; + static const size_t FQDN_FWD_COL = 6; + static const size_t FQDN_REV_COL = 7; + static const size_t HOSTNAME_COL = 8; + static const size_t STATE_COL = 9; + static const size_t USER_CONTEXT_COL = 10; + static const size_t RELAY_ID_COL = 11; + static const size_t REMOTE_ID_COL = 12; + static const size_t POOL_ID_COL = 13; + //@} + /// @brief Number of columns in the table holding DHCPv4 leases. + static const size_t LEASE_COLUMNS = 14; + +public: + + /// @brief Constructor + PgSqlLease4Exchange() + : lease_(), addr4_(0), client_id_length_(0), + relay_id_length_(0), remote_id_length_(0) { + + BOOST_STATIC_ASSERT(13 < LEASE_COLUMNS); + + memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_)); + memset(client_id_buffer_, 0, sizeof(client_id_buffer_)); + memset(relay_id_buffer_, 0, sizeof(relay_id_buffer_)); + memset(remote_id_buffer_, 0, sizeof(remote_id_buffer_)); + + // Set the column names (for error messages) + columns_.push_back("address"); + columns_.push_back("hwaddr"); + columns_.push_back("client_id"); + columns_.push_back("valid_lifetime"); + columns_.push_back("expire"); + columns_.push_back("subnet_id"); + columns_.push_back("fqdn_fwd"); + columns_.push_back("fqdn_rev"); + columns_.push_back("hostname"); + columns_.push_back("state"); + columns_.push_back("user_context"); + columns_.push_back("relay_id"); + columns_.push_back("remote_id"); + columns_.push_back("pool_id"); + } + + /// @brief Creates the bind array for sending Lease4 data to the database. + /// + /// Converts each Lease4 member into the appropriate form and adds it + /// to the bind array. Note that the array additions must occur in the + /// order the columns are specified in the SQL statement. By convention + /// all columns in the table are explicitly listed in the SQL statement(s) + /// in the same order as they occur in the table. + /// + /// @param lease Lease4 object that is to be written to the database + /// @param[out] bind_array array to populate with the lease data values + /// + /// @throw DbOperationError if bind_array cannot be populated. + void createBindForSend(const Lease4Ptr& lease, PsqlBindArray& bind_array) { + if (!lease) { + isc_throw(BadValue, "createBindForSend:: Lease4 object is NULL"); + } + + // Store lease object to ensure it remains valid. + lease_ = lease; + + try { + addr_str_ = boost::lexical_cast<std::string>(lease->addr_.toUint32()); + bind_array.add(addr_str_); + + if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) { + // PostgreSql does not provide MAX on variable length types + // so we have to enforce it ourselves. + if (lease->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) { + isc_throw(DbOperationError, "Hardware address length : " + << lease_->hwaddr_->hwaddr_.size() + << " exceeds maximum allowed of: " + << HWAddr::MAX_HWADDR_LEN); + } + bind_array.add(lease->hwaddr_->hwaddr_); + } else { + bind_array.add(""); + } + + if (lease->client_id_) { + bind_array.add(lease->client_id_->getClientId()); + } else { + bind_array.add(""); + } + + valid_lifetime_str_ = boost::lexical_cast<std::string>(lease->valid_lft_); + bind_array.add(valid_lifetime_str_); + + // The lease structure holds the client last transmission time (cltt_) + // For convenience for external tools, this is converted to lease + // expiry time (expire). The relationship is given by: + // expire = cltt_ + valid_lft_ + // Avoid overflow with infinite valid lifetime by using + // expire = cltt_ when valid_lft_ = 0xffffffff + if (lease_->valid_lft_ == Lease::INFINITY_LFT) { + expire_str_ = convertToDatabaseTime(lease->cltt_, 0); + } else { + expire_str_ = convertToDatabaseTime(lease->cltt_, + lease_->valid_lft_); + } + bind_array.add(expire_str_); + + subnet_id_str_ = boost::lexical_cast<std::string>(lease->subnet_id_); + bind_array.add(subnet_id_str_); + + bind_array.add(lease->fqdn_fwd_); + + bind_array.add(lease->fqdn_rev_); + + bind_array.add(lease->hostname_); + + state_str_ = boost::lexical_cast<std::string>(lease->state_); + bind_array.add(state_str_); + + ConstElementPtr ctx = lease->getContext(); + if (ctx) { + user_context_ = ctx->str(); + } else { + user_context_ = ""; + } + bind_array.add(user_context_); + + if (!lease->relay_id_.empty()) { + bind_array.add(lease->relay_id_); + } else { + bind_array.addNull(); + } + + if (!lease->remote_id_.empty()) { + bind_array.add(lease->remote_id_); + } else { + bind_array.addNull(); + } + + pool_id_str_ = boost::lexical_cast<std::string>(lease->pool_id_); + bind_array.add(pool_id_str_); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, + "Could not create bind array from Lease4: " + << lease_->addr_.toText() << ", reason: " << ex.what()); + } + } + + /// @brief Creates a Lease4 object from a given row in a result set. + /// + /// @param r result set containing one or rows from the Lease4 table + /// @param row row number within the result set from to create the Lease4 + /// object. + /// + /// @return Lease4Ptr to the newly created Lease4 object + /// @throw DbOperationError if the lease cannot be created. + Lease4Ptr convertFromDatabase(const PgSqlResult& r, int row) { + try { + getColumnValue(r, row, ADDRESS_COL, addr4_); + + convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_, + sizeof(hwaddr_buffer_), hwaddr_length_); + + convertFromBytea(r, row, CLIENT_ID_COL, client_id_buffer_, + sizeof(client_id_buffer_), client_id_length_); + + getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_); + + expire_ = convertFromDatabaseTime(getRawColumnValue(r, row, + EXPIRE_COL)); + + getColumnValue(r, row, SUBNET_ID_COL, subnet_id_); + + // Recover from overflow (see createBindForSend) + if (valid_lifetime_ == Lease::INFINITY_LFT) { + cltt_ = expire_; + } else { + cltt_ = expire_ - valid_lifetime_; + } + + getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_); + + getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_); + + hostname_ = getRawColumnValue(r, row, HOSTNAME_COL); + + uint32_t state; + getColumnValue(r, row, STATE_COL, state); + + HWAddrPtr hwaddr(new HWAddr(hwaddr_buffer_, hwaddr_length_, + HTYPE_ETHER)); + + user_context_ = getRawColumnValue(r, row, USER_CONTEXT_COL); + ConstElementPtr ctx; + if (!user_context_.empty()) { + ctx = Element::fromJSON(user_context_); + if (!ctx || (ctx->getType() != Element::map)) { + isc_throw(BadValue, "user context '" << user_context_ + << "' is not a JSON map"); + } + } + + convertFromBytea(r, row, RELAY_ID_COL, relay_id_buffer_, + sizeof(relay_id_buffer_), relay_id_length_); + + convertFromBytea(r, row, REMOTE_ID_COL, remote_id_buffer_, + sizeof(remote_id_buffer_), remote_id_length_); + + getColumnValue(r, row, POOL_ID_COL, pool_id_); + + Lease4Ptr result(boost::make_shared<Lease4>(addr4_, hwaddr, + client_id_buffer_, + client_id_length_, + valid_lifetime_, cltt_, + subnet_id_, fqdn_fwd_, + fqdn_rev_, hostname_)); + + result->state_ = state; + + if (ctx) { + result->setContext(ctx); + } + + if (relay_id_length_) { + result->relay_id_.assign(relay_id_buffer_, + relay_id_buffer_ + relay_id_length_); + } + + if (remote_id_length_) { + result->remote_id_.assign(remote_id_buffer_, + remote_id_buffer_ + remote_id_length_); + } + + result->pool_id_ = pool_id_; + + return (result); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, + "Could not convert data to Lease4, reason: " + << ex.what()); + } + } + +private: + + /// @brief Lease4 object currently being sent to the database. + /// Storing this value ensures that it remains in scope while any bindings + /// that refer to its contents are in use. + Lease4Ptr lease_; + + /// @brief Lease4 specific members for binding and conversion. + uint32_t addr4_; + size_t client_id_length_; + uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; + size_t relay_id_length_; + uint8_t relay_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; + size_t remote_id_length_; + uint8_t remote_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; +}; + +/// @brief Supports exchanging IPv6 leases with PostgreSQL. +class PgSqlLease6Exchange : public PgSqlLeaseExchange { +private: + + /// @brief Column numbers for each column in the Lease6 table. + /// These are used for both retrieving data and for looking up + /// column labels for logging. Note that their numeric order + /// MUST match that of the column order in the Lease6 table. + //@{ + static const size_t ADDRESS_COL = 0; + static const size_t DUID_COL = 1; + static const size_t VALID_LIFETIME_COL = 2; + static const size_t EXPIRE_COL = 3; + static const size_t SUBNET_ID_COL = 4; + static const size_t PREF_LIFETIME_COL = 5; + static const size_t LEASE_TYPE_COL = 6; + static const size_t IAID_COL = 7; + static const size_t PREFIX_LEN_COL = 8; + static const size_t FQDN_FWD_COL = 9; + static const size_t FQDN_REV_COL = 10; + static const size_t HOSTNAME_COL = 11; + static const size_t HWADDR_COL = 12; + static const size_t HWTYPE_COL = 13; + static const size_t HWADDR_SOURCE_COL = 14; + static const size_t STATE_COL = 15; + static const size_t USER_CONTEXT_COL = 16; + static const size_t POOL_ID_COL = 17; + //@} + /// @brief Number of columns in the table holding DHCPv6 leases. + static const size_t LEASE_COLUMNS = 18; + +public: + + /// @brief Union for marshalling IAID into and out of the database + /// IAID is defined in the RFC as 4 octets, which Kea code handles as + /// a uint32_t. Postgresql however, offers only signed integer types + /// of sizes 2, 4, and 8 bytes (SMALLINT, INT, and BIGINT respectively). + /// IAID is used in several indexes so rather than use the BIGINT, we + /// use this union to safely move the value into and out of an INT column. + union Uiaid { + /// @brief Constructor + /// @param val unsigned 32 bit value for the IAID. + Uiaid(uint32_t val) : uval_(val) {}; + + /// @brief Constructor + /// @param val signed 32 bit value for the IAID. + Uiaid(int32_t val) : ival_(val) {}; + + /// @brief Return a string representing the signed 32-bit value. + std::string dbInputString() { + return (boost::lexical_cast<std::string>(ival_)); + }; + + uint32_t uval_; + int32_t ival_; + }; + + PgSqlLease6Exchange() + : lease_(), duid_length_(0), duid_(duid_length_), iaid_u_(0), + iaid_str_(""), lease_type_(Lease6::TYPE_NA), lease_type_str_(""), + prefix_len_(0), prefix_len_str_(""), pref_lifetime_(0), + preferred_lifetime_str_(""), hwtype_(0), hwtype_str_(""), + hwaddr_source_(0), hwaddr_source_str_("") { + + BOOST_STATIC_ASSERT(17 < LEASE_COLUMNS); + + memset(duid_buffer_, 0, sizeof(duid_buffer_)); + + // Set the column names (for error messages) + columns_.push_back("address"); + columns_.push_back("duid"); + columns_.push_back("valid_lifetime"); + columns_.push_back("expire"); + columns_.push_back("subnet_id"); + columns_.push_back("pref_lifetime"); + columns_.push_back("lease_type"); + columns_.push_back("iaid"); + columns_.push_back("prefix_len"); + columns_.push_back("fqdn_fwd"); + columns_.push_back("fqdn_rev"); + columns_.push_back("hostname"); + columns_.push_back("hwaddr"); + columns_.push_back("hwtype"); + columns_.push_back("hwaddr_source"); + columns_.push_back("state"); + columns_.push_back("user_context"); + columns_.push_back("pool_id"); + } + + /// @brief Creates the bind array for sending Lease6 data to the database. + /// + /// Converts each Lease6 member into the appropriate form and adds it + /// to the bind array. Note that the array additions must occur in the + /// order the columns are specified in the SQL statement. By convention + /// all columns in the table are explicitly listed in the SQL statement(s) + /// in the same order as they occur in the table. + /// + /// @param lease Lease6 object that is to be written to the database + /// @param[out] bind_array array to populate with the lease data values + /// + /// @throw DbOperationError if bind_array cannot be populated. + void createBindForSend(const Lease6Ptr& lease, PsqlBindArray& bind_array) { + if (!lease) { + isc_throw(BadValue, "createBindForSend:: Lease6 object is NULL"); + } + + // Store lease object to ensure it remains valid. + lease_ = lease; + try { + addr_str_ = lease_->addr_.toText(); + bind_array.add(addr_str_); + + if (lease_->duid_) { + bind_array.add(lease_->duid_->getDuid()); + } else { + isc_throw (BadValue, "IPv6 Lease cannot have a null DUID"); + } + + valid_lifetime_str_ = boost::lexical_cast<std::string>(lease->valid_lft_); + bind_array.add(valid_lifetime_str_); + + // The lease structure holds the client last transmission time (cltt_) + // For convenience for external tools, this is converted to lease + // expiry time (expire). The relationship is given by: + // expire = cltt_ + valid_lft_ + // Avoid overflow with infinite valid lifetime by using + // expire = cltt_ when valid_lft_ = 0xffffffff + if (lease_->valid_lft_ == Lease::INFINITY_LFT) { + expire_str_ = convertToDatabaseTime(lease->cltt_, 0); + } else { + expire_str_ = convertToDatabaseTime(lease->cltt_, + lease_->valid_lft_); + } + bind_array.add(expire_str_); + + subnet_id_str_ = boost::lexical_cast<std::string>(lease->subnet_id_); + bind_array.add(subnet_id_str_); + + preferred_lifetime_str_ = boost::lexical_cast<std::string>(lease_->preferred_lft_); + bind_array.add(preferred_lifetime_str_); + + lease_type_str_ = boost::lexical_cast<std::string>(lease_->type_); + bind_array.add(lease_type_str_); + + // The iaid is stored as an INT in lease6 table, so we must + // lexically cast from an integer version to avoid out of range + // exception failure upon insert. + iaid_u_.uval_ = lease_->iaid_; + iaid_str_ = iaid_u_.dbInputString(); + bind_array.add(iaid_str_); + + prefix_len_str_ = boost::lexical_cast<std::string> + (static_cast<unsigned int>(lease_->prefixlen_)); + bind_array.add(prefix_len_str_); + + bind_array.add(lease->fqdn_fwd_); + + bind_array.add(lease->fqdn_rev_); + + bind_array.add(lease->hostname_); + + if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) { + // PostgreSql does not provide MAX on variable length types + // so we have to enforce it ourselves. + if (lease->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) { + isc_throw(DbOperationError, "Hardware address length : " + << lease_->hwaddr_->hwaddr_.size() + << " exceeds maximum allowed of: " + << HWAddr::MAX_HWADDR_LEN); + } + bind_array.add(lease->hwaddr_->hwaddr_); + } else { + bind_array.add(""); + } + + if (lease->hwaddr_) { + hwtype_str_ = boost::lexical_cast<std::string> + (static_cast<unsigned int>(lease_->hwaddr_->htype_)); + hwaddr_source_str_ = boost::lexical_cast<std::string> + (static_cast<unsigned int>(lease_->hwaddr_->source_)); + } else { + hwtype_str_ = boost::lexical_cast<std::string> + (static_cast<unsigned int>(HTYPE_UNDEFINED)); + hwaddr_source_str_ = boost::lexical_cast<std::string> + (static_cast<unsigned int>(HWAddr::HWADDR_SOURCE_UNKNOWN)); + } + + bind_array.add(hwtype_str_); + + bind_array.add(hwaddr_source_str_); + + state_str_ = boost::lexical_cast<std::string>(lease->state_); + bind_array.add(state_str_); + + ConstElementPtr ctx = lease->getContext(); + if (ctx) { + user_context_ = ctx->str(); + } else { + user_context_ = ""; + } + bind_array.add(user_context_); + + pool_id_str_ = boost::lexical_cast<std::string>(lease->pool_id_); + bind_array.add(pool_id_str_); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, + "Could not create bind array from Lease6: " + << lease_->addr_.toText() << ", reason: " << ex.what()); + } + } + + /// @brief Creates a Lease6 object from a given row in a result set. + /// + /// @param r result set containing one or rows from the Lease6 table + /// @param row row number within the result set from to create the Lease6 + /// object. + /// + /// @return Lease6Ptr to the newly created Lease4 object + /// @throw DbOperationError if the lease cannot be created. + Lease6Ptr convertFromDatabase(const PgSqlResult& r, int row) { + try { + + /// @todo In theory, an administrator could tweak lease + /// information in the database. In this case, some of the + /// values could be set to NULL. This is less likely than + /// in case of host reservations, but we may consider if + /// retrieved values should be checked for being NULL to + /// prevent cryptic errors during conversions from NULL + /// to actual values. + + IOAddress addr(getIPv6Value(r, row, ADDRESS_COL)); + + convertFromBytea(r, row, DUID_COL, duid_buffer_, sizeof(duid_buffer_), duid_length_); + DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_)); + + getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_); + + expire_ = convertFromDatabaseTime(getRawColumnValue(r, row, + EXPIRE_COL)); + + // Recover from overflow (see createBindForSend) + if (valid_lifetime_ == Lease::INFINITY_LFT) { + cltt_ = expire_; + } else { + cltt_ = expire_ - valid_lifetime_; + } + + getColumnValue(r, row, SUBNET_ID_COL, subnet_id_); + + getColumnValue(r, row, PREF_LIFETIME_COL, pref_lifetime_); + + getLeaseTypeColumnValue(r, row, LEASE_TYPE_COL, lease_type_); + + getColumnValue(r, row, IAID_COL, iaid_u_.ival_); + + getColumnValue(r, row, PREFIX_LEN_COL, prefix_len_); + + getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_); + + getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_); + + hostname_ = getRawColumnValue(r, row, HOSTNAME_COL); + + convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_, + sizeof(hwaddr_buffer_), hwaddr_length_); + + getColumnValue(r, row, HWTYPE_COL, hwtype_); + + getColumnValue(r, row, HWADDR_SOURCE_COL, hwaddr_source_); + + HWAddrPtr hwaddr; + + if (hwaddr_length_) { + hwaddr.reset(new HWAddr(hwaddr_buffer_, hwaddr_length_, + hwtype_)); + + hwaddr->source_ = hwaddr_source_; + } + + uint32_t state; + getColumnValue(r, row, STATE_COL, state); + + user_context_ = getRawColumnValue(r, row, USER_CONTEXT_COL); + ConstElementPtr ctx; + if (!user_context_.empty()) { + ctx = Element::fromJSON(user_context_); + if (!ctx || (ctx->getType() != Element::map)) { + isc_throw(BadValue, "user context '" << user_context_ + << "' is not a JSON map"); + } + } + + getColumnValue(r, row, POOL_ID_COL, pool_id_); + + if (lease_type_ != Lease::TYPE_PD) { + prefix_len_ = 128; + } + + Lease6Ptr result(boost::make_shared<Lease6>(lease_type_, addr, + duid_ptr, + iaid_u_.uval_, + pref_lifetime_, + valid_lifetime_, + subnet_id_, fqdn_fwd_, + fqdn_rev_, hostname_, + hwaddr, prefix_len_)); + // Update cltt_ and current_cltt_ explicitly. + result->cltt_ = cltt_; + result->current_cltt_ = cltt_; + + result->state_ = state; + + if (ctx) { + result->setContext(ctx); + } + + result->pool_id_ = pool_id_; + + return (result); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, + "Could not convert data to Lease6, reason: " + << ex.what()); + } + } + + /// @brief Fetches an integer text column as a Lease6::Type + /// + /// @param r the result set containing the query results + /// @param row the row number within the result set + /// @param col the column number within the row + /// @param[out] value parameter to receive the converted value + /// + /// Note we depart from overloading getColumnValue to avoid ambiguity + /// with base class methods for integers. + /// + /// @throw DbOperationError if the value cannot be fetched or is + /// invalid. + void getLeaseTypeColumnValue(const PgSqlResult& r, const int row, + const size_t col, Lease6::Type& value) const { + uint32_t raw_value = 0; + getColumnValue(r, row, col, raw_value); + switch (raw_value) { + case Lease6::TYPE_NA: + case Lease6::TYPE_TA: + case Lease6::TYPE_PD: + value = static_cast<Lease6::Type>(raw_value); + break; + + default: + isc_throw(DbOperationError, "Invalid lease type: " << raw_value + << " for: " << getColumnLabel(r, col) << " row:" << row); + } + } + +private: + /// @brief Lease6 object currently being sent to the database. + /// Storing this value ensures that it remains in scope while any bindings + /// that refer to its contents are in use. + Lease6Ptr lease_; + + /// @brief Lease6 specific members for binding and conversion. + //@{ + size_t duid_length_; + std::vector<uint8_t> duid_; + uint8_t duid_buffer_[DUID::MAX_DUID_LEN]; + union Uiaid iaid_u_; + std::string iaid_str_; + Lease6::Type lease_type_; + std::string lease_type_str_; + uint8_t prefix_len_; + std::string prefix_len_str_; + uint32_t pref_lifetime_; + std::string preferred_lifetime_str_; + uint32_t hwtype_; + std::string hwtype_str_; + uint32_t hwaddr_source_; + std::string hwaddr_source_str_; + //@} +}; + +/// @brief Base PgSql derivation of the statistical lease data query +/// +/// This class provides the functionality such as results storage and row +/// fetching common to fulfilling the statistical lease data query. +/// +class PgSqlLeaseStatsQuery : public LeaseStatsQuery { +public: + + /// @brief Constructor to query for all subnets' stats + /// + /// The query created will return statistics for all subnets + /// + /// @param conn A open connection to the database housing the lease data + /// @param statement The lease data SQL prepared statement to execute + /// @param fetch_type Indicates whether or not lease_type should be + /// fetched from the result set + /// @param fetch_pool Indicates if query requires pool data + PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement, + const bool fetch_type, const bool fetch_pool = false) + : conn_(conn), statement_(statement), result_set_(), next_row_(0), + fetch_type_(fetch_type), fetch_pool_(fetch_pool) { + } + + /// @brief Constructor to query for a single subnet's stats + /// + /// The query created will return statistics for a single subnet + /// + /// @param conn A open connection to the database housing the lease data + /// @param statement The lease data SQL prepared statement to execute + /// @param fetch_type Indicates if query supplies lease type + /// @param subnet_id id of the subnet for which stats are desired + PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement, + const bool fetch_type, const SubnetID& subnet_id) + : LeaseStatsQuery(subnet_id), conn_(conn), statement_(statement), result_set_(), + next_row_(0), fetch_type_(fetch_type), fetch_pool_(false) { + } + + /// @brief Constructor to query for the stats for a range of subnets + /// + /// The query created will return statistics for the inclusive range of + /// subnets described by first and last subnet IDs. + /// + /// @param conn A open connection to the database housing the lease data + /// @param statement The lease data SQL prepared statement to execute + /// @param fetch_type Indicates if query supplies lease type + /// @param first_subnet_id first subnet in the range of subnets + /// @param last_subnet_id last subnet in the range of subnets + PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement, + const bool fetch_type, const SubnetID& first_subnet_id, + const SubnetID& last_subnet_id) + : LeaseStatsQuery(first_subnet_id, last_subnet_id), conn_(conn), statement_(statement), + result_set_(), next_row_(0), fetch_type_(fetch_type), fetch_pool_(false) { + } + + /// @brief Destructor + virtual ~PgSqlLeaseStatsQuery() {}; + + /// @brief Creates the lease statistical data result set + /// + /// The result set is populated by executing a prepared SQL query + /// against the database fetches the lease count per lease state per + /// (per least type - v6 only) per subnet id. + /// + /// Depending upon the selection mode, the query will have either no + /// parameters (for all subnets), a subnet id for a single subnet, or + /// a first and last subnet id for a subnet range. + void start() { + + if (getSelectMode() == ALL_SUBNETS || getSelectMode() == ALL_SUBNET_POOLS) { + // Run the query with no where clause parameters. + result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name, + 0, 0, 0, 0, 0))); + } else { + // Set up the WHERE clause values + PsqlBindArray parms; + + // Add first_subnet_id used by both single and range. + parms.addTempString(boost::lexical_cast<std::string>(getFirstSubnetID())); + + // Add last_subnet_id for range. + if (getSelectMode() == SUBNET_RANGE) { + // Add last_subnet_id used by range. + parms.addTempString(boost::lexical_cast<std::string>(getLastSubnetID())); + } + + // Run the query with where clause parameters. + result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name, + parms.size(), &parms.values_[0], + &parms.lengths_[0], &parms.formats_[0], 0))); + } + + conn_.checkStatementError(*result_set_, statement_); + } + + /// @brief Fetches the next row in the result set + /// + /// Once the internal result set has been populated by invoking the + /// the start() method, this method is used to iterate over the + /// result set rows. Once the last row has been fetched, subsequent + /// calls will return false. + /// + /// Checks against negative values for the state count and logs once + /// a warning message. + /// + /// @param row Storage for the fetched row + /// + /// @return True if the fetch succeeded, false if there are no more + /// rows to fetch. + bool getNextRow(LeaseStatsRow& row) { + // If we're past the end, punt. + if (next_row_ >= result_set_->getRows()) { + return (false); + } + + // Fetch the subnet id. + uint32_t col = 0; + uint32_t subnet_id; + PgSqlExchange::getColumnValue(*result_set_, next_row_, col, subnet_id); + row.subnet_id_ = static_cast<SubnetID>(subnet_id); + ++col; + + // Fetch the pool id if we were told to do so. + if (fetch_pool_) { + PgSqlExchange::getColumnValue(*result_set_, next_row_, col, + row.pool_id_); + ++col; + } + + // Fetch the lease type if we were told to do so. + if (fetch_type_) { + uint32_t lease_type; + PgSqlExchange::getColumnValue(*result_set_, next_row_, col, + lease_type); + row.lease_type_ = static_cast<Lease::Type>(lease_type); + ++col; + } else { + row.lease_type_ = Lease::TYPE_NA; + } + + // Fetch the lease state. + PgSqlExchange::getColumnValue(*result_set_, next_row_, col, + row.lease_state_); + ++col; + + // Fetch the state count. + PgSqlExchange::getColumnValue(*result_set_, next_row_, col, + row.state_count_); + + // Protect against negative state count.a + if (row.state_count_ < 0) { + row.state_count_ = 0; + if (!negative_count_) { + negative_count_ = true; + LOG_WARN(dhcpsrv_logger, DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT); + } + } + + // Point to the next row. + ++next_row_; + return (true); + } + +protected: + + /// @brief Database connection to use to execute the query + PgSqlConnection& conn_; + + /// @brief The query's prepared statement + PgSqlTaggedStatement& statement_; + + /// @brief The result set returned by Postgres. + boost::shared_ptr<PgSqlResult> result_set_; + + /// @brief Index of the next row to fetch + uint32_t next_row_; + + /// @brief Indicates if query supplies lease type + bool fetch_type_; + + /// @brief Indicates if query requires pool data + bool fetch_pool_; + + /// @brief Received negative state count showing a problem + static bool negative_count_; +}; + +// Initialize negative state count flag to false. +bool PgSqlLeaseStatsQuery::negative_count_ = false; + +// PgSqlLeaseContext Constructor + +PgSqlLeaseContext::PgSqlLeaseContext(const DatabaseConnection::ParameterMap& parameters, + IOServiceAccessorPtr io_service_accessor, + DbCallback db_reconnect_callback) + : conn_(parameters, io_service_accessor, db_reconnect_callback) { +} + +// PgSqlLeaseContextAlloc Constructor and Destructor + +PgSqlLeaseMgr::PgSqlLeaseContextAlloc::PgSqlLeaseContextAlloc( + const PgSqlLeaseMgr& mgr) : ctx_(), mgr_(mgr) { + + if (MultiThreadingMgr::instance().getMode()) { + // multi-threaded + { + // we need to protect the whole pool_ operation, hence extra scope {} + lock_guard<mutex> lock(mgr_.pool_->mutex_); + if (!mgr_.pool_->pool_.empty()) { + ctx_ = mgr_.pool_->pool_.back(); + mgr_.pool_->pool_.pop_back(); + } + } + if (!ctx_) { + ctx_ = mgr_.createContext(); + } + } else { + // single-threaded + if (mgr_.pool_->pool_.empty()) { + isc_throw(Unexpected, "No available PostgreSQL lease context?!"); + } + ctx_ = mgr_.pool_->pool_.back(); + } +} + +PgSqlLeaseMgr::PgSqlLeaseContextAlloc::~PgSqlLeaseContextAlloc() { + if (MultiThreadingMgr::instance().getMode()) { + // multi-threaded + lock_guard<mutex> lock(mgr_.pool_->mutex_); + mgr_.pool_->pool_.push_back(ctx_); + } + // If running in single-threaded mode, there's nothing to do here. +} + +// PgSqlLeaseTrackingContextAlloc Constructor and Destructor + +PgSqlLeaseMgr::PgSqlLeaseTrackingContextAlloc::PgSqlLeaseTrackingContextAlloc( + PgSqlLeaseMgr& mgr, const LeasePtr& lease) : ctx_(), mgr_(mgr), lease_(lease) { + + if (MultiThreadingMgr::instance().getMode()) { + // multi-threaded + { + // we need to protect the whole pool_ operation, hence extra scope {} + lock_guard<mutex> lock(mgr_.pool_->mutex_); + if (mgr_.hasCallbacks() && !mgr_.tryLock(lease)) { + isc_throw(DbOperationError, "unable to lock the lease " << lease->addr_); + } + if (!mgr_.pool_->pool_.empty()) { + ctx_ = mgr_.pool_->pool_.back(); + mgr_.pool_->pool_.pop_back(); + } + } + if (!ctx_) { + ctx_ = mgr_.createContext(); + } + } else { + // single-threaded + if (mgr_.pool_->pool_.empty()) { + isc_throw(Unexpected, "No available PostgreSQL lease context?!"); + } + ctx_ = mgr_.pool_->pool_.back(); + } +} + +PgSqlLeaseMgr::PgSqlLeaseTrackingContextAlloc::~PgSqlLeaseTrackingContextAlloc() { + if (MultiThreadingMgr::instance().getMode()) { + // multi-threaded + lock_guard<mutex> lock(mgr_.pool_->mutex_); + if (mgr_.hasCallbacks()) { + mgr_.unlock(lease_); + } + mgr_.pool_->pool_.push_back(ctx_); + } + // If running in single-threaded mode, there's nothing to do here. +} + +void +PgSqlLeaseMgr::setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& /* parameters */) { + isc_throw(isc::NotImplemented, "extended info tables are not yet supported by mysql"); +} + +// PgSqlLeaseMgr Constructor and Destructor + +PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters) + : TrackingLeaseMgr(), parameters_(parameters), timer_name_("") { + + // Check if the extended info tables are enabled. + LeaseMgr::setExtendedInfoTablesEnabled(parameters); + + // Create unique timer name per instance. + timer_name_ = "PgSqlLeaseMgr["; + timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this)); + timer_name_ += "]DbReconnectTimer"; + + // Check TLS support. + size_t tls(0); + tls += parameters.count("trust-anchor"); + tls += parameters.count("cert-file"); + tls += parameters.count("key-file"); + tls += parameters.count("cipher-list"); +#ifdef HAVE_PGSQL_SSL + if ((tls > 0) && !PgSqlConnection::warned_about_tls) { + PgSqlConnection::warned_about_tls = true; + LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_TLS_SUPPORT) + .arg(DatabaseConnection::redactedAccessString(parameters_)); + PQinitSSL(1); + } +#else + if (tls > 0) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_NO_TLS_SUPPORT) + .arg(DatabaseConnection::redactedAccessString(parameters_)); + isc_throw(DbOpenError, "Attempt to configure TLS for PostgreSQL " + << "backend (built with this feature disabled)"); + } +#endif + + // Validate schema version first. + std::pair<uint32_t, uint32_t> code_version(PGSQL_SCHEMA_VERSION_MAJOR, + PGSQL_SCHEMA_VERSION_MINOR); + std::pair<uint32_t, uint32_t> db_version = getVersion(); + if (code_version != db_version) { + isc_throw(DbOpenError, + "PostgreSQL schema version mismatch: need version: " + << code_version.first << "." << code_version.second + << " found version: " << db_version.first << "." + << db_version.second); + } + + // Create an initial context. + pool_.reset(new PgSqlLeaseContextPool()); + pool_->pool_.push_back(createContext()); +} + +PgSqlLeaseMgr::~PgSqlLeaseMgr() { +} + +bool +PgSqlLeaseMgr::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) { + MultiThreadingCriticalSection cs; + + // Invoke application layer connection lost callback. + if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) { + return (false); + } + + bool reopened = false; + + const std::string timer_name = db_reconnect_ctl->timerName(); + + // At least one connection was lost. + try { + CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess(); + LeaseMgrFactory::recreate(cfg_db->getLeaseDbAccessString()); + reopened = true; + } catch (const std::exception& ex) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED) + .arg(ex.what()); + } + + if (reopened) { + // Cancel the timer. + if (TimerMgr::instance()->isTimerRegistered(timer_name)) { + TimerMgr::instance()->unregisterTimer(timer_name); + } + + // Invoke application layer connection recovered callback. + if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) { + return (false); + } + } else { + if (!db_reconnect_ctl->checkRetries()) { + // We're out of retries, log it and initiate shutdown. + LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED) + .arg(db_reconnect_ctl->maxRetries()); + + // Cancel the timer. + if (TimerMgr::instance()->isTimerRegistered(timer_name)) { + TimerMgr::instance()->unregisterTimer(timer_name); + } + + // Invoke application layer connection failed callback. + DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl); + return (false); + } + + LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE) + .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1) + .arg(db_reconnect_ctl->maxRetries()) + .arg(db_reconnect_ctl->retryInterval()); + + // Start the timer. + if (!TimerMgr::instance()->isTimerRegistered(timer_name)) { + TimerMgr::instance()->registerTimer(timer_name, + std::bind(&PgSqlLeaseMgr::dbReconnect, db_reconnect_ctl), + db_reconnect_ctl->retryInterval(), + asiolink::IntervalTimer::ONE_SHOT); + } + TimerMgr::instance()->setup(timer_name); + } + + return (true); +} + +// Create context. + +PgSqlLeaseContextPtr +PgSqlLeaseMgr::createContext() const { + PgSqlLeaseContextPtr ctx(new PgSqlLeaseContext(parameters_, + IOServiceAccessorPtr(new IOServiceAccessor(&LeaseMgr::getIOService)), + &PgSqlLeaseMgr::dbReconnect)); + + // Open the database. + ctx->conn_.openDatabase(); + + // Now prepare the SQL statements. + uint32_t i = 0; + for (; tagged_statements[i].text != NULL; ++i) { + ctx->conn_.prepareStatement(tagged_statements[i]); + } + + // Just in case somebody foo-barred things + if (i != NUM_STATEMENTS) { + isc_throw(DbOpenError, "Number of statements prepared: " << i + << " does not match expected count:" << NUM_STATEMENTS); + } + + // Create the exchange objects for use in exchanging data between the + // program and the database. + ctx->exchange4_.reset(new PgSqlLease4Exchange()); + ctx->exchange6_.reset(new PgSqlLease6Exchange()); + + // Create ReconnectCtl for this connection. + ctx->conn_.makeReconnectCtl(timer_name_); + + return (ctx); +} + +std::string +PgSqlLeaseMgr::getDBVersion() { + std::stringstream tmp; + tmp << "PostgreSQL backend " << PGSQL_SCHEMA_VERSION_MAJOR; + tmp << "." << PGSQL_SCHEMA_VERSION_MINOR; + tmp << ", library " << PQlibVersion(); + return (tmp.str()); +} + +bool +PgSqlLeaseMgr::addLeaseCommon(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, + PsqlBindArray& bind_array) { + PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + + int s = PQresultStatus(r); + + if (s != PGRES_COMMAND_OK) { + // Failure: check for the special case of duplicate entry. If this is + // the case, we return false to indicate that the row was not added. + // Otherwise we throw an exception. + if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) { + return (false); + } + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + } + + return (true); +} + +bool +PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ADD_ADDR4) + .arg(lease->addr_.toText()); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + PsqlBindArray bind_array; + ctx->exchange4_->createBindForSend(lease, bind_array); + auto result = addLeaseCommon(ctx, INSERT_LEASE4, bind_array); + + // Update lease current expiration time (allows update between the creation + // of the Lease up to the point of insertion in the database). + lease->updateCurrentExpirationTime(); + + // Run installed callbacks. + if (hasCallbacks()) { + trackAddLease(lease); + } + + return (result); +} + +bool +PgSqlLeaseMgr::addLease(const Lease6Ptr& lease) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ADD_ADDR6) + .arg(lease->addr_.toText()) + .arg(lease->type_); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + PsqlBindArray bind_array; + ctx->exchange6_->createBindForSend(lease, bind_array); + + auto result = addLeaseCommon(ctx, INSERT_LEASE6, bind_array); + + // Update lease current expiration time (allows update between the creation + // of the Lease up to the point of insertion in the database). + lease->updateCurrentExpirationTime(); + + // Run installed callbacks. + if (hasCallbacks()) { + trackAddLease(lease); + } + + return (result); +} + +template <typename Exchange, typename LeaseCollection> +void +PgSqlLeaseMgr::getLeaseCollection(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, + PsqlBindArray& bind_array, + Exchange& exchange, + LeaseCollection& result, + bool single) const { + const int n = tagged_statements[stindex].nbparams; + PgSqlResult r(PQexecPrepared(ctx->conn_, + tagged_statements[stindex].name, n, + n > 0 ? &bind_array.values_[0] : NULL, + n > 0 ? &bind_array.lengths_[0] : NULL, + n > 0 ? &bind_array.formats_[0] : NULL, 0)); + + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + int rows = PQntuples(r); + if (single && rows > 1) { + isc_throw(MultipleRecords, "multiple records were found in the " + "database where only one was expected for query " + << tagged_statements[stindex].name); + } + + for(int i = 0; i < rows; ++i) { + result.push_back(exchange->convertFromDatabase(r, i)); + } +} + +void +PgSqlLeaseMgr::getLease(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, PsqlBindArray& bind_array, + Lease4Ptr& result) const { + // Create appropriate collection object and get all leases matching + // the selection criteria. The "single" parameter 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(ctx, stindex, bind_array, ctx->exchange4_, + collection, true); + + // Return single record if present, else clear the lease. + if (collection.empty()) { + result.reset(); + } else { + result = *collection.begin(); + } +} + +void +PgSqlLeaseMgr::getLease(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, PsqlBindArray& bind_array, + Lease6Ptr& result) const { + // Create appropriate collection object and get all leases matching + // the selection criteria. The "single" parameter 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(ctx, stindex, bind_array, ctx->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 IOAddress& addr) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_ADDR4) + .arg(addr.toText()); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // LEASE ADDRESS + std::string addr_str = boost::lexical_cast<std::string>(addr.toUint32()); + bind_array.add(addr_str); + + // Get the data + Lease4Ptr result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLease(ctx, GET_LEASE4_ADDR, bind_array, 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 + PsqlBindArray bind_array; + + // HWADDR + if (!hwaddr.hwaddr_.empty()) { + bind_array.add(hwaddr.hwaddr_); + } else { + bind_array.add(""); + } + + // Get the data + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_HWADDR, bind_array, 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 + PsqlBindArray bind_array; + + // HWADDR + if (!hwaddr.hwaddr_.empty()) { + bind_array.add(hwaddr.hwaddr_); + } else { + bind_array.add(""); + } + + // SUBNET_ID + std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id); + bind_array.add(subnet_id_str); + + // Get the data + Lease4Ptr result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLease(ctx, GET_LEASE4_HWADDR_SUBID, bind_array, 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 + PsqlBindArray bind_array; + + // CLIENT_ID + bind_array.add(clientid.getClientId()); + + // Get the data + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_CLIENTID, bind_array, 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 + PsqlBindArray bind_array; + + // CLIENT_ID + bind_array.add(clientid.getClientId()); + + // SUBNET_ID + std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id); + bind_array.add(subnet_id_str); + + // Get the data + Lease4Ptr result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLease(ctx, GET_LEASE4_CLIENTID_SUBID, bind_array, result); + + return (result); +} + +Lease4Collection +PgSqlLeaseMgr::getLeases4(SubnetID subnet_id) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID4) + .arg(subnet_id); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // SUBNET_ID + std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id); + bind_array.add(subnet_id_str); + + // ... and get the data + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_SUBID, bind_array, result); + + return (result); +} + +Lease4Collection +PgSqlLeaseMgr::getLeases4(const std::string& hostname) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_HOSTNAME4) + .arg(hostname); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // Hostname + bind_array.add(hostname); + + // ... and get the data + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_HOSTNAME, bind_array, result); + + return (result); +} + +Lease4Collection +PgSqlLeaseMgr::getLeases4() const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET4); + + // Provide empty binding array because our query has no parameters in + // WHERE clause. + PsqlBindArray bind_array; + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4, bind_array, result); + + return (result); +} + +Lease4Collection +PgSqlLeaseMgr::getLeases4(const IOAddress& lower_bound_address, + const LeasePageSize& page_size) const { + // Expecting IPv4 address. + if (!lower_bound_address.isV4()) { + isc_throw(InvalidAddressFamily, "expected IPv4 address while " + "retrieving leases from the lease database, got " + << lower_bound_address); + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_PAGE4) + .arg(page_size.page_size_) + .arg(lower_bound_address.toText()); + + // Prepare WHERE clause + PsqlBindArray bind_array; + + // Bind lower bound address + std::string lb_address_data = boost::lexical_cast<std::string>(lower_bound_address.toUint32()); + bind_array.add(lb_address_data); + + // Bind page size value + std::string page_size_data = boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + // Get the leases + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_PAGE, bind_array, result); + + return (result); +} + +Lease6Ptr +PgSqlLeaseMgr::getLease6(Lease::Type lease_type, + const 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 + PsqlBindArray bind_array; + + // LEASE ADDRESS + std::string addr_str = addr.toText(); + bind_array.add(addr_str); + + // LEASE_TYPE + std::string type_str_ = boost::lexical_cast<std::string>(lease_type); + bind_array.add(type_str_); + + // ... and get the data + Lease6Ptr result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLease(ctx, GET_LEASE6_ADDR, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6(Lease::Type lease_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(lease_type); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // DUID + bind_array.add(duid.getDuid()); + + // IAID + std::string iaid_str = PgSqlLease6Exchange::Uiaid(iaid).dbInputString(); + bind_array.add(iaid_str); + + // LEASE_TYPE + std::string lease_type_str = boost::lexical_cast<std::string>(lease_type); + bind_array.add(lease_type_str); + + // ... and get the data + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6_DUID_IAID, bind_array, 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_PGSQL_GET_IAID_SUBID_DUID) + .arg(iaid) + .arg(subnet_id) + .arg(duid.toText()) + .arg(lease_type); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // LEASE_TYPE + std::string lease_type_str = boost::lexical_cast<std::string>(lease_type); + bind_array.add(lease_type_str); + + // DUID + bind_array.add(duid.getDuid()); + + // IAID + std::string iaid_str = PgSqlLease6Exchange::Uiaid(iaid).dbInputString(); + bind_array.add(iaid_str); + + // SUBNET ID + std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id); + bind_array.add(subnet_id_str); + + // ... and get the data + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6_DUID_IAID_SUBID, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6(SubnetID subnet_id) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID6) + .arg(subnet_id); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // SUBNET_ID + std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id); + bind_array.add(subnet_id_str); + + // ... and get the data + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6_SUBID, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6(const DUID& duid) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_DUID) + .arg(duid.toText()); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // DUID + bind_array.add(duid.getDuid()); + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // query to fetch the data + getLeaseCollection(ctx, GET_LEASE6_DUID, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6(const std::string& hostname) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_HOSTNAME6) + .arg(hostname); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + // Hostname + bind_array.add(hostname); + + // ... and get the data + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6_HOSTNAME, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6() const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET6); + + // Provide empty binding array because our query has no parameters in + // WHERE clause. + PsqlBindArray bind_array; + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6, bind_array, result); + + return (result); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6(const IOAddress& lower_bound_address, + const LeasePageSize& page_size) const { + // Expecting IPv6 address. + if (!lower_bound_address.isV6()) { + isc_throw(InvalidAddressFamily, "expected IPv6 address while " + "retrieving leases from the lease database, got " + << lower_bound_address); + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_PAGE6) + .arg(page_size.page_size_) + .arg(lower_bound_address.toText()); + + // Prepare WHERE clause + PsqlBindArray bind_array; + + // Bind lower bound address + std::string lb_address_data = lower_bound_address.toText(); + bind_array.add(lb_address_data); + + // Bind page size value + std::string page_size_data = + boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + // Get the leases + Lease6Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE6_PAGE, bind_array, result); + + return (result); +} + +void +PgSqlLeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases, + const size_t max_leases) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_EXPIRED4) + .arg(max_leases); + getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE4_EXPIRE); +} + +void +PgSqlLeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases, + const size_t max_leases) const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_EXPIRED6) + .arg(max_leases); + getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE6_EXPIRE); +} + +template<typename LeaseCollection> +void +PgSqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases, + const size_t max_leases, + StatementIndex statement_index) const { + PsqlBindArray bind_array; + + // Exclude reclaimed leases. + std::string state_str = boost::lexical_cast<std::string>(Lease::STATE_EXPIRED_RECLAIMED); + bind_array.add(state_str); + + // Expiration timestamp. + std::string timestamp_str = PgSqlLeaseExchange::convertToDatabaseTime(time(0)); + bind_array.add(timestamp_str); + + // If the number of leases is 0, we will return all leases. This is + // achieved by setting the limit to a very high value. + uint32_t limit = max_leases > 0 ? static_cast<uint32_t>(max_leases) : + std::numeric_limits<uint32_t>::max(); + std::string limit_str = boost::lexical_cast<std::string>(limit); + bind_array.add(limit_str); + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // Retrieve leases from the database. + getLeaseCollection(ctx, statement_index, bind_array, expired_leases); +} + +template<typename LeasePtr> +void +PgSqlLeaseMgr::updateLeaseCommon(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, + PsqlBindArray& bind_array, + const LeasePtr& lease) { + PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r)); + + // Check success case first as it is the most likely outcome. + if (affected_rows == 1) { + return; + } + + // If no rows affected, lease doesn't exist. + if (affected_rows == 0) { + isc_throw(NoSuchLease, "unable to update lease for address " << + lease->addr_.toText() << " as it does not exist"); + } + + // 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()); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // Create the BIND array for the data being updated + PsqlBindArray bind_array; + ctx->exchange4_->createBindForSend(lease, bind_array); + + // Set up the WHERE clause and append it to the SQL_BIND array + std::string addr4_str = boost::lexical_cast<std::string>(lease->addr_.toUint32()); + bind_array.add(addr4_str); + + std::string expire_str; + // Avoid overflow (see createBindForSend) + if (lease->current_valid_lft_ == Lease::INFINITY_LFT) { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0); + } else { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, + lease->current_valid_lft_); + } + bind_array.add(expire_str); + + // Drop to common update code + updateLeaseCommon(ctx, stindex, bind_array, lease); + + // Update lease current expiration time. + lease->updateCurrentExpirationTime(); + + // Run installed callbacks. + if (hasCallbacks()) { + trackUpdateLease(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()) + .arg(lease->type_); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // Create the BIND array for the data being updated + PsqlBindArray bind_array; + ctx->exchange6_->createBindForSend(lease, bind_array); + + // Set up the WHERE clause and append it to the BIND array + std::string addr_str = lease->addr_.toText(); + bind_array.add(addr_str); + + std::string expire_str; + // Avoid overflow (see createBindForSend) + if (lease->current_valid_lft_ == Lease::INFINITY_LFT) { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0); + } else { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, + lease->current_valid_lft_); + } + bind_array.add(expire_str); + + // Drop to common update code + updateLeaseCommon(ctx, stindex, bind_array, lease); + + // Update lease current expiration time. + lease->updateCurrentExpirationTime(); + + // Run installed callbacks. + if (hasCallbacks()) { + trackUpdateLease(lease); + } +} + +uint64_t +PgSqlLeaseMgr::deleteLeaseCommon(PgSqlLeaseContextPtr& ctx, + StatementIndex stindex, + PsqlBindArray& bind_array) { + PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r)); + + return (affected_rows); +} + +bool +PgSqlLeaseMgr::deleteLease(const Lease4Ptr& lease) { + const IOAddress& addr = lease->addr_; + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_ADDR) + .arg(addr.toText()); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + std::string addr4_str = boost::lexical_cast<std::string>(addr.toUint32()); + bind_array.add(addr4_str); + + std::string expire_str; + // Avoid overflow (see createBindForSend) + if (lease->current_valid_lft_ == Lease::INFINITY_LFT) { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0); + } else { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, + lease->current_valid_lft_); + } + bind_array.add(expire_str); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE4, bind_array); + + // Check success case first as it is the most likely outcome. + if (affected_rows == 1) { + if (hasCallbacks()) { + trackDeleteLease(lease); + } + return (true); + } + + // If no rows affected, lease doesn't exist. + if (affected_rows == 0) { + return (false); + } + + // Should not happen - primary key constraint should only have selected + // one row. + isc_throw(DbOperationError, "apparently deleted more than one lease " + "that had the address " << lease->addr_.toText()); +} + +bool +PgSqlLeaseMgr::deleteLease(const Lease6Ptr& lease) { + const IOAddress& addr = lease->addr_; + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_DELETE_ADDR) + .arg(addr.toText()); + + // Set up the WHERE clause value + PsqlBindArray bind_array; + + std::string addr6_str = addr.toText(); + bind_array.add(addr6_str); + + std::string expire_str; + // Avoid overflow (see createBindForSend) + if (lease->current_valid_lft_ == Lease::INFINITY_LFT) { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0); + } else { + expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, + lease->current_valid_lft_); + } + bind_array.add(expire_str); + + // Get a context + PgSqlLeaseTrackingContextAlloc get_context(*this, lease); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE6, bind_array); + + // Check success case first as it is the most likely outcome. + if (affected_rows == 1) { + if (hasCallbacks()) { + trackDeleteLease(lease); + } + return (true); + } + + // If no rows affected, lease doesn't exist. + if (affected_rows == 0) { + return (false); + } + + // Should not happen - primary key constraint should only have selected + // one row. + isc_throw(DbOperationError, "apparently deleted more than one lease " + "that had the address " << lease->addr_.toText()); +} + +uint64_t +PgSqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4) + .arg(secs); + return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE4_STATE_EXPIRED)); +} + +uint64_t +PgSqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6) + .arg(secs); + return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE6_STATE_EXPIRED)); +} + +uint64_t +PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs, + StatementIndex statement_index) { + PsqlBindArray bind_array; + + // State is reclaimed. + std::string state_str = boost::lexical_cast<std::string>(Lease::STATE_EXPIRED_RECLAIMED); + bind_array.add(state_str); + + // Expiration timestamp. + std::string expiration_str = PgSqlLeaseExchange::convertToDatabaseTime(time(0) - + static_cast<time_t>(secs)); + bind_array.add(expiration_str); + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // Delete leases. + return (deleteLeaseCommon(ctx, statement_index, bind_array)); +} + +string +PgSqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const { + // No user context means no limits means allocation allowed means empty string. + if (!user_context) { + return string(); + } + + // Get a context. + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx(get_context.ctx_); + + // Create bindings. + PsqlBindArray bind_array; + std::string const user_context_str(user_context->str()); + bind_array.add(user_context_str); + + // Execute the select. + PgSqlResult r(PQexecPrepared(ctx->conn_, + tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + std::string limits; + PgSqlExchange::getColumnValue(r, 0, 0, limits); + return limits; +} + +string +PgSqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE4_LIMITS); +} + +string +PgSqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const { + return checkLimits(user_context, CHECK_LEASE6_LIMITS); +} + +bool +PgSqlLeaseMgr::isJsonSupported() const { + // Get a context. + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx(get_context.ctx_); + + // Execute the select. + StatementIndex const stindex(IS_JSON_SUPPORTED); + PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name, + 0, 0, 0, 0, 0)); + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + bool json_supported; + PgSqlExchange::getColumnValue(r, 0, 0, json_supported); + return json_supported; +} + +size_t +PgSqlLeaseMgr::getClassLeaseCount(const ClientClass& client_class, + const Lease::Type& ltype /* = Lease::TYPE_V4*/) const { + // Get a context. + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx(get_context.ctx_); + + // Create bindings. + PsqlBindArray bind_array; + bind_array.add(client_class); + if (ltype != Lease::TYPE_V4) { + bind_array.add(ltype); + } + + // Execute the select. + StatementIndex const stindex(ltype == Lease::TYPE_V4 ? GET_LEASE4_COUNT_BY_CLASS : + GET_LEASE6_COUNT_BY_CLASS); + PgSqlResult r(PQexecPrepared(ctx->conn_, + tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array.values_[0], + &bind_array.lengths_[0], + &bind_array.formats_[0], 0)); + ctx->conn_.checkStatementError(r, tagged_statements[stindex]); + + int rows = PQntuples(r); + if (rows == 0) { + // No entries means 0 leases. + return 0; + } + + size_t count; + PgSqlExchange::getColumnValue(r, 0, 0, count); + return count; +} + +void +PgSqlLeaseMgr::recountClassLeases4() { + isc_throw(NotImplemented, "PgSqlLeaseMgr::recountClassLeases4() not implemented"); +} + +void +PgSqlLeaseMgr::recountClassLeases6() { + isc_throw(NotImplemented, "PgSqlLeaseMgr::recountClassLeases6() not implemented"); +} + +void +PgSqlLeaseMgr::clearClassLeaseCounts() { + isc_throw(NotImplemented, "PgSqlLeaseMgr::clearClassLeaseCounts() not implemented"); +} + +void +PgSqlLeaseMgr::writeLeases4(const std::string&) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::writeLeases4() not implemented"); +} + +void +PgSqlLeaseMgr::writeLeases6(const std::string&) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::writeLeases6() not implemented"); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startLeaseStatsQuery4() { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[ALL_LEASE4_STATS], + false)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startPoolLeaseStatsQuery4() { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[ALL_POOL_LEASE4_STATS], + false, true)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[SUBNET_LEASE4_STATS], + false, + subnet_id)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id, + const SubnetID& last_subnet_id) { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[SUBNET_RANGE_LEASE4_STATS], + false, + first_subnet_id, + last_subnet_id)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startLeaseStatsQuery6() { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[ALL_LEASE6_STATS], + true)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startPoolLeaseStatsQuery6() { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[ALL_POOL_LEASE6_STATS], + true, true)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[SUBNET_LEASE6_STATS], + true, + subnet_id)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id, + const SubnetID& last_subnet_id) { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_, + tagged_statements[SUBNET_RANGE_LEASE6_STATS], + true, + first_subnet_id, + last_subnet_id)); + query->start(); + return(query); +} + +size_t +PgSqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) { + isc_throw(NotImplemented, "wipeLeases4 is not implemented for PostgreSQL backend"); +} + +size_t +PgSqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) { + isc_throw(NotImplemented, "wipeLeases6 is not implemented for PostgreSQL backend"); +} + +std::string +PgSqlLeaseMgr::getName() const { + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + std::string name = ""; + try { + name = ctx->conn_.getParameter("name"); + } catch (...) { + // Return an empty name + } + return (name); +} + +std::string +PgSqlLeaseMgr::getDescription() const { + return (std::string("PostgreSQL Database")); +} + +std::pair<uint32_t, uint32_t> +PgSqlLeaseMgr::getVersion() const { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_VERSION); + + return (PgSqlConnection::getVersion(parameters_)); +} + +void +PgSqlLeaseMgr::commit() { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT); +} + +void +PgSqlLeaseMgr::rollback() { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK); +} + +void +PgSqlLeaseMgr::deleteExtendedInfo6(const IOAddress& /* addr */) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::deleteExtendedInfo6 not implemented"); +} + +void +PgSqlLeaseMgr::addRelayId6(const IOAddress& /* lease_addr */, + const vector<uint8_t>& /* relay_id */) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::addRelayId6 not implemented"); +} + +void +PgSqlLeaseMgr::addRemoteId6(const IOAddress& /* lease_addr */, + const vector<uint8_t>& /* remote_id */) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::addRemoteId6 not implemented"); +} + +namespace { + +std::string +idToText(const OptionBuffer& id) { + std::stringstream tmp; + tmp << std::hex; + bool delim = false; + for (std::vector<uint8_t>::const_iterator it = id.begin(); + it != id.end(); ++it) { + if (delim) { + tmp << ":"; + } + tmp << std::setw(2) << std::setfill('0') + << static_cast<unsigned int>(*it); + delim = true; + } + return (tmp.str()); +} + +} // anonymous namespace + +Lease4Collection +PgSqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id, + const IOAddress& lower_bound_address, + const LeasePageSize& page_size, + const time_t& qry_start_time /* = 0 */, + const time_t& qry_end_time /* = 0 */) { + // Expecting IPv4 address. + if (!lower_bound_address.isV4()) { + isc_throw(InvalidAddressFamily, "expected IPv4 address while " + "retrieving leases from the lease database, got " + << lower_bound_address); + } + + // Catch 2038 bug with 32 bit time_t. + if ((qry_start_time < 0) || (qry_end_time < 0)) { + isc_throw(BadValue, "negative time value"); + } + + bool have_qst = (qry_start_time > 0); + bool have_qet = (qry_end_time > 0); + + // Start time must be before end time. + if (have_qst && have_qet && (qry_start_time > qry_end_time)) { + isc_throw(BadValue, "start time must be before end time"); + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_GET_RELAYID4) + .arg(page_size.page_size_) + .arg(lower_bound_address.toText()) + .arg(idToText(relay_id)) + .arg(qry_start_time) + .arg(qry_end_time); + + // Prepare WHERE clause + PsqlBindArray bind_array; + + // Bind relay id + if (!relay_id.empty()) { + bind_array.add(relay_id); + } else { + bind_array.add(""); + } + + // Bind lower bound address + std::string lb_address_data = + boost::lexical_cast<std::string>(lower_bound_address.toUint32()); + bind_array.add(lb_address_data); + + // Bind query start time. + std::string start_time_str; + if (have_qst) { + start_time_str = boost::lexical_cast<std::string>(qry_start_time); + bind_array.add(start_time_str); + } + + // Bind query end time. + std::string end_time_str; + if (have_qet) { + end_time_str = boost::lexical_cast<std::string>(qry_end_time); + bind_array.add(end_time_str); + } + + // Bind page size value + std::string page_size_data = + boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + StatementIndex stindex = GET_LEASE4_RELAYID; + if (have_qst && !have_qet) { + stindex = GET_LEASE4_RELAYID_QST; + } else if (have_qst && have_qet) { + stindex = GET_LEASE4_RELAYID_QSET; + } else if (!have_qst && have_qet) { + stindex = GET_LEASE4_RELAYID_QET; + } + + // Get the leases + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, stindex, bind_array, result); + + return (result); +} + +Lease4Collection +PgSqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id, + const IOAddress& lower_bound_address, + const LeasePageSize& page_size, + const time_t& qry_start_time /* = 0 */, + const time_t& qry_end_time /* = 0 */) { + // Expecting IPv4 address. + if (!lower_bound_address.isV4()) { + isc_throw(InvalidAddressFamily, "expected IPv4 address while " + "retrieving leases from the lease database, got " + << lower_bound_address); + } + + // Catch 2038 bug with 32 bit time_t. + if ((qry_start_time < 0) || (qry_end_time < 0)) { + isc_throw(BadValue, "negative time value"); + } + + bool have_qst = (qry_start_time > 0); + bool have_qet = (qry_end_time > 0); + + // Start time must be before end time. + if (have_qst && have_qet && (qry_start_time > qry_end_time)) { + isc_throw(BadValue, "start time must be before end time"); + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_GET_REMOTEID4) + .arg(page_size.page_size_) + .arg(lower_bound_address.toText()) + .arg(idToText(remote_id)) + .arg(qry_start_time) + .arg(qry_end_time); + + // Prepare WHERE clause + PsqlBindArray bind_array; + + // Bind remote id + if (!remote_id.empty()) { + bind_array.add(remote_id); + } else { + bind_array.add(""); + } + + // Bind lower bound address + std::string lb_address_data = + boost::lexical_cast<std::string>(lower_bound_address.toUint32()); + bind_array.add(lb_address_data); + + // Bind query start time. + std::string start_time_str; + if (have_qst) { + start_time_str = boost::lexical_cast<std::string>(qry_start_time); + bind_array.add(start_time_str); + } + + // Bind query end time. + std::string end_time_str; + if (have_qet) { + end_time_str = boost::lexical_cast<std::string>(qry_end_time); + bind_array.add(end_time_str); + } + + // Bind page size value + std::string page_size_data = + boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + StatementIndex stindex = GET_LEASE4_REMOTEID; + if (have_qst && !have_qet) { + stindex = GET_LEASE4_REMOTEID_QST; + } else if (have_qst && have_qet) { + stindex = GET_LEASE4_REMOTEID_QSET; + } else if (!have_qst && have_qet) { + stindex = GET_LEASE4_REMOTEID_QET; + } + + // Get the leases + Lease4Collection result; + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, stindex, bind_array, result); + + return (result); +} + +size_t +PgSqlLeaseMgr::upgradeExtendedInfo4(const LeasePageSize& page_size) { + auto check = CfgMgr::instance().getCurrentCfg()-> + getConsistency()->getExtendedInfoSanityCheck(); + + size_t pages = 0; + size_t updated = 0; + IOAddress start_addr = IOAddress::IPV4_ZERO_ADDRESS(); + for (;;) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE) + .arg(pages) + .arg(start_addr.toText()) + .arg(updated); + + // Prepare WHERE clause. + PsqlBindArray bind_array; + + // Bind start address. + uint32_t start_addr_data = start_addr.toUint32(); + bind_array.add(start_addr_data); + + // Bind page size value. + std::string page_size_data = + boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + Lease4Collection leases; + + // Get a context. + { + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + getLeaseCollection(ctx, GET_LEASE4_UCTX_PAGE, bind_array, leases); + } + + if (leases.empty()) { + // Done. + break; + } + + ++pages; + start_addr = leases.back()->addr_; + for (auto lease : leases) { + ConstElementPtr previous_user_context = lease->getContext(); + vector<uint8_t> previous_relay_id = lease->relay_id_; + vector<uint8_t> previous_remote_id = lease->remote_id_; + if (!previous_user_context && + previous_relay_id.empty() && + previous_remote_id.empty()) { + continue; + } + bool modified = upgradeLease4ExtendedInfo(lease, check); + try { + lease->relay_id_.clear(); + lease->remote_id_.clear(); + extractLease4ExtendedInfo(lease, false); + if (modified || + (previous_relay_id != lease->relay_id_) || + (previous_remote_id != lease->remote_id_)) { + updateLease4(lease); + ++updated; + } + } catch (const NoSuchLease&) { + // The lease was modified in parallel: + // as its extended info was processed just ignore. + continue; + } catch (const std::exception& ex) { + // Something when wrong, for instance extract failed. + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR) + .arg(lease->addr_.toText()) + .arg(ex.what()); + } + } + } + + LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4) + .arg(pages) + .arg(updated); + + return (updated); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6ByRelayId(const DUID& /* relay_id */, + const IOAddress& /* link_addr */, + uint8_t /* link_len */, + const IOAddress& /* lower_bound_address */, + const LeasePageSize& /* page_size */) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases6ByRelayId not implemented"); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6ByRemoteId(const OptionBuffer& /* remote_id */, + const IOAddress& /* link_addr */, + uint8_t /* link_len */, + const IOAddress& /* lower_bound_address */, + const LeasePageSize& /* page_size*/) { + isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases6ByRemoteId not implemented"); +} + +Lease6Collection +PgSqlLeaseMgr::getLeases6ByLink(const IOAddress& link_addr, + uint8_t link_len, + const IOAddress& lower_bound_address, + const LeasePageSize& page_size) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_PGSQL_GET_LINKADDR6) + .arg(page_size.page_size_) + .arg(lower_bound_address.toText()) + .arg(link_addr.toText()) + .arg(static_cast<unsigned>(link_len)); + + // Expecting IPv6 valid prefix and address. + if (!link_addr.isV6()) { + isc_throw(InvalidAddressFamily, "expected IPv6 address while " + "retrieving leases from the lease database, got " + << link_addr); + } + + if ((link_len == 0) || (link_len > 128)) { + isc_throw(OutOfRange, "invalid IPv6 prefix length " + << static_cast<unsigned>(link_len)); + } + + if (!lower_bound_address.isV6()) { + isc_throw(InvalidAddressFamily, "expected IPv6 address while " + "retrieving leases from the lease database, got " + << lower_bound_address); + } + + Lease6Collection result; + const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len); + const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len); + IOAddress start_addr = lower_bound_address; + if (lower_bound_address < first_addr) { + start_addr = first_addr; + } else if (last_addr <= lower_bound_address) { + // Range was already done. + return (result); + } else { + // The lower bound address is from the last call so skip it. + start_addr = IOAddress::increase(lower_bound_address); + } + + // Prepare WHERE clause + PsqlBindArray bind_array; + + // Bind start address + std::string start_addr_str = start_addr.toText(); + bind_array.add(start_addr_str); + + // Bind last address + std::string last_addr_str = last_addr.toText(); + bind_array.add(last_addr_str); + + // Bind page size value + std::string page_size_data = + boost::lexical_cast<std::string>(page_size.page_size_); + bind_array.add(page_size_data); + + // Get a context + PgSqlLeaseContextAlloc get_context(*this); + PgSqlLeaseContextPtr ctx = get_context.ctx_; + + // Get the leases + getLeaseCollection(ctx, GET_LEASE6_LINK, bind_array, result); + + return (result); +} + +size_t +PgSqlLeaseMgr::buildExtendedInfoTables6(bool /* update */, bool /* current */) { + isc_throw(isc::NotImplemented, + "PgSqlLeaseMgr::buildExtendedInfoTables6 not implemented"); +} + +} // namespace dhcp +} // namespace isc |