diff options
Diffstat (limited to 'src/lib')
65 files changed, 3151 insertions, 862 deletions
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc index 8a2bf7590e..abe71fda1f 100644 --- a/src/lib/dhcp/pkt4.cc +++ b/src/lib/dhcp/pkt4.cc @@ -444,6 +444,9 @@ Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen, isc_throw(OutOfRange, "Invalid HW Address specified"); } + /// @todo: what if mac_addr.size() doesn't match hlen? + /// We would happily copy over hardware address that is possibly + /// too long or doesn't match hlen value. hw_addr.reset(new HWAddr(mac_addr, htype)); } diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc index 6c6fd408d3..f1c7a8f658 100644 --- a/src/lib/dhcpsrv/cfg_subnets4.cc +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -8,6 +8,7 @@ #include <dhcp/iface_mgr.h> #include <dhcpsrv/cfg_subnets4.h> #include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/subnet_id.h> #include <dhcpsrv/addr_utilities.h> #include <asiolink/io_address.h> @@ -232,18 +233,21 @@ CfgSubnets4::removeStatistics() { using namespace isc::stats; // For each v4 subnet currently configured, remove the statistic. - /// @todo: May move this to CfgSubnets4 class if there will be more - /// statistics here. + StatsMgr& stats_mgr = StatsMgr::instance(); for (Subnet4Collection::const_iterator subnet4 = subnets_.begin(); subnet4 != subnets_.end(); ++subnet4) { + SubnetID subnet_id = (*subnet4)->getID(); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "total-addresses")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet4)->getID(), - "total-addresses")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "assigned-addresses")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet4)->getID(), - "assigned-addresses")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "declined-reclaimed-addresses")); } } @@ -251,14 +255,21 @@ void CfgSubnets4::updateStatistics() { using namespace isc::stats; - /// @todo: May move this to CfgSubnets4 class if there will be more - /// statistics here. - for (Subnet4Collection::const_iterator subnet = subnets_.begin(); - subnet != subnets_.end(); ++subnet) { + StatsMgr& stats_mgr = StatsMgr::instance(); + for (Subnet4Collection::const_iterator subnet4 = subnets_.begin(); + subnet4 != subnets_.end(); ++subnet4) { + SubnetID subnet_id = (*subnet4)->getID(); + + stats_mgr.setValue(StatsMgr:: + generateName("subnet", subnet_id, "total-addresses"), + static_cast<int64_t> + ((*subnet4)->getPoolCapacity(Lease:: + TYPE_V4))); + } - StatsMgr::instance().setValue( - StatsMgr::generateName("subnet", (*subnet)->getID(), "total-addresses"), - static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_V4))); + // Only recount the stats if we have subnets. + if (subnets_.begin() != subnets_.end()) { + LeaseMgrFactory::instance().recountLeaseStats4(); } } diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc index ad8ed4c6c6..3136763f3c 100644 --- a/src/lib/dhcpsrv/cfg_subnets6.cc +++ b/src/lib/dhcpsrv/cfg_subnets6.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,6 +7,7 @@ #include <config.h> #include <dhcpsrv/cfg_subnets6.h> #include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/subnet_id.h> #include <stats/stats_mgr.h> @@ -176,25 +177,26 @@ void CfgSubnets6::removeStatistics() { using namespace isc::stats; + StatsMgr& stats_mgr = StatsMgr::instance(); // For each v6 subnet currently configured, remove the statistics. for (Subnet6Collection::const_iterator subnet6 = subnets_.begin(); subnet6 != subnets_.end(); ++subnet6) { + SubnetID subnet_id = (*subnet6)->getID(); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, "total-nas")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet6)->getID(), - "total-nas")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "assigned-nas")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet6)->getID(), - "assigned-nas")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, "total-pds")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet6)->getID(), - "total-pds")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "assigned-pds")); - StatsMgr::instance().del(StatsMgr::generateName("subnet", - (*subnet6)->getID(), - "assigned-pds")); + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses")); + + stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, + "declined-reclaimed-addresses")); } } @@ -202,16 +204,26 @@ void CfgSubnets6::updateStatistics() { using namespace isc::stats; - for (Subnet6Collection::const_iterator subnet = subnets_.begin(); - subnet != subnets_.end(); ++subnet) { + StatsMgr& stats_mgr = StatsMgr::instance(); + // For each v6 subnet currently configured, calculate totals + for (Subnet6Collection::const_iterator subnet6 = subnets_.begin(); + subnet6 != subnets_.end(); ++subnet6) { + SubnetID subnet_id = (*subnet6)->getID(); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "total-nas"), + static_cast<int64_t> + ((*subnet6)->getPoolCapacity(Lease::TYPE_NA))); - StatsMgr::instance().setValue( - StatsMgr::generateName("subnet", (*subnet)->getID(), "total-nas"), - static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_NA))); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "total-pds"), + static_cast<int64_t> + ((*subnet6)->getPoolCapacity(Lease::TYPE_PD))); + } - StatsMgr::instance().setValue( - StatsMgr::generateName("subnet", (*subnet)->getID(), "total-pds"), - static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_PD))); + // Only recount the stats if we have subnets. + if (subnets_.begin() != subnets_.end()) { + LeaseMgrFactory::instance().recountLeaseStats6(); } } diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox index 104318ece2..682bc5dea0 100644 --- a/src/lib/dhcpsrv/database_backends.dox +++ b/src/lib/dhcpsrv/database_backends.dox @@ -83,137 +83,5 @@ - <b>user</b> - database user ID under which the database is accessed. If not specified, no user ID is used - the database is assumed to be open. - @section dhcp-backend-unittest Running Unit Tests - - With the use of databases requiring separate authorisation, there are - certain database-specific pre-requisites for successfully running the unit - tests. These are listed in the following sections. - - @subsection dhcp-mysql-unittest MySQL Unit Tests - - A database called <i>keatest</i> must be created. A database user, also called - <i>keatest</i> (and with a password <i>keatest</i>) must also be created and - be given full privileges in that database. The unit tests create the schema - in the database before each test and delete it afterwards. - - In detail, the steps to create the database and user are: - - -# Log into MySQL as root: - @verbatim - % mysql -u root -p - Enter password: - : - mysql>@endverbatim\n - -# Create the test database. This must be called "keatest": - @verbatim - mysql> CREATE DATABASE keatest; - mysql>@endverbatim\n - -# Create the user under which the test client will connect to the database - (the apostrophes around the words <i>keatest</i> and <i>localhost</i> are - required): - @verbatim - mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest'; - mysql>@endverbatim\n - -# Grant the created user permissions to access the <i>keatest</i> database - (again, the apostrophes around the words <i>keatest</i> and <i>localhost</i> - are required): - @verbatim - mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost'; - mysql>@endverbatim\n - -# Exit MySQL: - @verbatim - mysql> quit - Bye - %@endverbatim - - The unit tests are run automatically when "make check" is executed (providing - that Kea has been build with the \--with-dhcp-mysql switch (see the installation - section in the <a href="http://kea.isc.org/docs/kea-guide.html">Kea Administrator - Reference Manual</a>). - - @subsection dhcp-pgsql-unittest PostgreSQL Unit Tests - - Conceptually, the steps required to run PostgreSQL unit-tests are the same as - in MySQL. First, a database called <i>keatest</i> must be created. A database - user, also called <i>keatest</i> (that will be allowed to log in using password - <i>keatest</i>) must be created and given full privileges in that database. The - unit tests create the schema in the database before each test and delete it - afterwards. - - PostgreSQL set up differs from system to system. Please consult your OS-specific - PostgreSQL documentation. The remainder of that section uses Ubuntu 13.10 x64 as - example. On Ubuntu, after installing PostgreSQL (with <tt>sudo apt-get install - postgresql</tt>), it is installed as user <i>postgres</i>. To create new databases - or add new users, initial commands must be issued as user postgres: - -@verbatim -$ sudo -u postgres psql postgres -[sudo] password for thomson: -psql (9.1.12) -Type "help" for help. -postgres=# CREATE USER keatest WITH PASSWORD 'keatest'; -CREATE ROLE -postgres=# CREATE DATABASE keatest; -CREATE DATABASE -postgres=# GRANT ALL PRIVILEGES ON DATABASE keatest TO keatest; -GRANT -postgres=# \q -@endverbatim - - Now we are back to our regular, unprivileged user. Try to log into the newly - created database using keatest credentials: -@verbatim -$ psql -d keatest -U keatest -Password for user keatest: -psql (9.1.12) -Type "help" for help. - -keatest=> -@endverbatim - - If instead of seeing keatest=> prompt, your login will be refused with error - code about failed peer or indent authentication, it means that PostgreSQL is - configured to check unix username and reject login attepts if PostgreSQL names - are different. To alter that, PostgreSQL configuration must be changed. - Alternatively, you may set up your environment, so the tests would be run from - unix account keatest. <tt>/etc/postgresql/9.1/main/pg_hba.conf</tt> config file - had to betweaked. It may be in a different location in your system. The following - lines: - -@verbatim -local all all peer -host all all 127.0.0.1/32 md5 -host all all ::1/128 md5 -@endverbatim - - were replaced with: - -@verbatim -local all all password -host all all 127.0.0.1/32 password -host all all ::1/128 password -@endverbatim - - Another possible problem is to get no password prompt, in general because - you have no <tt>pg_hba.conf</tt> config file and everybody is by default - trusted. As it has a very bad effect on the security you should have - been warned it is a highly unsafe config. The solution is the same, - i.e., require password or md5 authentication method. If you lose - the postgres user access you can add first: -@verbatim -local all postgres trust -@endverbatim - to trust only the local postgres user. Note the postgres user can - be pgsql on some systems. - - Please consult your PostgreSQL user manual before applying those changes as - those changes may expose your other databases that you run on the same system. - In general case, it is a poor idea to run anything of value on a system - that runs tests. Use caution! - - The unit tests are run automatically when "make check" is executed (providing - that Kea has been build with the \--with-dhcp-pgsql switch (see the installation - section in the <a href="http://kea.isc.org/docs/kea-guide.html">Kea Administrator - Reference Manual</a>). */ diff --git a/src/lib/dhcpsrv/database_connection.cc b/src/lib/dhcpsrv/database_connection.cc index 983a752d96..0aa886fa88 100644 --- a/src/lib/dhcpsrv/database_connection.cc +++ b/src/lib/dhcpsrv/database_connection.cc @@ -1,10 +1,11 @@ -// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <dhcpsrv/database_connection.h> +#include <dhcpsrv/db_exceptions.h> #include <dhcpsrv/dhcpsrv_log.h> #include <exceptions/exceptions.h> @@ -86,5 +87,24 @@ DatabaseConnection::redactedAccessString(const ParameterMap& parameters) { return (access); } +bool +DatabaseConnection::configuredReadOnly() const { + std::string readonly_value = "false"; + try { + readonly_value = getParameter("readonly"); + boost::algorithm::to_lower(readonly_value); + } catch (...) { + // Parameter "readonly" hasn't been specified so we simply use + // the default value of "false". + } + + if ((readonly_value != "false") && (readonly_value != "true")) { + isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value + << "' specified for boolean parameter 'readonly'"); + } + + return (readonly_value == "true"); +} + }; }; diff --git a/src/lib/dhcpsrv/database_connection.h b/src/lib/dhcpsrv/database_connection.h index 2be98baf41..e6a902babf 100644 --- a/src/lib/dhcpsrv/database_connection.h +++ b/src/lib/dhcpsrv/database_connection.h @@ -54,6 +54,15 @@ public: isc::Exception(file, line, what) {} }; +/// @brief Invalid 'readonly' value specification. +/// +/// Thrown when the value of the 'readonly' boolean parameter is invalid. +class DbInvalidReadOnly : public Exception { +public: + DbInvalidReadOnly(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + /// @brief Common database connection class. /// @@ -112,6 +121,14 @@ public: /// @return Redacted database access string. static std::string redactedAccessString(const ParameterMap& parameters); + /// @brief Convenience method checking if database should be opened with + /// read only access. + /// + /// @return true if "readonly" parameter is specified and set to true; + /// false if "readonly" parameter is not specified or it is specified + /// and set to false. + bool configuredReadOnly() const; + private: /// @brief List of parameters passed in dbconfig diff --git a/src/lib/dhcpsrv/db_exceptions.h b/src/lib/dhcpsrv/db_exceptions.h index 664c43e144..d9cc4449dd 100644 --- a/src/lib/dhcpsrv/db_exceptions.h +++ b/src/lib/dhcpsrv/db_exceptions.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -40,6 +40,13 @@ public: isc::Exception(file, line, what) {} }; +/// @brief Attempt to modify data in read-only database. +class ReadOnlyDb : public Exception { +public: + ReadOnlyDb(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + }; }; diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index 714148548c..e9ff369859 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -551,6 +551,12 @@ connection including database name and username needed to access it A debug message issued when the server is about to obtain schema version information from the MySQL hosts database. +% DHCPSRV_MYSQL_HOST_DB_READONLY MySQL host database opened for read access only +This informational message is issued when the user has configured the MySQL +database in read-only mode. Kea will not be able to insert or modify +host reservations but will be able to retrieve existing ones and +assign them to the clients communicating with the server. + % DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database The code has issued a rollback call. All outstanding transaction will be rolled back and not committed to the database. @@ -696,6 +702,12 @@ V6) is about to open a PostgreSQL hosts database. The parameters of the connection including database name and username needed to access it (but not the password if any) are logged. +% DHCPSRV_PGSQL_HOST_DB_READONLY PostgreSQL host database opened for read access only +This informational message is issued when the user has configured the PostgreSQL +database in read-only mode. Kea will not be able to insert or modify +host reservations but will be able to retrieve existing ones and +assign them to the clients communicating with the server. + % DHCPSRV_PGSQL_ROLLBACK rolling back PostgreSQL database The code has issued a rollback call. All outstanding transaction will be rolled back and not committed to the database. diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index 16709c5be8..090a5433ac 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -47,7 +47,7 @@ HostMgr::create(const std::string& access) { HostDataSourceFactory::create(access); } else { // Ok, no parameters were specified. We should destroy the existing - // insteance. + // instance. HostDataSourceFactory::destroy(); } @@ -214,8 +214,12 @@ HostMgr::get6(const SubnetID& subnet_id, } void -HostMgr::add(const HostPtr&) { - isc_throw(isc::NotImplemented, "HostMgr::add is not implemented"); +HostMgr::add(const HostPtr& host) { + if (!alternate_source_) { + isc_throw(NoHostDataSourceManager, "unable to add new host because there is " + "no alternate host data source present"); + } + alternate_source_->add(host); } } // end of isc::dhcp namespace diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index f4633281b9..f7483fe074 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -53,7 +53,7 @@ namespace dhcp { /// reservations specified in the configuration file) can't be disabled. /// /// @todo Implement alternate host data sources: MySQL, PostgreSQL, etc. -class HostMgr : public boost::noncopyable, BaseHostDataSource { +class HostMgr : public boost::noncopyable, public BaseHostDataSource { public: /// @brief Creates new instance of the @c HostMgr. diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h index d1ab300f11..c89ad523a3 100644 --- a/src/lib/dhcpsrv/lease.h +++ b/src/lib/dhcpsrv/lease.h @@ -52,8 +52,6 @@ struct Lease { /// @brief Expired and reclaimed lease. static const uint32_t STATE_EXPIRED_RECLAIMED; - //@} - /// @brief Returns name(s) of the basic lease state(s). /// /// @param state A numeric value holding a state information. diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc index 58f95d7dc9..cd84a69839 100644 --- a/src/lib/dhcpsrv/lease_mgr.cc +++ b/src/lib/dhcpsrv/lease_mgr.cc @@ -6,8 +6,11 @@ #include <config.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> #include <dhcpsrv/lease_mgr.h> #include <exceptions/exceptions.h> +#include <stats/stats_mgr.h> #include <boost/foreach.hpp> #include <boost/algorithm/string.hpp> @@ -44,6 +47,166 @@ LeaseMgr::getLease6(Lease::Type type, const DUID& duid, return (*col.begin()); } +void +LeaseMgr::recountLeaseStats4() { + using namespace stats; + + StatsMgr& stats_mgr = StatsMgr::instance(); + + LeaseStatsQueryPtr query = startLeaseStatsQuery4(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Zero out the global stats. + int64_t zero = 0; + stats_mgr.setValue("declined-addresses", zero); + stats_mgr.setValue("declined-reclaimed-addresses", zero); + + // Clear subnet level stats. This ensures we don't end up with corner + // cases that leave stale values in place. + const Subnet4Collection* subnets = + CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll(); + + for (Subnet4Collection::const_iterator subnet = subnets->begin(); + subnet != subnets->end(); ++subnet) { + SubnetID subnet_id = (*subnet)->getID(); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-addresses"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses"), + zero); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "declined-reclaimed-addresses"), + zero); + } + + // Get counts per state per subnet. Iterate over the result set + // updating the subnet and global values. + LeaseStatsRow row; + while (query->getNextRow(row)) { + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-addresses"), + row.state_count_); + } else if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + "declined-addresses"), + row.state_count_); + + // Add to the global value. + stats_mgr.addValue("declined-addresses", row.state_count_); + } + } +} + +LeaseStatsQueryPtr +LeaseMgr::startLeaseStatsQuery4() { + return(LeaseStatsQueryPtr()); +} + +bool +LeaseStatsQuery::getNextRow(LeaseStatsRow& /*row*/) { + return (false); +} + +void +LeaseMgr::recountLeaseStats6() { + using namespace stats; + + StatsMgr& stats_mgr = StatsMgr::instance(); + + LeaseStatsQueryPtr query = startLeaseStatsQuery6(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Zero out the global stats. (Ok, so currently there's only one + // that should be cleared. "reclaimed-declined-addresses" never + // gets zeroed. @todo discuss with Tomek the rational of not + // clearing it when we clear the rest. + int64_t zero = 0; + stats_mgr.setValue("declined-addresses", zero); + stats_mgr.setValue("declined-reclaimed-addresses", zero); + + // Clear subnet level stats. This ensures we don't end up with corner + // cases that leave stale values in place. + const Subnet6Collection* subnets = + CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll(); + + for (Subnet6Collection::const_iterator subnet = subnets->begin(); + subnet != subnets->end(); ++subnet) { + SubnetID subnet_id = (*subnet)->getID(); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-nas"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses"), + zero); + + stats_mgr.setValue(StatsMgr:: + generateName("subnet", subnet_id, + "declined-reclaimed-addresses"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-pds"), + zero); + } + + // Get counts per state per subnet. Iterate over the result set + // updating the subnet and global values. + LeaseStatsRow row; + while (query->getNextRow(row)) { + switch(row.lease_type_) { + case Lease::TYPE_NA: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr:: + generateName("subnet", row.subnet_id_, + "assigned-nas"), + row.state_count_); + } if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr:: + generateName("subnet", row.subnet_id_, + "declined-addresses"), + row.state_count_); + + // Add to the global value. + stats_mgr.addValue("declined-addresses", row.state_count_); + } + break; + + case Lease::TYPE_PD: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr:: + generateName("subnet", row.subnet_id_, + "assigned-pds"), + row.state_count_); + } + break; + + default: + // We dont' support TYPE_TAs yet + break; + } + } +} + +LeaseStatsQueryPtr +LeaseMgr::startLeaseStatsQuery6() { + return(LeaseStatsQueryPtr()); +} + std::string LeaseMgr::getDBVersion() { isc_throw(NotImplemented, "LeaseMgr::getDBVersion() called"); diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index c31f2dfa12..c8b4ef4e0d 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -146,6 +146,86 @@ public: virtual ~SqlExchange() {}; ExchangeColumnInfoContainer parameters_; ///< Column names and types }; + +/// @brief Contains a single row of lease statistical data +/// +/// The contents of the row consist of a subnet ID, a lease +/// type, a lease state, and the number of leases in that state +/// for that type for that subnet ID. +struct LeaseStatsRow { + /// @brief Default constructor + LeaseStatsRow() : + subnet_id_(0), lease_type_(Lease::TYPE_NA), + lease_state_(Lease::STATE_DEFAULT), state_count_(0) { + } + + /// @brief Constructor + /// + /// Constructor which defaults the type to TYPE_NA. + /// + /// @param subnet_id The subnet id to which this data applies + /// @param lease_state The lease state counted + /// @param state_count The count of leases in the lease state + LeaseStatsRow(const SubnetID& subnet_id, const uint32_t lease_state, + const int64_t state_count) + : subnet_id_(subnet_id), lease_type_(Lease::TYPE_NA), + lease_state_(lease_state), state_count_(state_count) { + } + + /// @brief Constructor + /// + /// @param subnet_id The subnet id to which this data applies + /// @param lease_type The lease type for this state count + /// @param lease_state The lease state counted + /// @param state_count The count of leases in the lease state + LeaseStatsRow(const SubnetID& subnet_id, const Lease::Type& lease_type, + const uint32_t lease_state, const int64_t state_count) + : subnet_id_(subnet_id), lease_type_(lease_type), + lease_state_(lease_state), state_count_(state_count) { + } + + /// @brief The subnet ID to which this data applies + SubnetID subnet_id_; + /// @brief The lease_type to which the count applies + Lease::Type lease_type_; + /// @brief The lease_state to which the count applies + uint32_t lease_state_; + /// @brief state_count The count of leases in the lease state + int64_t state_count_; +}; + +/// @brief Base class for fulfilling a statistical lease data query +/// +/// LeaseMgr derivations implement this class such that it provides +/// upto date statistical lease data organized as rows of LeaseStatsRow +/// instances. The rows must be accessible in ascending order by subnet id. +class LeaseStatsQuery { +public: + /// @brief Default constructor + LeaseStatsQuery() {}; + + /// @brief virtual destructor + virtual ~LeaseStatsQuery() {}; + + /// @brief Executes the query + /// + /// This method should conduct whatever steps are required to + /// calculate the lease statistical data by examining the + /// lease data and making that results available row by row. + virtual void start() {}; + + /// @brief Fetches the next row of data + /// + /// @param[out] row Storage into which the row is fetched + /// + /// @return True if a row was fetched, false if there are no + /// more rows. + virtual bool getNextRow(LeaseStatsRow& row); +}; + +/// @brief Defines a pointer to an LeaseStatsQuery. +typedef boost::shared_ptr<LeaseStatsQuery> LeaseStatsQueryPtr; + /// @brief Abstract Lease Manager /// /// This is an abstract API for lease database backends. It provides unified @@ -397,6 +477,68 @@ public: /// @return Number of leases deleted. virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) = 0; + /// @brief Recalculates per-subnet and global stats for IPv4 leases + /// + /// This method recalculates the following statistics: + /// per-subnet: + /// - assigned-addresses + /// - declined-addresses + /// - declined-reclaimed-addresses (reset to zero) + /// global: + /// - declined-addresses + /// - declined-reclaimed-addresses (reset to zero) + /// + /// It invokes the virtual method, startLeaseStatsQuery4(), which + /// returns an instance of an LeaseStatsQuery. The query + /// query contains a "result set" where each row is an LeaseStatRow + /// that contains a subnet id, a lease type (currently always TYPE_NA), + /// a lease state, and the number of leases of that type, in that state + /// and is ordered by subnet id. The method iterates over the + /// result set rows, setting the appropriate statistic per subnet and + /// adding to the approporate global statistic. + void recountLeaseStats4(); + + /// @brief Virtual method which creates and runs the IPv4 lease stats query + /// + /// LeaseMgr derivations implement this method such that it creates and + /// returns an instance of an LeaseStatsQuery whose result set has been + /// populated with upto date IPv4 lease statistical data. Each row of the + /// result set is an LeaseStatRow which ordered ascending by subnet ID. + /// + /// @return A populated LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery4(); + + /// @brief Recalculates per-subnet and global stats for IPv6 leases + /// + /// This method recalculates the following statistics: + /// per-subnet: + /// - assigned-addresses + /// - declined-addresses + /// - declined-reclaimed-addresses (reset to zero) + /// - assigned-pds + /// global: + /// - declined-addresses + /// - declined-reclaimed-addresses (reset to zero) + /// + /// It invokes the virtual method, startLeaseStatsQuery6(), which + /// returns an instance of an LeaseStatsQuery. The query contains + /// a "result set" where each row is an LeaseStatRow that contains + /// a subnet id, a lease type, a lease state, and the number of leases + /// of that type, in that state and is ordered by subnet id. The method + /// iterates over the result set rows, setting the appropriate statistic + /// per subnet and adding to the approporate global statistic. + void recountLeaseStats6(); + + /// @brief Virtual method which creates and runs the IPv6 lease stats query + /// + /// LeaseMgr derivations implement this method such that it creates and + /// returns an instance of an LeaseStatsQuery whose result set has been + /// populated with upto date IPv6 lease statistical data. Each row of the + /// result set is an LeaseStatRow which ordered ascending by subnet ID. + /// + /// @return A populated LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery6(); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc index 5f2f33a4f3..a9f1507b2b 100644 --- a/src/lib/dhcpsrv/lease_mgr_factory.cc +++ b/src/lib/dhcpsrv/lease_mgr_factory.cc @@ -101,6 +101,11 @@ LeaseMgrFactory::destroy() { getLeaseMgrPtr().reset(); } +bool +LeaseMgrFactory::haveInstance() { + return (getLeaseMgrPtr().get()); +} + LeaseMgr& LeaseMgrFactory::instance() { LeaseMgr* lmptr = getLeaseMgrPtr().get(); diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h index 279058bed8..4c6baf1270 100644 --- a/src/lib/dhcpsrv/lease_mgr_factory.h +++ b/src/lib/dhcpsrv/lease_mgr_factory.h @@ -85,7 +85,10 @@ public: /// create() to create one before calling this method. static LeaseMgr& instance(); - + /// @brief Indicates if the lease manager has been instantiated. + /// + /// @return True if the lease manager instance exists, false otherwise. + static bool haveInstance(); private: /// @brief Hold pointer to lease manager diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc index 51314b4cd8..4ff78be9d1 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.cc +++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc @@ -256,6 +256,268 @@ LFCSetup::getExitStatus() const { return (process_->getExitStatus(pid_)); } + +/// @brief Base Memfile 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 MemfileLeaseStatsQuery : public LeaseStatsQuery { +public: + /// @brief Constructor + /// + MemfileLeaseStatsQuery() + : rows_(0), next_pos_(rows_.end()) { + }; + + /// @brief Destructor + virtual ~MemfileLeaseStatsQuery() {}; + + /// @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. + /// @param row Storage for the fetched row + /// + /// @return True if the fetch succeeded, false if there are no more + /// rows to fetch. + virtual bool getNextRow(LeaseStatsRow& row) { + if (next_pos_ == rows_.end()) { + return (false); + } + + row = *next_pos_; + ++next_pos_; + return (true); + } + + /// @brief Returns the number of rows in the result set + int getRowCount() const { + return (rows_.size()); + } + +protected: + /// @brief A vector containing the "result set" + std::vector<LeaseStatsRow> rows_; + + /// @brief An iterator for accessing the next row within the result set + std::vector<LeaseStatsRow>::iterator next_pos_; +}; + +/// @brief Memfile derivation of the IPv4 statistical lease data query +/// +/// This class is used to recalculate IPv4 lease statistics for Memfile +/// lease storage. It does so by iterating over the given storage, +/// accumulating counts of leases in each of the monitored lease states +/// for each subnet and storing these counts in an internal collection. +/// The populated result set will contain one entry per monitored state +/// per subnet. +/// +class MemfileLeaseStatsQuery4 : public MemfileLeaseStatsQuery { +public: + /// @brief Constructor + /// + /// @param storage4 A pointer to the v4 lease storage to be counted + MemfileLeaseStatsQuery4(Lease4Storage& storage4) + : MemfileLeaseStatsQuery(), storage4_(storage4) { + }; + + /// @brief Destructor + virtual ~MemfileLeaseStatsQuery4() {}; + + /// @brief Creates the IPv4 lease statistical data result set + /// + /// The result set is populated by iterating over the IPv4 leases in + /// storage, in ascending order by address, accumulating the lease state + /// counts per subnet. Note that walking the leases by address should + /// inherently group them by subnet, and while this does not gaurantee + /// ascending order of subnet id, it should be sufficient to accumulate + /// state counts per subnet. This avoids introducing an additional + /// subnet_id index. + /// At the completion of all entries for a given subnet, the counts are + /// used to create LeaseStatsRow instances which are appended to an + /// internal vector. The process results in a vector containing one entry + /// per state per subnet. + /// + /// Currently the states counted are: + /// + /// - Lease::STATE_DEFAULT (i.e. assigned) + /// - Lease::STATE_DECLINED + void start() { + const Lease4StorageAddressIndex& idx + = storage4_.get<AddressIndexTag>(); + + // Iterate over the leases in order by subnet, accumulating per + // subnet counts for each state of interest. As we finish each + // subnet, add the appropriate rows to our result set. + SubnetID cur_id = 0; + int64_t assigned = 0; + int64_t declined = 0; + for(Lease4StorageAddressIndex::const_iterator lease = idx.begin(); + lease != idx.end(); ++lease) { + // If we've hit the next subnet, add rows for the current subnet + // and wipe the accumulators + if ((*lease)->subnet_id_ != cur_id) { + if (cur_id > 0) { + rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT, + assigned)); + assigned = 0; + rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED, + declined)); + declined = 0; + } + + // Update current subnet id + cur_id = (*lease)->subnet_id_; + } + + // Bump the appropriate accumulator + if ((*lease)->state_ == Lease::STATE_DEFAULT) { + ++assigned; + } else if ((*lease)->state_ == Lease::STATE_DECLINED) { + ++declined; + } + } + + // Make the rows for last subnet, unless there were no rows + if (idx.begin() != idx.end()) { + rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT, + assigned)); + rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED, + declined)); + } + + // Set the next row position to the beginning of the rows. + next_pos_ = rows_.begin(); + } + +private: + /// @brief The Memfile storage containing the IPv4 leases to analyze + Lease4Storage& storage4_; +}; + + +/// @brief Memfile derivation of the IPv6 statistical lease data query +/// +/// This class is used to recalculate IPv6 lease statistics for Memfile +/// lease storage. It does so by iterating over the given storage, +/// accumulating counts of leases in each of the monitored lease states +/// for each subnet and storing these counts in an internal collection. +/// The populated result set will contain one entry per monitored state +/// per subnet. +/// +class MemfileLeaseStatsQuery6 : public MemfileLeaseStatsQuery { +public: + /// @brief Constructor + /// + /// @param storage6 A pointer to the v6 lease storage to be counted + MemfileLeaseStatsQuery6(Lease6Storage& storage6) + : MemfileLeaseStatsQuery(), storage6_(storage6) { + }; + + /// @brief Destructor + virtual ~MemfileLeaseStatsQuery6() {}; + + /// @brief Creates the IPv6 lease statistical data result set + /// + /// The result set is populated by iterating over the IPv6 leases in + /// storage, in ascending order by address, accumulating the lease state + /// counts per subnet. Note that walking the leases by address should + /// inherently group them by subnet, and while this does not gaurantee + /// ascending order of subnet id, it should be sufficient to accumulate + /// state counts per subnet. This avoids introducing an additional + /// subnet_id index. + /// At the completion of all entries for a given subnet, the counts + /// are used to create LeaseStatsRow instances which are appended to an + /// internal vector. The process results in a vector containing one entry + /// per state per lease type per subnet. + /// + /// Currently the states counted are: + /// + /// - Lease::STATE_DEFAULT (i.e. assigned) + /// - Lease::STATE_DECLINED + virtual void start() { + // Get the subnet_id index + const Lease6StorageAddressIndex& idx + = storage6_.get<AddressIndexTag>(); + + // Iterate over the leases in order by subnet, accumulating per + // subnet counts for each state of interest. As we finish each + // subnet, add the appropriate rows to our result set. + SubnetID cur_id = 0; + int64_t assigned = 0; + int64_t declined = 0; + int64_t assigned_pds = 0; + + for(Lease6StorageAddressIndex::const_iterator lease = idx.begin(); + lease != idx.end(); ++lease) { + + // If we've hit the next subnet, add rows for the current subnet + // and wipe the accumulators + if ((*lease)->subnet_id_ != cur_id) { + if (cur_id > 0) { + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA, + Lease::STATE_DEFAULT, + assigned)); + assigned = 0; + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA, + Lease::STATE_DECLINED, + declined)); + declined = 0; + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD, + Lease::STATE_DEFAULT, + assigned_pds)); + assigned_pds = 0; + } + + // Update current subnet id + cur_id = (*lease)->subnet_id_; + } + + // Bump the appropriate accumulator + if ((*lease)->state_ == Lease::STATE_DEFAULT) { + switch((*lease)->type_) { + case Lease::TYPE_NA: + ++assigned; + break; + case Lease::TYPE_PD: + ++assigned_pds; + break; + default: + break; + } + } else if ((*lease)->state_ == Lease::STATE_DECLINED) { + // In theory only NAs can be declined + if (((*lease)->type_) == Lease::TYPE_NA) { + ++declined; + } + } + } + + // Make the rows for last subnet, unless there were no rows + if (idx.begin() != idx.end()) { + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA, + Lease::STATE_DEFAULT, + assigned)); + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA, + Lease::STATE_DECLINED, + declined)); + rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD, + Lease::STATE_DEFAULT, + assigned_pds)); + } + + // Set the next row position to the beginning of the rows. + next_pos_ = rows_.begin(); + } + +private: + /// @brief The Memfile storage containing the IPv6 leases to analyze + Lease6Storage& storage6_; +}; + // Explicit definition of class static constants. Values are given in the // declaration so they're not needed here. const int Memfile_LeaseMgr::MAJOR_VERSION; @@ -299,6 +561,7 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& param } lfcSetup(conversion_needed); } + } Memfile_LeaseMgr::~Memfile_LeaseMgr() { @@ -1048,5 +1311,19 @@ void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) } } +LeaseStatsQueryPtr +Memfile_LeaseMgr::startLeaseStatsQuery4() { + LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +Memfile_LeaseMgr::startLeaseStatsQuery6() { + LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_)); + query->start(); + return(query); +} + } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h index 1ace364cf7..bdbd985511 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.h +++ b/src/lib/dhcpsrv/memfile_lease_mgr.h @@ -92,6 +92,7 @@ public: /// @} + /// @brief Specifies universe (V4, V6) /// /// This enumeration is used by various functions in Memfile %Lease Manager, @@ -594,6 +595,23 @@ public: int getLFCExitStatus() const; //@} + /// @brief Creates and runs the IPv4 lease stats query + /// + /// It creates an instance of a MemfileLeaseStatsQuery4 and then + /// invokes its start method in which the query constructs its + /// statistical data result set. The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery4(); + + /// @brief Creates and runs the IPv6 lease stats query + /// + /// It creates an instance of a MemfileLeaseStatsQuery6 and then + /// invokes its start method in which the query constructs its + /// statistical data result set. The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery. + virtual LeaseStatsQueryPtr startLeaseStatsQuery6(); /// @name Protected methods used for %Lease File Cleanup. /// The following methods are protected so as they can be accessed and diff --git a/src/lib/dhcpsrv/memfile_lease_storage.h b/src/lib/dhcpsrv/memfile_lease_storage.h index 566a74d7c5..3b07fad238 100644 --- a/src/lib/dhcpsrv/memfile_lease_storage.h +++ b/src/lib/dhcpsrv/memfile_lease_storage.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc index 30de103d53..06162bdf1e 100644 --- a/src/lib/dhcpsrv/mysql_connection.cc +++ b/src/lib/dhcpsrv/mysql_connection.cc @@ -12,7 +12,6 @@ #include <boost/lexical_cast.hpp> #include <algorithm> -#include <iterator> #include <stdint.h> #include <string> #include <limits> @@ -213,22 +212,26 @@ MySqlConnection::prepareStatement(uint32_t index, const char* text) { } void -MySqlConnection::prepareStatements(const TaggedStatement tagged_statements[], - size_t num_statements) { - // Allocate space for all statements - statements_.clear(); - statements_.resize(num_statements, NULL); - - text_statements_.clear(); - text_statements_.resize(num_statements, std::string("")); - +MySqlConnection::prepareStatements(const TaggedStatement* start_statement, + const TaggedStatement* end_statement) { // Created the MySQL prepared statements for each DML statement. - for (int i = 0; tagged_statements[i].text != NULL; ++i) { - prepareStatement(tagged_statements[i].index, - tagged_statements[i].text); + for (const TaggedStatement* tagged_statement = start_statement; + tagged_statement != end_statement; ++tagged_statement) { + if (tagged_statement->index >= statements_.size()) { + statements_.resize(tagged_statement->index + 1, NULL); + text_statements_.resize(tagged_statement->index + 1, + std::string("")); + } + prepareStatement(tagged_statement->index, + tagged_statement->text); } } +void MySqlConnection::clearStatements() { + statements_.clear(); + text_statements_.clear(); +} + /// @brief Destructor MySqlConnection::~MySqlConnection() { // Free up the prepared statements, ignoring errors. (What would we do diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h index 8acb894577..4f84731bcd 100644 --- a/src/lib/dhcpsrv/mysql_connection.h +++ b/src/lib/dhcpsrv/mysql_connection.h @@ -240,15 +240,21 @@ public: /// /// Creates the prepared statements for all of the SQL statements used /// by the MySQL backend. - /// @param tagged_statements an array of statements to be compiled - /// @param num_statements number of statements in tagged_statements + /// + /// @param start_statement Pointer to the first statement in range of the + /// statements to be compiled. + /// @param end_statement Pointer to the statement marking end of the + /// range of statements to be compiled. This last statement is not compiled. /// /// @throw isc::dhcp::DbOperationError An operation on the open database has /// failed. /// @throw isc::InvalidParameter 'index' is not valid for the vector. This /// represents an internal error within the code. - void prepareStatements(const TaggedStatement tagged_statements[], - size_t num_statements); + void prepareStatements(const TaggedStatement* start_statement, + const TaggedStatement* end_statement); + + /// @brief Clears prepared statements and text statements. + void clearStatements(); /// @brief Open Database /// diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 7421facd5a..3dde4c4c04 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -11,6 +11,7 @@ #include <dhcp/option_definition.h> #include <dhcp/option_space.h> #include <dhcpsrv/cfg_option.h> +#include <dhcpsrv/db_exceptions.h> #include <dhcpsrv/dhcpsrv_log.h> #include <dhcpsrv/mysql_host_data_source.h> #include <dhcpsrv/db_exceptions.h> @@ -19,6 +20,7 @@ #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/classification.hpp> +#include <boost/array.hpp> #include <boost/pointer_cast.hpp> #include <boost/static_assert.hpp> @@ -960,7 +962,7 @@ private: size_t start_column_; /// @brief Option id. - uint64_t option_id_; + uint32_t option_id_; /// @brief Option code. uint16_t code_; @@ -1030,7 +1032,7 @@ private: //@} /// @brief Option id for last processed row. - uint64_t most_recent_option_id_; + uint32_t most_recent_option_id_; }; /// @brief Pointer to the @ref OptionProcessor class. @@ -1227,7 +1229,7 @@ public: /// @brief Returns last fetched reservation id. /// /// @return Reservation id or 0 if no reservation data is fetched. - uint64_t getReservationId() const { + uint32_t getReservationId() const { if (reserv_type_null_ == MLM_FALSE) { return (reservation_id_); } @@ -1365,7 +1367,7 @@ public: private: /// @brief IPv6 reservation id. - uint64_t reservation_id_; + uint32_t reservation_id_; /// @brief IPv6 reservation type. uint8_t reserv_type_; @@ -1386,7 +1388,7 @@ private: uint8_t prefix_len_; /// @brief IAID. - uint8_t iaid_; + uint32_t iaid_; /// @name Indexes of columns holding information about IPv6 reservations. //@{ @@ -1408,7 +1410,7 @@ private: //@} /// @brief Reservation id for last processed row. - uint64_t most_recent_reservation_id_; + uint32_t most_recent_reservation_id_; }; @@ -1699,7 +1701,7 @@ private: std::vector<uint8_t> value_; /// @brief Option value length. - size_t value_len_; + unsigned long value_len_; /// @brief Formatted option value length. unsigned long formatted_value_len_; @@ -1708,7 +1710,7 @@ private: std::string space_; /// @brief Option space name length. - size_t space_len_; + unsigned long space_len_; /// @brief Boolean flag indicating if the option is always returned to /// a client or only when requested. @@ -1718,7 +1720,7 @@ private: std::string client_class_; /// @brief Length of the string holding client classes for the option. - size_t client_class_len_; + unsigned long client_class_len_; /// @brief Subnet identifier. uint32_t subnet_id_; @@ -1744,12 +1746,11 @@ public: /// @brief Statement Tags /// - /// The contents of the enum are indexes into the list of SQL statements + /// The contents of the enum are indexes into the list of SQL statements. + /// It is assumed that the order is such that the indicies of statements + /// reading the database are less than those of statements modifying the + /// database. enum StatementIndex { - INSERT_HOST, // Insert new host to collection - INSERT_V6_RESRV, // Insert v6 reservation - INSERT_V4_OPTION, // Insert DHCPv4 option - INSERT_V6_OPTION, // Insert DHCPv6 option GET_HOST_DHCPID, // Gets hosts by host identifier GET_HOST_ADDR, // Gets hosts by IPv4 address GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID @@ -1757,9 +1758,20 @@ public: GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address GET_HOST_PREFIX, // Gets host by IPv6 prefix GET_VERSION, // Obtain version number + INSERT_HOST, // Insert new host to collection + INSERT_V6_RESRV, // Insert v6 reservation + INSERT_V4_OPTION, // Insert DHCPv4 option + INSERT_V6_OPTION, // Insert DHCPv6 option NUM_STATEMENTS // Number of statements }; + /// @brief Index of first statement performing write to the database. + /// + /// This value is used to mark border line between queries and other + /// statements and statements performing write operation on the database, + /// such as INSERT, DELETE, UPDATE. + static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST; + /// @brief Constructor. /// /// This constructor opens database connection and initializes prepared @@ -1865,6 +1877,15 @@ public: StatementIndex stindex, boost::shared_ptr<MySqlHostExchange> exchange) const; + /// @brief Throws exception if database is read only. + /// + /// This method should be called by the methods which write to the + /// database. If the backend is operating in read-only mode this + /// method will throw exception. + /// + /// @throw DbReadOnly if backend is operating in read only mode. + void checkReadOnly() const; + /// @brief Pointer to the object representing an exchange which /// can be used to retrieve hosts and DHCPv4 options. boost::shared_ptr<MySqlHostWithOptionsExchange> host_exchange_; @@ -1890,40 +1911,18 @@ public: /// @brief MySQL connection MySqlConnection conn_; + /// @brief Indicates if the database is opened in read only mode. + bool is_readonly_; }; -namespace { -/// @brief Prepared MySQL statements used by the backend to insert and -/// retrieve hosts from the database. -TaggedStatement tagged_statements[] = { - // Inserts a host into the 'hosts' table. - {MySqlHostDataSourceImpl::INSERT_HOST, - "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, " - "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " - "dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, " - "dhcp4_server_hostname, dhcp4_boot_file_name) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"}, - - // Inserts a single IPv6 reservation into 'reservations' table. - {MySqlHostDataSourceImpl::INSERT_V6_RESRV, - "INSERT INTO ipv6_reservations(address, prefix_len, type, " - "dhcp6_iaid, host_id) " - "VALUES (?,?,?,?,?)"}, - // Inserts a single DHCPv4 option into 'dhcp4_options' table. - // Using fixed scope_id = 3, which associates an option with host. - {MySqlHostDataSourceImpl::INSERT_V4_OPTION, - "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, " - "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) " - " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}, - - // Inserts a single DHCPv6 option into 'dhcp6_options' table. - // Using fixed scope_id = 3, which associates an option with host. - {MySqlHostDataSourceImpl::INSERT_V6_OPTION, - "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, " - "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) " - " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}, +/// @brief Array of tagged statements. +typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS> +TaggedStatementArray; +/// @brief Prepared MySQL statements used by the backend to insert and +/// retrieve hosts from the database. +TaggedStatementArray tagged_statements = { { // Retrieves host information, IPv6 reservations and both DHCPv4 and // DHCPv6 options associated with the host. The LEFT JOIN clause is used // to retrieve information from 4 different tables using a single query. @@ -2051,11 +2050,34 @@ TaggedStatement tagged_statements[] = { {MySqlHostDataSourceImpl::GET_VERSION, "SELECT version, minor FROM schema_version"}, - // Marks the end of the statements table. - {MySqlHostDataSourceImpl::NUM_STATEMENTS, NULL} -}; + // Inserts a host into the 'hosts' table. + {MySqlHostDataSourceImpl::INSERT_HOST, + "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, " + "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + "dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, " + "dhcp4_server_hostname, dhcp4_boot_file_name) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"}, -}; // end anonymouse namespace + // Inserts a single IPv6 reservation into 'reservations' table. + {MySqlHostDataSourceImpl::INSERT_V6_RESRV, + "INSERT INTO ipv6_reservations(address, prefix_len, type, " + "dhcp6_iaid, host_id) " + "VALUES (?,?,?,?,?)"}, + + // Inserts a single DHCPv4 option into 'dhcp4_options' table. + // Using fixed scope_id = 3, which associates an option with host. + {MySqlHostDataSourceImpl::INSERT_V4_OPTION, + "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, " + "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) " + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}, + + // Inserts a single DHCPv6 option into 'dhcp6_options' table. + // Using fixed scope_id = 3, which associates an option with host. + {MySqlHostDataSourceImpl::INSERT_V6_OPTION, + "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, " + "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) " + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}} +}; MySqlHostDataSourceImpl:: MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters) @@ -2065,23 +2087,43 @@ MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters) DHCP4_AND_DHCP6)), host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()), host_option_exchange_(new MySqlOptionExchange()), - conn_(parameters) { + conn_(parameters), + is_readonly_(false) { // Open the database. conn_.openDatabase(); - // Disable autocommit. To avoid a flush to disk on every commit, the global - // parameter innodb_flush_log_at_trx_commit should be set to 2. This will - // cause the changes to be written to the log, but flushed to disk in the - // background every second. Setting the parameter to that value will speed - // up the system, but at the risk of losing data if the system crashes. - my_bool result = mysql_autocommit(conn_.mysql_, 0); + // Enable autocommit. In case transaction is explicitly used, this + // setting will be overwritten for the transaction. However, there are + // cases when lack of autocommit could cause transactions to hang + // until commit or rollback is explicitly called. This already + // caused issues for some unit tests which were unable to cleanup + // the database after the test because of pending transactions. + // Use of autocommit will eliminate this problem. + my_bool result = mysql_autocommit(conn_.mysql_, 1); if (result != 0) { isc_throw(DbOperationError, mysql_error(conn_.mysql_)); } - // Prepare all statements likely to be used. - conn_.prepareStatements(tagged_statements, NUM_STATEMENTS); + // Prepare query statements. Those are will be only used to retrieve + // information from the database, so they can be used even if the + // database is read only for the current user. + conn_.prepareStatements(tagged_statements.begin(), + tagged_statements.begin() + WRITE_STMTS_BEGIN); + + // Check if the backend is explicitly configured to operate with + // read only access to the database. + is_readonly_ = conn_.configuredReadOnly(); + + // If we are using read-write mode for the database we also prepare + // statements for INSERTS etc. + if (!is_readonly_) { + // Prepare statements for writing to the database, e.g. INSERT. + conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN, + tagged_statements.end()); + } else { + LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY); + } } MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() { @@ -2280,6 +2322,14 @@ getHost(const SubnetID& subnet_id, return (result); } +void +MySqlHostDataSourceImpl::checkReadOnly() const { + if (is_readonly_) { + isc_throw(ReadOnlyDb, "MySQL host database backend is configured to" + " operate in read only mode"); + } +} + MySqlHostDataSource:: MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters) @@ -2292,6 +2342,9 @@ MySqlHostDataSource::~MySqlHostDataSource() { void MySqlHostDataSource::add(const HostPtr& host) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + // Initiate MySQL transaction as we will have to make multiple queries // to insert host information into multiple tables. If that fails on // any stage, the transaction will be rolled back by the destructor of @@ -2614,12 +2667,16 @@ std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const { void MySqlHostDataSource::commit() { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); impl_->conn_.commit(); } void MySqlHostDataSource::rollback() { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); impl_->conn_.rollback(); } diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 9ce64f2256..d5e56949a0 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -8,6 +8,7 @@ #define MYSQL_HOST_DATA_SOURCE_H #include <dhcpsrv/base_host_data_source.h> +#include <dhcpsrv/db_exceptions.h> #include <dhcpsrv/mysql_connection.h> namespace isc { @@ -255,7 +256,7 @@ public: private: /// @brief Pointer to the implementation of the @ref MySqlHostDataSource. - MySqlHostDataSourceImpl* impl_; + MySqlHostDataSourceImpl* impl_; }; } diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index 410880af36..344fb272fc 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,6 +13,7 @@ #include <dhcpsrv/mysql_lease_mgr.h> #include <dhcpsrv/mysql_connection.h> +#include <boost/array.hpp> #include <boost/static_assert.hpp> #include <mysqld_error.h> @@ -70,6 +71,7 @@ using namespace std; /// lease object. namespace { + /// @brief Maximum length of the hostname stored in DNS. /// /// This length is restricted by the length of the domain-name carried @@ -82,7 +84,8 @@ const size_t HOSTNAME_MAX_LEN = 255; /// colon separators. const size_t ADDRESS6_TEXT_MAX_LEN = 39; -TaggedStatement tagged_statements[] = { +boost::array<TaggedStatement, MySqlLeaseMgr::NUM_STATEMENTS> +tagged_statements = { { {MySqlLeaseMgr::DELETE_LEASE4, "DELETE FROM lease4 WHERE address = ?"}, {MySqlLeaseMgr::DELETE_LEASE4_STATE_EXPIRED, @@ -205,8 +208,14 @@ TaggedStatement tagged_statements[] = { "hostname = ?, hwaddr = ?, hwtype = ?, hwaddr_source = ?, " "state = ? " "WHERE address = ?"}, - // End of list sentinel - {MySqlLeaseMgr::NUM_STATEMENTS, NULL} + {MySqlLeaseMgr::RECOUNT_LEASE4_STATS, + "SELECT subnet_id, state, count(state) as state_count " + " FROM lease4 GROUP BY subnet_id, state ORDER BY subnet_id"}, + {MySqlLeaseMgr::RECOUNT_LEASE6_STATS, + "SELECT subnet_id, lease_type, state, count(state) as state_count" + " FROM lease6 GROUP BY subnet_id, lease_type, state " + " ORDER BY subnet_id" } + } }; }; @@ -1214,8 +1223,145 @@ private: uint32_t state_; ///< Lease state. }; +/// @brief MySql derivation of the statistical lease data query +/// +/// This class is used to recalculate lease statistics for MySQL +/// lease storage. It does so by executing a query which returns a result +/// containining contain one row per monitored state per lease type per +/// subnet, ordered by subnet id in ascending order. +/// +class MySqlLeaseStatsQuery : public LeaseStatsQuery { +public: + /// @brief Constructor + /// + /// @param conn A open connection to the database housing the lease data + /// @brief statement_index Index of the query's prepared statement + /// @brief fetch_type Indicates if query supplies lease type + MySqlLeaseStatsQuery(MySqlConnection& conn, const size_t statement_index, + const bool fetch_type) + : conn_(conn), statement_index_(statement_index), statement_(NULL), + fetch_type_(fetch_type), + // Set the number of columns in the bind array based on fetch_type + // This is the number of columns expected in the result set + bind_(fetch_type_ ? 4 : 3) { + if (statement_index_ >= MySqlLeaseMgr::NUM_STATEMENTS) { + isc_throw(BadValue, "MySqlLeaseStatsQuery" + " - invalid statement index" << statement_index_); + } + + statement_ = conn.statements_[statement_index_]; + } + + /// @brief Destructor + virtual ~MySqlLeaseStatsQuery() { + (void) mysql_stmt_free_result(statement_); + } + /// @brief Creates the IPv4 lease statistical data result set + /// + /// The result set is populated by executing a SQL query against the + /// lease(4/6) table which sums the leases per lease state per lease + /// type (v6 only) per subnet id. This method binds the statement to + /// the output bind array and then executes the statement, and fetches + /// entire result set. + void start() { + int col = 0; + // subnet_id: unsigned int + bind_[col].buffer_type = MYSQL_TYPE_LONG; + bind_[col].buffer = reinterpret_cast<char*>(&subnet_id_); + bind_[col].is_unsigned = MLM_TRUE; + ++col; + + // Fetch the lease type if we were told to do so. + if (fetch_type_) { + // lease type: uint32_t + bind_[col].buffer_type = MYSQL_TYPE_LONG; + bind_[col].buffer = reinterpret_cast<char*>(&lease_type_); + bind_[col].is_unsigned = MLM_TRUE; + ++col; + } else { + fetch_type_ = Lease::TYPE_NA; + } + // state: uint32_t + bind_[col].buffer_type = MYSQL_TYPE_LONG; + bind_[col].buffer = reinterpret_cast<char*>(&lease_state_); + bind_[col].is_unsigned = MLM_TRUE; + ++col; + + // state_count_: uint32_t + bind_[col].buffer_type = MYSQL_TYPE_LONG; + bind_[col].buffer = reinterpret_cast<char*>(&state_count_); + bind_[col].is_unsigned = MLM_TRUE; + + // Set up the MYSQL_BIND array for the data being returned + // and bind it to the statement. + int status = mysql_stmt_bind_result(statement_, &bind_[0]); + conn_.checkError(status, statement_index_, "outbound binding failed"); + + // Execute the statement + status = mysql_stmt_execute(statement_); + conn_.checkError(status, statement_index_, "unable to execute"); + + // Ensure that all the lease information is retrieved in one go to avoid + // overhead of going back and forth between client and server. + status = mysql_stmt_store_result(statement_); + conn_.checkError(status, statement_index_, "results storage failed"); + } + + + /// @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. + /// + /// @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) { + bool have_row = false; + int status = mysql_stmt_fetch(statement_); + if (status == MLM_MYSQL_FETCH_SUCCESS) { + row.subnet_id_ = static_cast<SubnetID>(subnet_id_); + row.lease_type_ = static_cast<Lease::Type>(lease_type_); + row.lease_state_ = lease_state_; + row.state_count_ = state_count_; + have_row = true; + } else if (status != MYSQL_NO_DATA) { + conn_.checkError(status, statement_index_, "getNextRow failed"); + } + + return (have_row); + } + +private: + /// @brief Database connection to use to execute the query + MySqlConnection& conn_; + + /// @brief Index of the query's prepared statement + size_t statement_index_; + + /// @brief The query's prepared statement + MYSQL_STMT *statement_; + + /// @brief Indicates if query supplies lease type + bool fetch_type_; + + /// @brief Bind array used to store the query result set; + std::vector<MYSQL_BIND> bind_; + + /// @brief Receives subnet ID when fetching a row + uint32_t subnet_id_; + /// @brief Receives the lease type when fetching a row + uint32_t lease_type_; + /// @brief Receives the lease state when fetching a row + uint32_t lease_state_; + /// @brief Receives the state count when fetching a row + uint32_t state_count_; +}; // MySqlLeaseMgr Constructor and Destructor @@ -1236,7 +1382,7 @@ MySqlLeaseMgr::MySqlLeaseMgr(const MySqlConnection::ParameterMap& parameters) } // Prepare all statements likely to be used. - conn_.prepareStatements(tagged_statements, MySqlLeaseMgr::NUM_STATEMENTS); + conn_.prepareStatements(tagged_statements.begin(), tagged_statements.end()); // Create the exchange objects for use in exchanging data between the // program and the database. @@ -2025,6 +2171,23 @@ MySqlLeaseMgr::getVersion() const { return (std::make_pair(major, minor)); } +LeaseStatsQueryPtr +MySqlLeaseMgr::startLeaseStatsQuery4() { + LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(conn_, + RECOUNT_LEASE4_STATS, + false)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +MySqlLeaseMgr::startLeaseStatsQuery6() { + LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(conn_, + RECOUNT_LEASE6_STATS, + true)); + query->start(); + return(query); +} void MySqlLeaseMgr::commit() { diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h index a5824becbb..0e4b3af0b2 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.h +++ b/src/lib/dhcpsrv/mysql_lease_mgr.h @@ -25,7 +25,6 @@ namespace dhcp { class MySqlLease4Exchange; class MySqlLease6Exchange; - /// @brief MySQL Lease Manager /// /// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL @@ -410,6 +409,8 @@ public: INSERT_LEASE6, // Add entry to lease6 table UPDATE_LEASE4, // Update a Lease4 entry UPDATE_LEASE6, // Update a Lease6 entry + RECOUNT_LEASE4_STATS, // Fetches IPv4 address statisics + RECOUNT_LEASE6_STATS, // Fetches IPv6 address statisics NUM_STATEMENTS // Number of statements }; @@ -590,6 +591,25 @@ private: uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs, StatementIndex statement_index); + /// @brief Creates and runs the IPv4 lease stats query + /// + /// It creates an instance of a MySqlLeaseStatsQuery4 and then + /// invokes its start method, which fetches its statistical data + /// result set by executing the RECOUNT_LEASE_STATS4 query. + /// The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery4(); + + /// @brief Creates and runs the IPv6 lease stats query + /// + /// It creates an instance of a MySqlLeaseStatsQuery6 and then + /// invokes its start method, which fetches its statistical data + /// result set by executing the RECOUNT_LEASE_STATS6 query. + /// The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery6(); /// @brief Check Error and Throw Exception /// diff --git a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc index 5386e9d2d6..2de5485175 100644 --- a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc +++ b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc @@ -53,7 +53,7 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) { // 2. Update the copy with the passed keywords. BOOST_FOREACH(ConfigPair param, config_value->mapValue()) { try { - if (param.first == "persist") { + if ((param.first == "persist") || (param.first == "readonly")) { values_copy[param.first] = (param.second->boolValue() ? "true" : "false"); @@ -72,7 +72,8 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) { } } catch (const isc::data::TypeError& ex) { // Append position of the element. - isc_throw(isc::data::TypeError, ex.what() << " (" + isc_throw(BadValue, "invalid value type specified for " + "parameter '" << param.first << "' (" << param.second->getPosition() << ")"); } } diff --git a/src/lib/dhcpsrv/pgsql_connection.cc b/src/lib/dhcpsrv/pgsql_connection.cc index 9d722a458a..e79712c2fa 100644 --- a/src/lib/dhcpsrv/pgsql_connection.cc +++ b/src/lib/dhcpsrv/pgsql_connection.cc @@ -130,6 +130,16 @@ PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) { } void +PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement, + const PgSqlTaggedStatement* end_statement) { + // Created the PostgreSQL prepared statements. + for (const PgSqlTaggedStatement* tagged_statement = start_statement; + tagged_statement != end_statement; ++tagged_statement) { + prepareStatement(*tagged_statement); + } +} + +void PgSqlConnection::openDatabase() { string dbconnparameters; string shost = "localhost"; diff --git a/src/lib/dhcpsrv/pgsql_connection.h b/src/lib/dhcpsrv/pgsql_connection.h index 92bcd4b5ed..b0d793b611 100644 --- a/src/lib/dhcpsrv/pgsql_connection.h +++ b/src/lib/dhcpsrv/pgsql_connection.h @@ -313,6 +313,21 @@ public: /// failed. void prepareStatement(const PgSqlTaggedStatement& statement); + /// @brief Prepare statements + /// + /// Creates the prepared statements for all of the SQL statements used + /// by the PostgreSQL backend. + /// + /// @param start_statement Pointer to the first statement in range of the + /// statements to be compiled. + /// @param end_statement Pointer to the statement marking end of the + /// range of statements to be compiled. This last statement is not compiled. + /// + /// @throw isc::dhcp::DbOperationError An operation on the open database has + /// failed. + void prepareStatements(const PgSqlTaggedStatement* start_statement, + const PgSqlTaggedStatement* end_statement); + /// @brief Open Database /// /// Opens the database using the information supplied in the parameters diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index ba886c9d0c..732db7cf1d 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -10,6 +10,7 @@ #include <dhcp/option.h> #include <dhcp/option_definition.h> #include <dhcp/option_space.h> +#include <dhcpsrv/db_exceptions.h> #include <dhcpsrv/cfg_option.h> #include <dhcpsrv/dhcpsrv_log.h> #include <dhcpsrv/pgsql_host_data_source.h> @@ -19,6 +20,7 @@ #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/classification.hpp> +#include <boost/array.hpp> #include <boost/pointer_cast.hpp> #include <boost/static_assert.hpp> @@ -35,7 +37,7 @@ namespace { /// @brief Maximum length of option value. /// The maximum size of the raw option data that may be read from the -/// database. +/// database. const size_t OPTION_VALUE_MAX_LEN = 4096; /// @brief Numeric value representing last supported identifier. @@ -1134,12 +1136,11 @@ public: /// @brief Statement Tags /// - /// The contents of the enum are indexes into the list of SQL statements + /// The contents of the enum are indexes into the list of SQL statements. + /// It is assumed that the order is such that the indicies of statements + /// reading the database are less than those of statements modifying the + /// database. enum StatementIndex { - INSERT_HOST, // Insert new host to collection - INSERT_V6_RESRV, // Insert v6 reservation - INSERT_V4_HOST_OPTION, // Insert DHCPv4 option - INSERT_V6_HOST_OPTION, // Insert DHCPv6 option GET_HOST_DHCPID, // Gets hosts by host identifier GET_HOST_ADDR, // Gets hosts by IPv4 address GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID @@ -1147,9 +1148,20 @@ public: GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address GET_HOST_PREFIX, // Gets host by IPv6 prefix GET_VERSION, // Obtain version number + INSERT_HOST, // Insert new host to collection + INSERT_V6_RESRV, // Insert v6 reservation + INSERT_V4_HOST_OPTION, // Insert DHCPv4 option + INSERT_V6_HOST_OPTION, // Insert DHCPv6 option NUM_STATEMENTS // Number of statements }; + /// @brief Index of first statement performing write to the database. + /// + /// This value is used to mark border line between queries and other + /// statements and statements performing write operation on the database, + /// such as INSERT, DELETE, UPDATE. + static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST; + /// @brief Constructor. /// /// This constructor opens database connection and initializes prepared @@ -1253,6 +1265,14 @@ public: StatementIndex stindex, boost::shared_ptr<PgSqlHostExchange> exchange) const; + /// @brief Throws exception if database is read only. + /// + /// This method should be called by the methods which write to the + /// database. If the backend is operating in read-only mode this + /// method will throw exception. + /// + /// @throw DbReadOnly if backend is operating in read only mode. + void checkReadOnly() const; /// @brief Returns PostgreSQL schema version of the open database /// @@ -1289,67 +1309,25 @@ public: /// @brief MySQL connection PgSqlConnection conn_; + /// @brief Indicates if the database is opened in read only mode. + bool is_readonly_; }; namespace { +/// @brief Array of tagged statements. +typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS> +TaggedStatementArray; + /// @brief Prepared PosgreSQL statements used by the backend to insert and /// retrieve reservation data from the database. -PgSqlTaggedStatement tagged_statements[] = { - // PgSqlHostDataSourceImpl::INSERT_HOST - // Inserts a host into the 'hosts' table. Returns the inserted host id. - {11, - { OID_BYTEA, OID_INT2, - OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR, - OID_VARCHAR, OID_VARCHAR }, - "insert_host", - "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, " - " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " - " dhcp4_client_classes, dhcp6_client_classes, " - " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name) " - "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING host_id" - }, - - //PgSqlHostDataSourceImpl::INSERT_V6_RESRV - // Inserts a single IPv6 reservation into 'reservations' table. - {5, - { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 }, - "insert_v6_resrv", - "INSERT INTO ipv6_reservations(address, prefix_len, type, " - " dhcp6_iaid, host_id) " - "VALUES ($1, $2, $3, $4, $5)" - }, - - // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION - // Inserts a single DHCPv4 option into 'dhcp4_options' table. - // Using fixed scope_id = 3, which associates an option with host. - {6, - { OID_INT2, OID_BYTEA, OID_TEXT, - OID_VARCHAR, OID_BOOL, OID_INT8}, - "insert_v4_host_option", - "INSERT INTO dhcp4_options(code, value, formatted_value, space, " - " persistent, host_id, scope_id) " - "VALUES ($1, $2, $3, $4, $5, $6, 3)" - }, - - // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION - // Inserts a single DHCPv6 option into 'dhcp6_options' table. - // Using fixed scope_id = 3, which associates an option with host. - {6, - { OID_INT2, OID_BYTEA, OID_TEXT, - OID_VARCHAR, OID_BOOL, OID_INT8}, - "insert_v6_host_option", - "INSERT INTO dhcp6_options(code, value, formatted_value, space, " - " persistent, host_id, scope_id) " - "VALUES ($1, $2, $3, $4, $5, $6, 3)" - }, - +TaggedStatementArray tagged_statements = { { // PgSqlHostDataSourceImpl::GET_HOST_DHCPID // Retrieves host information, IPv6 reservations and both DHCPv4 and // DHCPv6 options associated with the host. The LEFT JOIN clause is used // to retrieve information from 4 different tables using a single query. // Hence, this query returns multiple rows for a single host. - {2, + {2, { OID_BYTEA, OID_INT2 }, "get_host_dhcpid", "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " @@ -1454,7 +1432,7 @@ PgSqlTaggedStatement tagged_statements[] = { // are returned due to left joining IPv6 reservations and DHCPv6 options. // The number of rows returned is multiplication of number of existing // IPv6 reservations and DHCPv6 options. - {2, + {2, { OID_VARCHAR, OID_INT2 }, "get_host_prefix", "SELECT h.host_id, h.dhcp_identifier, " @@ -1477,14 +1455,60 @@ PgSqlTaggedStatement tagged_statements[] = { // PgSqlHostDataSourceImpl::GET_VERSION // Retrieves MySQL schema version. - {0, + {0, { OID_NONE }, "get_version", "SELECT version, minor FROM schema_version" }, - // Marks the end of the statements table. - {0, { 0 }, NULL, NULL} + // PgSqlHostDataSourceImpl::INSERT_HOST + // Inserts a host into the 'hosts' table. Returns the inserted host id. + {11, + { OID_BYTEA, OID_INT2, + OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR, + OID_VARCHAR, OID_VARCHAR }, + "insert_host", + "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, " + " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " + " dhcp4_client_classes, dhcp6_client_classes, " + " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name) " + "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING host_id" + }, + + //PgSqlHostDataSourceImpl::INSERT_V6_RESRV + // Inserts a single IPv6 reservation into 'reservations' table. + {5, + { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 }, + "insert_v6_resrv", + "INSERT INTO ipv6_reservations(address, prefix_len, type, " + " dhcp6_iaid, host_id) " + "VALUES ($1, $2, $3, $4, $5)" + }, + + // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION + // Inserts a single DHCPv4 option into 'dhcp4_options' table. + // Using fixed scope_id = 3, which associates an option with host. + {6, + { OID_INT2, OID_BYTEA, OID_TEXT, + OID_VARCHAR, OID_BOOL, OID_INT8}, + "insert_v4_host_option", + "INSERT INTO dhcp4_options(code, value, formatted_value, space, " + " persistent, host_id, scope_id) " + "VALUES ($1, $2, $3, $4, $5, $6, 3)" + }, + + // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION + // Inserts a single DHCPv6 option into 'dhcp6_options' table. + // Using fixed scope_id = 3, which associates an option with host. + {6, + { OID_INT2, OID_BYTEA, OID_TEXT, + OID_VARCHAR, OID_BOOL, OID_INT8}, + "insert_v6_host_option", + "INSERT INTO dhcp6_options(code, value, formatted_value, space, " + " persistent, host_id, scope_id) " + "VALUES ($1, $2, $3, $4, $5, $6, 3)" + } +} }; }; // end anonymous namespace @@ -1497,20 +1521,27 @@ PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters) DHCP4_AND_DHCP6)), host_ipv6_reservation_exchange_(new PgSqlIPv6ReservationExchange()), host_option_exchange_(new PgSqlOptionExchange()), - conn_(parameters) { + conn_(parameters), + is_readonly_(false) { // Open the database. conn_.openDatabase(); - int i = 0; - for( ; tagged_statements[i].text != NULL ; ++i) { - conn_.prepareStatement(tagged_statements[i]); - } + conn_.prepareStatements(tagged_statements.begin(), + tagged_statements.begin() + WRITE_STMTS_BEGIN); - // 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); + // Check if the backend is explicitly configured to operate with + // read only access to the database. + is_readonly_ = conn_.configuredReadOnly(); + + // If we are using read-write mode for the database we also prepare + // statements for INSERTS etc. + if (!is_readonly_) { + conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN, + tagged_statements.end()); + + } else { + LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY); } } @@ -1670,6 +1701,15 @@ std::pair<uint32_t, uint32_t> PgSqlHostDataSourceImpl::getVersion() const { return (std::make_pair<uint32_t, uint32_t>(version, minor)); } +void +PgSqlHostDataSourceImpl::checkReadOnly() const { + if (is_readonly_) { + isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured" + " to operate in read only mode"); + } +} + + /*********** PgSqlHostDataSource *********************/ @@ -1684,6 +1724,9 @@ PgSqlHostDataSource::~PgSqlHostDataSource() { void PgSqlHostDataSource::add(const HostPtr& host) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + // Initiate PostgreSQL transaction as we will have to make multiple queries // to insert host information into multiple tables. If that fails on // any stage, the transaction will be rolled back by the destructor of @@ -1932,5 +1975,20 @@ std::pair<uint32_t, uint32_t> PgSqlHostDataSource::getVersion() const { return(impl_->getVersion()); } +void +PgSqlHostDataSource::commit() { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + impl_->conn_.commit(); +} + + +void +PgSqlHostDataSource::rollback() { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + impl_->conn_.rollback(); +} + }; // end of isc::dhcp namespace }; // end of isc namespace diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index 2d7ab6c10b..40ad840c86 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -273,6 +273,16 @@ public: /// has failed. virtual std::pair<uint32_t, uint32_t> getVersion() const; + /// @brief Commit Transactions + /// + /// Commits all pending database operations. + virtual void commit(); + + /// @brief Rollback Transactions + /// + /// Rolls back all pending database operations. + virtual void rollback(); + private: /// @brief Pointer to the implementation of the @ref PgSqlHostDataSource. diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index f9a5ad28eb..3ea7475484 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -26,7 +26,7 @@ using namespace std; namespace { -/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source +/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source /// columns. This is coverd by tickets #3557, #4530, and PR#9. /// @brief Catalog of all the SQL statements currently supported. Note @@ -201,6 +201,19 @@ PgSqlTaggedStatement tagged_statements[] = { "state = $13 " "WHERE address = $14"}, + // RECOUNT_LEASE4_STATS, + { 0, { OID_NONE }, + "recount_lease4_stats", + "SELECT subnet_id, state, count(state) as state_count " + "FROM lease4 GROUP BY subnet_id, state ORDER BY subnet_id"}, + + // RECOUNT_LEASE6_STATS, + { 0, { OID_NONE }, + "recount_lease6_stats", + "SELECT subnet_id, lease_type, state, count(state) as state_count " + "FROM lease6 GROUP BY subnet_id, lease_type, state " + "ORDER BY subnet_id"}, + // End of list sentinel { 0, { 0 }, NULL, NULL} }; @@ -681,6 +694,107 @@ private: //@} }; +/// @brief Base PgSql derivation of the statistical lease data query +/// +/// This class provides the functionality such as results storgae and row +/// fetching common to fulfilling the statistical lease data query. +/// +class PgSqlLeaseStatsQuery : public LeaseStatsQuery { +public: + /// @brief Constructor + /// + /// @param conn A open connection to the database housing the lease data + /// @param statement The lease data SQL prepared statement to execute + /// @param fetch_statement Indicates whether or not lease_type should be + /// fetched from the result set + PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement, + const bool fetch_type) + : conn_(conn), statement_(statement), result_set_(), next_row_(0), + fetch_type_(fetch_type) { + } + + /// @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 which sums the leases per lease state per + /// subnet id. + void start() { + // The query has no parameters, so we only need it's name. + result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name, + 0, NULL, NULL, NULL, 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. + /// + /// @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 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_); + + // 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_; +}; + PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters) : LeaseMgr(), exchange4_(new PgSqlLease4Exchange()), exchange6_(new PgSqlLease6Exchange()), conn_(parameters) { @@ -1222,6 +1336,26 @@ PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs, return (deleteLeaseCommon(statement_index, bind_array)); } +LeaseStatsQueryPtr +PgSqlLeaseMgr::startLeaseStatsQuery4() { + LeaseStatsQueryPtr query( + new PgSqlLeaseStatsQuery(conn_, + tagged_statements[RECOUNT_LEASE4_STATS], + false)); + query->start(); + return(query); +} + +LeaseStatsQueryPtr +PgSqlLeaseMgr::startLeaseStatsQuery6() { + LeaseStatsQueryPtr query( + new PgSqlLeaseStatsQuery(conn_, + tagged_statements[RECOUNT_LEASE6_STATS], + true)); + query->start(); + return(query); +} + string PgSqlLeaseMgr::getName() const { string name = ""; diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index d316f1b9f0..69924067a3 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -317,6 +317,26 @@ public: /// @return Number of leases deleted. virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs); + /// @brief Creates and runs the IPv4 lease stats query + /// + /// It creates an instance of a PgSqlLeaseStatsQuery4 and then + /// invokes its start method, which fetches its statistical data + /// result set by executing the RECOUNT_LEASE_STATS4 query. + /// The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery4(); + + /// @brief Creates and runs the IPv6 lease stats query + /// + /// It creates an instance of a PgSqlLeaseStatsQuery and then + /// invokes its start method, which fetches its statistical data + /// result set by executing the RECOUNT_LEASE_STATS6 query. + /// The query object is then returned. + /// + /// @return The populated query as a pointer to an LeaseStatsQuery + virtual LeaseStatsQueryPtr startLeaseStatsQuery6(); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) @@ -385,6 +405,8 @@ public: INSERT_LEASE6, // Add entry to lease6 table UPDATE_LEASE4, // Update a Lease4 entry UPDATE_LEASE6, // Update a Lease6 entry + RECOUNT_LEASE4_STATS, // Fetch IPv4 lease statistical data + RECOUNT_LEASE6_STATS, // Fetch IPv4 lease statistical data NUM_STATEMENTS // Number of statements }; diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index 84e29bd6fc..7c7830966f 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -7,6 +7,7 @@ #include <config.h> #include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/srv_config.h> +#include <dhcpsrv/lease_mgr_factory.h> #include <log/logger_manager.h> #include <log/logger_specification.h> #include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_* @@ -164,11 +165,17 @@ SrvConfig::removeStatistics() { void SrvConfig::updateStatistics() { - - // Updates statistics for v4 and v6 subnets - getCfgSubnets4()->updateStatistics(); - - getCfgSubnets6()->updateStatistics(); + // Updating subnet statistics involves updating lease statistics, which + // is done by the LeaseMgr. Since servers with subnets, must have a + // LeaseMgr, we do not bother updating subnet stats for servers without + // a lease manager, such as D2. @todo We should probably examine why + // "SrvConfig" is being used by D2. + if (LeaseMgrFactory::haveInstance()) { + // Updates statistics for v4 and v6 subnets + getCfgSubnets4()->updateStatistics(); + + getCfgSubnets6()->updateStatistics(); + } } } diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc index 8f86f6a5e9..764ad60ee1 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc @@ -123,6 +123,9 @@ AllocEngine4Test::generateDeclinedLease(const std::string& addr, AllocEngine6Test::AllocEngine6Test() { CfgMgr::instance().clear(); + // This lease mgr needs to exist to before configuration commits. + factory_.create("type=memfile universe=6 persist=false"); + duid_ = DuidPtr(new DUID(std::vector<uint8_t>(8, 0x42))); iaid_ = 42; @@ -141,7 +144,6 @@ AllocEngine6Test::AllocEngine6Test() { initFqdn("", false, false); - factory_.create("type=memfile universe=6 persist=false"); } void @@ -525,6 +527,10 @@ AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start, } AllocEngine4Test::AllocEngine4Test() { + + // This lease mgr needs to exist to before configuration commits. + factory_.create("type=memfile universe=4 persist=false"); + // Create fresh instance of the HostMgr, and drop any previous HostMgr state. HostMgr::instance().create(); @@ -548,7 +554,6 @@ AllocEngine4Test::AllocEngine4Test() { initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.109")); cfg_mgr.commit(); - factory_.create("type=memfile universe=4 persist=false"); // Create a default context. Note that remaining parameters must be // assigned when needed. diff --git a/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc index 03523d34b0..80e39370b1 100644 --- a/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc @@ -89,6 +89,7 @@ public: /// @brief Destructor. virtual ~CfgMySQLDbAccessTest() { destroyMySQLSchema(); + LeaseMgrFactory::destroy(); } }; diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc index c9edb88768..a6de68d7dd 100644 --- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc @@ -10,6 +10,7 @@ #include <dhcp/dhcp6.h> #include <dhcp/tests/iface_mgr_test_config.h> #include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/subnet_id.h> #include <dhcpsrv/parsers/dhcp_parsers.h> #include <stats/stats_mgr.h> @@ -280,6 +281,23 @@ public: void clear() { CfgMgr::instance().setVerbose(false); CfgMgr::instance().clear(); + LeaseMgrFactory::destroy(); + } + + /// @brief Creates instance of the backend. + /// + /// @param family AF_INET for v4, AF_INET6 for v6 + void startBackend(int family = AF_INET) { + try { + std::ostringstream s; + s << "type=memfile persist=false " << (family == AF_INET6 ? + "universe=6" : "universe=4"); + LeaseMgrFactory::create(s.str()); + } catch (const std::exception& ex) { + std::cerr << "*** ERROR: unable to create instance of the Memfile\n" + " lease database backend: " << ex.what() << std::endl; + throw; + } } /// used in client classification (or just empty container for other tests) @@ -575,6 +593,7 @@ TEST_F(CfgMgrTest, verbosity) { TEST_F(CfgMgrTest, commitStats4) { CfgMgr& cfg_mgr = CfgMgr::instance(); StatsMgr& stats_mgr = StatsMgr::instance(); + startBackend(AF_INET); // Let's prepare the "old" configuration: a subnet with id 123 // and pretend there were addresses assigned, so statistics are non-zero. @@ -641,6 +660,7 @@ TEST_F(CfgMgrTest, clearStats4) { TEST_F(CfgMgrTest, commitStats6) { CfgMgr& cfg_mgr = CfgMgr::instance(); StatsMgr& stats_mgr = StatsMgr::instance(); + startBackend(AF_INET6); // Let's prepare the "old" configuration: a subnet with id 123 // and pretend there were addresses assigned, so statistics are non-zero. diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc index 846808da1f..e71dc2530f 100644 --- a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc @@ -88,7 +88,7 @@ public: } // Add the keyword and value - make sure that they are quoted. - // The parameters which are not quoted are persist and + // The parameters which are not quoted are persist, readonly and // lfc-interval as they are boolean and integer respectively. result += quote + keyval[i] + quote + colon + space; if (!quoteValue(std::string(keyval[i]))) { @@ -176,7 +176,8 @@ private: /// @return true if the value of the parameter should be quoted. bool quoteValue(const std::string& parameter) const { return ((parameter != "persist") && (parameter != "lfc-interval") && - (parameter != "connect-timeout")); + (parameter != "connect-timeout") && + (parameter != "readonly")); } }; @@ -560,4 +561,45 @@ TEST_F(DbAccessParserTest, getDbAccessString) { EXPECT_EQ(dbaccess, "name=keatest type=mysql"); } +// Check that the configuration is accepted for the valid value +// of "readonly". +TEST_F(DbAccessParserTest, validReadOnly) { + const char* config[] = {"type", "mysql", + "user", "keatest", + "password", "keatest", + "name", "keatest", + "readonly", "true", + NULL}; + + string json_config = toJson(config); + ConstElementPtr json_elements = Element::fromJSON(json_config); + EXPECT_TRUE(json_elements); + + TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB); + EXPECT_NO_THROW(parser.build(json_elements)); + + checkAccessString("Valid readonly parameter", + parser.getDbAccessParameters(), + config); +} + +// Check that for the invalid value of the "readonly" parameter +// an exception is thrown. +TEST_F(DbAccessParserTest, invalidReadOnly) { + const char* config[] = {"type", "mysql", + "user", "keatest", + "password", "keatest", + "name", "keatest", + "readonly", "1", + NULL}; + + string json_config = toJson(config); + ConstElementPtr json_elements = Element::fromJSON(json_config); + EXPECT_TRUE(json_elements); + + TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB); + EXPECT_THROW(parser.build(json_elements), BadValue); +} + + }; // Anonymous namespace diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc index 009985e70c..515b6757b3 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc @@ -12,8 +12,10 @@ #include <dhcp/option_string.h> #include <dhcp/option_int.h> #include <dhcp/option_vendor.h> +#include <dhcpsrv/host_data_source_factory.h> #include <dhcpsrv/tests/generic_host_data_source_unittest.h> #include <dhcpsrv/tests/test_utils.h> +#include <dhcpsrv/testutils/schema.h> #include <dhcpsrv/database_connection.h> #include <asiolink/io_address.h> #include <util/buffer.h> @@ -507,6 +509,55 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host, LibDHCP::setRuntimeOptionDefs(defs); } +void +GenericHostDataSourceTest::testReadOnlyDatabase(const char* valid_db_type) { + ASSERT_TRUE(hdsptr_); + + // The database is initially opened in "read-write" mode. We can + // insert some data to the databse. + HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false); + ASSERT_TRUE(host); + ASSERT_NO_THROW(hdsptr_->add(host)); + + // Subnet id will be used in queries to the database. + SubnetID subnet_id = host->getIPv6SubnetID(); + + // Make sure that the host has been inserted and that the data can be + // retrieved. + ConstHostPtr host_by_id = hdsptr_->get6(subnet_id, host->getIdentifierType(), + &host->getIdentifier()[0], + host->getIdentifier().size()); + ASSERT_TRUE(host_by_id); + ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id)); + + // Close the database connection and reopen in "read-only" mode as + // specified by the "VALID_READONLY_DB" parameter. + HostDataSourceFactory::destroy(); + HostDataSourceFactory::create(connectionString(valid_db_type, + VALID_NAME, + VALID_HOST, + VALID_READONLY_USER, + VALID_PASSWORD, + VALID_READONLY_DB)); + + hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr(); + + // Check that an attempt to insert new host would result in + // exception. + HostPtr host2 = initializeHost6("2001:db8::2", Host::IDENT_DUID, false); + ASSERT_TRUE(host2); + ASSERT_THROW(hdsptr_->add(host2), ReadOnlyDb); + ASSERT_THROW(hdsptr_->commit(), ReadOnlyDb); + ASSERT_THROW(hdsptr_->rollback(), ReadOnlyDb); + + // Reading from the database should still be possible, though. + host_by_id = hdsptr_->get6(subnet_id, host->getIdentifierType(), + &host->getIdentifier()[0], + host->getIdentifier().size()); + ASSERT_TRUE(host_by_id); + ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_id)); +} + void GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) { // Make sure we have the pointer to the host data source. ASSERT_TRUE(hdsptr_); diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h index 79b9f97b33..807ca21490 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h @@ -337,6 +337,23 @@ public: /// @brief Pointer to the host data source HostDataSourcePtr hdsptr_; + /// @brief Test that backend can be started in read-only mode. + /// + /// Some backends can operate when the database is read only, e.g. + /// host reservation tables are read only, or the database user has + /// read only privileges on the entire database. In such cases, the + /// Kea server administrator can specify in the backend configuration + /// that the database should be opened in read only mode, i.e. + /// INSERT, UPDATE, DELETE statements can't be issued. If any of the + /// functions updating the database is called for the backend, the + /// error is reported. The database running in read only mode can + /// be merely used to retrieve existing host reservations from the + /// database. This test verifies that this is the case. + /// + /// @param valid_db_type Parameter specifying type of backend to + /// be used, e.g. type=mysql. + void testReadOnlyDatabase(const char* valid_db_type); + /// @brief Test that checks that simple host with IPv4 reservation /// can be inserted and later retrieved. /// diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index a94a3a2b5c..25d1b2bd07 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -5,11 +5,18 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <config.h> + +#include <asiolink/io_address.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/database_connection.h> #include <dhcpsrv/tests/generic_lease_mgr_unittest.h> #include <dhcpsrv/tests/test_utils.h> -#include <dhcpsrv/database_connection.h> -#include <asiolink/io_address.h> +#include <stats/stats_mgr.h> + +#include <boost/foreach.hpp> + #include <gtest/gtest.h> + #include <sstream> using namespace std; @@ -57,6 +64,7 @@ GenericLeaseMgrTest::GenericLeaseMgrTest() /// a template leasetype6_.push_back(LEASETYPE6[i]); } + } GenericLeaseMgrTest::~GenericLeaseMgrTest() { @@ -2381,6 +2389,300 @@ GenericLeaseMgrTest::testGetDeclinedLeases6() { } } +void +GenericLeaseMgrTest::checkStat(const std::string& name, + const int64_t expected_value) { + stats::ObservationPtr obs = + stats::StatsMgr::instance().getObservation(name); + + ASSERT_TRUE(obs) << " stat: " << name << " not found "; + ASSERT_EQ(expected_value, obs->getInteger().first) + << " stat: " << name << " value wrong"; +} + +void +GenericLeaseMgrTest::checkLeaseStats(const StatValMapList& expectedStats) { + // Global accumulators + int64_t declined_addresses = 0; + int64_t declined_reclaimed_addresses = 0; + + // Iterate over all stats for each subnet + for (int subnet_idx = 0; subnet_idx < expectedStats.size(); ++subnet_idx) { + BOOST_FOREACH(StatValPair expectedStat, expectedStats[subnet_idx]) { + // Verify the per subnet value. + checkStat(stats::StatsMgr::generateName("subnet", subnet_idx+1, + expectedStat.first), + expectedStat.second); + + // Add the value to globals as needed. + if (expectedStat.first == "declined-addresses") { + declined_addresses += expectedStat.second; + } else if (expectedStat.first == "declined-reclaimed-addresses") { + declined_reclaimed_addresses += expectedStat.second; + } + } + } + + // Verify the globals. + checkStat("declined-addresses", declined_addresses); + checkStat("declined-reclaimed-addresses", declined_reclaimed_addresses); +} + +void +GenericLeaseMgrTest::makeLease4(const std::string& address, + const SubnetID& subnet_id, + const uint32_t state) { + Lease4Ptr lease(new Lease4()); + + // set the address + lease->addr_ = IOAddress(address); + + // make a MAC from the address + std::vector<uint8_t> hwaddr = lease->addr_.toBytes(); + hwaddr.push_back(0); + hwaddr.push_back(0); + + lease->hwaddr_.reset(new HWAddr(hwaddr, HTYPE_ETHER)); + lease->valid_lft_ = 86400; + lease->cltt_ = 168256; + lease->subnet_id_ = subnet_id; + lease->state_ = state; + ASSERT_TRUE(lmptr_->addLease(lease)); +} + +void +GenericLeaseMgrTest::makeLease6(const Lease::Type& type, + const std::string& address, + uint8_t prefix_len, + const SubnetID& subnet_id, + const uint32_t state) { + IOAddress addr(address); + + // make a DUID from the address + std::vector<uint8_t> bytes = addr.toBytes(); + bytes.push_back(prefix_len); + + Lease6Ptr lease(new Lease6(type, addr, DuidPtr(new DUID(bytes)), 77, + 16000, 24000, 0, 0, subnet_id, HWAddrPtr(), + prefix_len)); + lease->state_ = state; + ASSERT_TRUE(lmptr_->addLease(lease)); +} + +void +GenericLeaseMgrTest::testRecountLeaseStats4() { + using namespace stats; + + StatsMgr::instance().removeAll(); + + // Create two subnets. + int num_subnets = 2; + CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4(); + Subnet4Ptr subnet; + Pool4Ptr pool; + + subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1)); + pool.reset(new Pool4(IOAddress("192.0.1.0"), 24)); + subnet->addPool(pool); + cfg->add(subnet); + + subnet.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 2)); + pool.reset(new Pool4(IOAddress("192.0.2.0"), 24)); + subnet->addPool(pool); + cfg->add(subnet); + + + ASSERT_NO_THROW(CfgMgr::instance().commit()); + + // Create the expected stats list. At this point, the only stat + // that should be non-zero is total-addresses. + StatValMapList expectedStats(num_subnets); + for (int i = 0; i < num_subnets; ++i) { + expectedStats[i]["total-addresses"] = 256; + expectedStats[i]["assigned-addresses"] = 0; + expectedStats[i]["declined-addresses"] = 0; + expectedStats[i]["declined-reclaimed-addresses"] = 0; + } + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + // Recount stats. We should have the same results. + ASSERT_NO_THROW(lmptr_->recountLeaseStats4()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + // Now let's insert some leases into subnet 1. + int subnet_id = 1; + + // Insert one lease in default state, i.e. assigned. + makeLease4("192.0.1.1", subnet_id); + + // Insert one lease in declined state. + makeLease4("192.0.1.2", subnet_id, Lease::STATE_DECLINED); + + // Insert one lease in the expired state. + makeLease4("192.0.1.3", subnet_id, Lease::STATE_EXPIRED_RECLAIMED); + + // Insert another lease in default state, i.e. assigned. + makeLease4("192.0.1.4", subnet_id); + + // Update the expected stats list for subnet 1. + expectedStats[subnet_id - 1]["assigned-addresses"] = 2; + expectedStats[subnet_id - 1]["declined-addresses"] = 1; + + // Now let's add leases to subnet 2. + subnet_id = 2; + + // Insert one delined lease. + makeLease4("192.0.2.2", subnet_id, Lease::STATE_DECLINED); + + // Update the expected stats. + expectedStats[subnet_id - 1]["declined-addresses"] = 1; + + // Now Recount the stats. + ASSERT_NO_THROW(lmptr_->recountLeaseStats4()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + // Delete some leases from subnet, and update the expected stats. + EXPECT_TRUE(lmptr_->deleteLease(IOAddress("192.0.1.1"))); + expectedStats[0]["assigned-addresses"] = 1; + + EXPECT_TRUE(lmptr_->deleteLease(IOAddress("192.0.1.2"))); + expectedStats[0]["declined-addresses"] = 0; + + // Recount the stats. + ASSERT_NO_THROW(lmptr_->recountLeaseStats4()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); +} + + +void +GenericLeaseMgrTest::testRecountLeaseStats6() { + using namespace stats; + + StatsMgr::instance().removeAll(); + + // Create two subnets. + int num_subnets = 2; + CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6(); + Subnet6Ptr subnet; + Pool6Ptr pool; + StatValMapList expectedStats(num_subnets); + + int subnet_id = 1; + subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, subnet_id)); + pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"), + IOAddress("3001:1::FF"))); + subnet->addPool(pool); + expectedStats[subnet_id - 1]["total-nas"] = 256; + + pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"),96,112)); + subnet->addPool(pool); + expectedStats[subnet_id - 1]["total-pds"] = 65536; + cfg->add(subnet); + + ++subnet_id; + subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, + subnet_id)); + pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 120)); + subnet->addPool(pool); + expectedStats[subnet_id - 1]["total-nas"] = 256; + expectedStats[subnet_id - 1]["total-pds"] = 0; + cfg->add(subnet); + + ASSERT_NO_THROW(CfgMgr::instance().commit()); + + + // Create the expected stats list. At this point, the only stat + // that should be non-zero is total-nas/total-pds. + for (int i = 0; i < num_subnets; ++i) { + expectedStats[i]["assigned-nas"] = 0; + expectedStats[i]["declined-addresses"] = 0; + expectedStats[i]["declined-reclaimed-addresses"] = 0; + expectedStats[i]["assigned-pds"] = 0; + } + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + + // Recount stats. We should have the same results. + ASSERT_NO_THROW(lmptr_->recountLeaseStats4()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + // Now let's insert some leases into subnet 1. + subnet_id = 1; + + // Insert three assigned NAs. + makeLease6(Lease::TYPE_NA, "3001:1::1", 0, subnet_id); + makeLease6(Lease::TYPE_NA, "3001:1::2", 0, subnet_id); + makeLease6(Lease::TYPE_NA, "3001:1::3", 0, subnet_id); + expectedStats[subnet_id - 1]["assigned-nas"] = 3; + + // Insert two declined NAs. + makeLease6(Lease::TYPE_NA, "3001:1::4", 0, subnet_id, + Lease::STATE_DECLINED); + makeLease6(Lease::TYPE_NA, "3001:1::5", 0, subnet_id, + Lease::STATE_DECLINED); + expectedStats[subnet_id - 1]["declined-addresses"] = 2; + + // Insert one expired NA. + makeLease6(Lease::TYPE_NA, "3001:1::6", 0, subnet_id, + Lease::STATE_EXPIRED_RECLAIMED); + + // Insert two assigned PDs. + makeLease6(Lease::TYPE_PD, "3001:1:2:0100::", 112, subnet_id); + makeLease6(Lease::TYPE_PD, "3001:1:2:0200::", 112, subnet_id); + expectedStats[subnet_id - 1]["assigned-pds"] = 2; + + // Insert two expired PDs. + makeLease6(Lease::TYPE_PD, "3001:1:2:0300::", 112, subnet_id, + Lease::STATE_EXPIRED_RECLAIMED); + makeLease6(Lease::TYPE_PD, "3001:1:2:0400::", 112, subnet_id, + Lease::STATE_EXPIRED_RECLAIMED); + + // Now let's add leases to subnet 2. + subnet_id = 2; + + // Insert two assigned NAs. + makeLease6(Lease::TYPE_NA, "2001:db81::1", 0, subnet_id); + makeLease6(Lease::TYPE_NA, "2001:db81::2", 0, subnet_id); + expectedStats[subnet_id - 1]["assigned-nas"] = 2; + + // Insert one declined NA. + makeLease6(Lease::TYPE_NA, "2001:db81::3", 0, subnet_id, + Lease::STATE_DECLINED); + expectedStats[subnet_id - 1]["declined-addresses"] = 1; + + // Now Recount the stats. + ASSERT_NO_THROW(lmptr_->recountLeaseStats6()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); + + // Delete some leases and update the expected stats. + EXPECT_TRUE(lmptr_->deleteLease(IOAddress("3001:1::2"))); + expectedStats[0]["assigned-nas"] = 2; + + EXPECT_TRUE(lmptr_->deleteLease(IOAddress("2001:db81::3"))); + expectedStats[1]["declined-addresses"] = 0; + + // Recount the stats. + ASSERT_NO_THROW(lmptr_->recountLeaseStats6()); + + // Make sure stats are as expected. + ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); +} + + }; // namespace test }; // namespace dhcp }; // namespace isc diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h index 3ef1405503..f426787833 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -15,6 +15,12 @@ namespace isc { namespace dhcp { namespace test { + +/// @brief typedefs to simplify lease statistic testing +typedef std::map<std::string, int64_t> StatValMap; +typedef std::pair<std::string, int64_t> StatValPair; +typedef std::vector<StatValMap> StatValMapList; + /// @brief Test Fixture class with utility functions for LeaseMgr backends /// /// It contains utility functions, like dummy lease creation. @@ -94,6 +100,47 @@ public: /// @return vector<Lease6Ptr> Vector of pointers to leases std::vector<Lease6Ptr> createLeases6(); + /// @brief Compares a StatsMgr statistic to an expected value + /// + /// Attempt to fetch the named statistic from the StatsMg and if + /// found, compare its observed value to the given value. + /// Fails if the stat is not found or if the values do not match. + /// + /// @param name StatsMgr name for the statistic to check + /// @param expected_value expected value of the statistic + void checkStat(const std::string& name, const int64_t expected_value); + + /// @brief Compares StatsMgr statistics against an expected list of values + /// + /// Iterates over a list of statistic names and expected values, attempting + /// to fetch each from the StatsMgr and if found, compare its observed value + /// to the expected value. Fails if any of the expected stats are not + /// found or if the values do not match. + /// + /// @param expected_stats Map of expected static names and values. + void checkLeaseStats(const StatValMapList& expected_stats); + + /// @brief Constructs a minimal IPv4 lease and adds it to the lease storage + /// + /// @param address - IPv4 address for the lease + /// @param subnet_id - subnet ID to which the lease belongs + /// @param state - the state of the lease + void makeLease4(const std::string& address, const SubnetID& subnet_id, + const uint32_t state = Lease::STATE_DEFAULT); + + /// @brief Constructs a minimal IPv6 lease and adds it to the lease storage + /// + /// The DUID is constructed from the address and prefix length. + /// + /// @param type - type of lease to create (TYPE_NA, TYPE_PD...) + /// @param address - IPv6 address/prefix for the lease + /// @param prefix_len = length of the prefix (should be 0 for TYPE_NA) + /// @param subnet_id - subnet ID to which the lease belongs + /// @param state - the state of the lease + void makeLease6(const Lease::Type& type, const std::string& address, + uint8_t prefix_len, const SubnetID& subnet_id, + const uint32_t state = Lease::STATE_DEFAULT); + /// @brief checks that addLease, getLease4(addr) and deleteLease() works void testBasicLease4(); @@ -313,6 +360,20 @@ public: /// leases can be removed. void testDeleteExpiredReclaimedLeases4(); + /// @brief Check that the IPv4 lease statistics can be recounted + /// + /// This test creates two subnets and several leases associated with + /// them, then verifies that lease statistics are recalculated correctly + /// after altering the lease states in various ways. + void testRecountLeaseStats4(); + + /// @brief Check that the IPv6 lease statistics can be recounted + /// + /// This test creates two subnets and several leases associated with + /// them, then verifies that lease statistics are recalculated correctly + /// after altering the lease states in various ways. + void testRecountLeaseStats6(); + /// @brief String forms of IPv4 addresses std::vector<std::string> straddress4_; diff --git a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc index 2abad6c444..8e922d355d 100644 --- a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc @@ -9,7 +9,17 @@ #include <dhcp/hwaddr.h> #include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/host.h> +#include <dhcpsrv/host_data_source_factory.h> #include <dhcpsrv/host_mgr.h> + +#if defined HAVE_MYSQL +#include <dhcpsrv/testutils/mysql_schema.h> +#endif + +#if defined HAVE_PGSQL +#include <dhcpsrv/testutils/pgsql_schema.h> +#endif + #include <gtest/gtest.h> #include <vector> @@ -37,6 +47,85 @@ protected: /// in the @c CfgMgr. CfgHostsPtr getCfgHosts() const; + /// @brief Inserts IPv4 reservation into the host data source. + /// + /// @param data_source Reference to the data source to which the reservation + /// should be inserted. + /// @param hwaddr Pointer to the hardware address to be associated with the + /// reservation. + /// @param subnet_id IPv4 subnet id. + /// @param address IPv4 address to be reserved. + void addHost4(BaseHostDataSource& data_source, + const HWAddrPtr& hwaddr, + const SubnetID& subnet_id, + const IOAddress& address); + + /// @brief Inserts IPv6 reservation into the host data source. + /// + /// @param data_source Reference to the data source to which the reservation + /// should be inserted. + /// @param duid Pointer to the DUID to be associated with the reservation. + /// @param subnet_id IPv6 subnet id. + /// @param address IPv6 address/prefix to be reserved. + /// @param prefix_len Prefix length. The default value is 128 which + /// indicates that the reservation is for an IPv6 address rather than a + /// prefix. + void addHost6(BaseHostDataSource& data_source, + const DuidPtr& duid, + const SubnetID& subnet_id, + const IOAddress& address, + const uint8_t prefix_len = 128); + + /// @brief This test verifies that HostMgr returns all reservations for the + /// specified HW address. + /// + /// If reservations are added to different host data sources, it is expected + /// that the @c HostMgr will retrieve reservations from both of them. + /// + /// @param data_source1 Host data source to which first reservation is + /// inserted. + /// @param data_source2 Host data source to which second reservation is + /// inserted. + void testGetAll(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2); + + /// @brief This test verifies that it is possible to retrieve IPv4 + /// reservation for the particular host using HostMgr. + /// + /// If reservations are added to different host data sources, it is expected + /// that the @c HostMgr will retrieve reservations from both of them. + /// + /// @param data_source1 Host data source to which first reservation is + /// inserted. + /// @param data_source2 Host data source to which second reservation is + /// inserted. + void testGetAll4(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2); + + /// @brief This test verifies that it is possible to retrieve an IPv4 + /// reservation for the particular host using HostMgr. + /// + /// @param data_source Host data source to which reservation is inserted and + /// from which it will be retrieved. + void testGet4(BaseHostDataSource& data_source); + + /// @brief This test verifies that it is possible to retrieve an IPv6 + /// reservation for the particular host using HostMgr. + /// + /// @param data_source Host data source to which reservation is inserted and + /// from which it will be retrieved. + void testGet6(BaseHostDataSource& data_source); + + /// @brief This test verifies that it is possible to retrieve an IPv6 + /// prefix reservation for the particular host using HostMgr. + /// + /// @param data_source1 Host data source to which first reservation is + /// inserted. + /// @param data_source2 Host data source to which second reservation is + /// inserted. + void testGet6ByPrefix(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2); + /// @brief HW addresses to be used by the tests. std::vector<HWAddrPtr> hwaddrs_; /// @brief DUIDs to be used by the tests. @@ -79,29 +168,52 @@ HostMgrTest::getCfgHosts() const { return (CfgMgr::instance().getStagingCfg()->getCfgHosts()); } -/// This test verifies that HostMgr returns all reservations for the -/// specified HW address. The reservations are defined in the server's -/// configuration. -TEST_F(HostMgrTest, getAll) { +void +HostMgrTest::addHost4(BaseHostDataSource& data_source, + const HWAddrPtr& hwaddr, + const SubnetID& subnet_id, + const IOAddress& address) { + data_source.add(HostPtr(new Host(hwaddr->toText(false), + "hw-address", subnet_id, SubnetID(0), + address))); +} + +void +HostMgrTest::addHost6(BaseHostDataSource& data_source, + const DuidPtr& duid, + const SubnetID& subnet_id, + const IOAddress& address, + const uint8_t prefix_len) { + HostPtr new_host(new Host(duid->toText(), "duid", SubnetID(1), + subnet_id, IOAddress::IPV4_ZERO_ADDRESS())); + new_host->addReservation(IPv6Resrv(prefix_len == 128 ? IPv6Resrv::TYPE_NA : + IPv6Resrv::TYPE_PD, + address, prefix_len)); + data_source.add(new_host); +} + + +void +HostMgrTest::testGetAll(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2) { // Initially, no reservations should be present. ConstHostCollection hosts = HostMgr::instance().getAll(hwaddrs_[0]); ASSERT_TRUE(hosts.empty()); // Add two reservations for the same HW address. They differ by the IP // address reserved and the IPv4 subnet. - getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false), - "hw-address", SubnetID(1), SubnetID(0), - IOAddress("192.0.2.5")))); - getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false), - "hw-address", SubnetID(10), SubnetID(0), - IOAddress("192.0.3.10")))); + addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5")); + addHost4(data_source2, hwaddrs_[0], SubnetID(10), IOAddress("192.0.3.10")); + CfgMgr::instance().commit(); // If there non-matching HW address is specified, nothing should be // returned. - ASSERT_TRUE(HostMgr::instance().getAll(Host::IDENT_HWADDR, + hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR, &hwaddrs_[1]->hwaddr_[0], - hwaddrs_[1]->hwaddr_.size()).empty()); + hwaddrs_[1]->hwaddr_.size()); + ASSERT_TRUE(hosts.empty()); + // For the correct HW address, there should be two reservations. hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR, &hwaddrs_[0]->hwaddr_[0], @@ -136,26 +248,24 @@ TEST_F(HostMgrTest, getAll) { ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10" " not found using getAll method"; } - } -// This test verifies that it is possible to gather all reservations for the -// specified IPv4 address from the HostMgr. The reservations are specified in -// the server's configuration. -TEST_F(HostMgrTest, getAll4) { +void +HostMgrTest::testGetAll4(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2) { + // Initially, no hosts should be present. ConstHostCollection hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")); ASSERT_TRUE(hosts.empty()); - getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false), - "hw-address", SubnetID(1), SubnetID(0), - IOAddress("192.0.2.5")))); + // Add two hosts to different data sources. + addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5")); + addHost4(data_source2, hwaddrs_[1], SubnetID(10), IOAddress("192.0.2.5")); - getCfgHosts()->add(HostPtr(new Host(hwaddrs_[1]->toText(false), - "hw-address", SubnetID(10), SubnetID(0), - IOAddress("192.0.2.5")))); CfgMgr::instance().commit(); + // Retrieve all hosts, This should return hosts from both sources + // in a single container. hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")); ASSERT_EQ(2, hosts.size()); @@ -167,19 +277,18 @@ TEST_F(HostMgrTest, getAll4) { EXPECT_NE(hosts[0]->getIPv4SubnetID(), hosts[1]->getIPv4SubnetID()); } -// This test verifies that it is possible to retrieve a reservation for the -// particular host using HostMgr. The reservation is specified in the server's -// configuration. -TEST_F(HostMgrTest, get4) { +void +HostMgrTest::testGet4(BaseHostDataSource& data_source) { + // Initially, no host should be present. ConstHostPtr host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]); ASSERT_FALSE(host); - getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false), - "hw-address", - SubnetID(1), SubnetID(2), - IOAddress("192.0.2.5")))); + // Add new host to the database. + addHost4(data_source, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5")); + CfgMgr::instance().commit(); + // Retrieve the host from the database and expect that the parameters match. host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR, &hwaddrs_[0]->hwaddr_[0], hwaddrs_[0]->hwaddr_.size()); @@ -188,20 +297,18 @@ TEST_F(HostMgrTest, get4) { EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText()); } -// This test verifies that it is possible to retrieve IPv6 reservations for -// the particular host using HostMgr. The reservation is specified in the -// server's configuration. -TEST_F(HostMgrTest, get6) { +void +HostMgrTest::testGet6(BaseHostDataSource& data_source) { + // Initially, no host should be present. ConstHostPtr host = HostMgr::instance().get6(SubnetID(2), duids_[0]); ASSERT_FALSE(host); - HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1), - SubnetID(2), IOAddress("0.0.0.0"))); - new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, - IOAddress("2001:db8:1::1"))); - getCfgHosts()->add(new_host); + // Add new host to the database. + addHost6(data_source, duids_[0], SubnetID(2), IOAddress("2001:db8:1::1")); + CfgMgr::instance().commit(); + // Retrieve the host from the database and expect that the parameters match. host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID, &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()); @@ -210,32 +317,25 @@ TEST_F(HostMgrTest, get6) { IOAddress("2001:db8:1::1")))); } -// This test verifies that it is possible to retrieve the reservation of the -// particular IPv6 prefix using HostMgr. -TEST_F(HostMgrTest, get6ByPrefix) { +void +HostMgrTest::testGet6ByPrefix(BaseHostDataSource& data_source1, + BaseHostDataSource& data_source2) { ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64); ASSERT_FALSE(host); // Add a host with a reservation for a prefix 2001:db8:1::/64. - HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1), - SubnetID(2), IOAddress("0.0.0.0"))); - new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, - IOAddress("2001:db8:1::"), 64)); - getCfgHosts()->add(new_host); + addHost6(data_source1, duids_[0], SubnetID(2), IOAddress("2001:db8:1::"), 64); // Add another host having a reservation for prefix 2001:db8:1:0:6::/72. - new_host.reset(new Host(duids_[1]->toText(), "duid", SubnetID(2), - SubnetID(3), IOAddress::IPV4_ZERO_ADDRESS())); - new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, - IOAddress("2001:db8:1:0:6::"), 72)); - getCfgHosts()->add(new_host); + addHost6(data_source2, duids_[1], SubnetID(3), IOAddress("2001:db8:1:0:6::"), 72); + CfgMgr::instance().commit(); // Retrieve first reservation. host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64); ASSERT_TRUE(host); EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, - IOAddress("2001:db8:1::"), 64))); + IOAddress("2001:db8:1::"), 64))); // Make sure the first reservation is not retrieved when the prefix // length is incorrect. @@ -246,7 +346,7 @@ TEST_F(HostMgrTest, get6ByPrefix) { host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 72); ASSERT_TRUE(host); EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, - IOAddress("2001:db8:1:0:6::"), 72))); + IOAddress("2001:db8:1:0:6::"), 72))); // Make sure the second reservation is not retrieved when the prefix // length is incorrect. @@ -254,4 +354,190 @@ TEST_F(HostMgrTest, get6ByPrefix) { EXPECT_FALSE(host); } +/// This test verifies that HostMgr returns all reservations for the +/// specified HW address. The reservations are defined in the server's +/// configuration. +TEST_F(HostMgrTest, getAll) { + testGetAll(*getCfgHosts(), *getCfgHosts()); +} + +// This test verifies that it is possible to gather all reservations for the +// specified IPv4 address from the HostMgr. The reservations are specified in +// the server's configuration. +TEST_F(HostMgrTest, getAll4) { + testGetAll4(*getCfgHosts(), *getCfgHosts()); +} + +// This test verifies that it is possible to retrieve a reservation for the +// particular host using HostMgr. The reservation is specified in the server's +// configuration. +TEST_F(HostMgrTest, get4) { + testGet4(*getCfgHosts()); +} + +// This test verifies that it is possible to retrieve IPv6 reservations for +// the particular host using HostMgr. The reservation is specified in the +// server's configuration. +TEST_F(HostMgrTest, get6) { + testGet6(*getCfgHosts()); +} + +// This test verifies that it is possible to retrieve the reservation of the +// particular IPv6 prefix using HostMgr. +TEST_F(HostMgrTest, get6ByPrefix) { + testGet6ByPrefix(*getCfgHosts(), *getCfgHosts()); +} + +// The following tests require MySQL enabled. +#if defined HAVE_MYSQL + +/// @brief Test fixture class for validating @c HostMgr using +/// MySQL as alternate host data source. +class MySQLHostMgrTest : public HostMgrTest { +protected: + + /// @brief Build MySQL schema for a test. + virtual void SetUp(); + + /// @brief Rollback and drop MySQL schema after the test. + virtual void TearDown(); +}; + +void +MySQLHostMgrTest::SetUp() { + HostMgrTest::SetUp(); + + // Ensure schema is the correct one. + test::destroyMySQLSchema(); + test::createMySQLSchema(); + + // Connect to the database + try { + HostMgr::create(test::validMySQLConnectionString()); + } catch (...) { + std::cerr << "*** ERROR: unable to open database. The test\n" + "*** environment is broken and must be fixed before\n" + "*** the MySQL tests will run correctly.\n" + "*** The reason for the problem is described in the\n" + "*** accompanying exception output.\n"; + throw; + } +} + +void +MySQLHostMgrTest::TearDown() { + HostDataSourceFactory::getHostDataSourcePtr()->rollback(); + HostDataSourceFactory::destroy(); + test::destroyMySQLSchema(); +} + +// This test verifies that reservations for a particular client can +// be retrieved from the confguration file and a database simultaneously. +TEST_F(MySQLHostMgrTest, getAll) { + testGetAll(*getCfgHosts(), HostMgr::instance()); +} + +// This test verifies that IPv4 reservations for a particular client can +// be retrieved from the configuration file and a database simulatneously. +TEST_F(MySQLHostMgrTest, getAll4) { + testGetAll4(*getCfgHosts(), HostMgr::instance()); +} + +// This test verifies that the IPv4 reservation can be retrieved from a +// database. +TEST_F(MySQLHostMgrTest, get4) { + testGet4(HostMgr::instance()); +} + +// This test verifies that the IPv6 reservation can be retrieved from a +// database. +TEST_F(MySQLHostMgrTest, get6) { + testGet6(HostMgr::instance()); +} + +// This test verifies that the IPv6 prefix reservation can be retrieved +// from a configuration file and a database. +TEST_F(MySQLHostMgrTest, get6ByPrefix) { + testGet6ByPrefix(*getCfgHosts(), HostMgr::instance()); +} + +#endif + + +// The following tests require PostgreSQL enabled. +#if defined HAVE_PGSQL + +/// @brief Test fixture class for validating @c HostMgr using +/// PostgreSQL as alternate host data source. +class PostgreSQLHostMgrTest : public HostMgrTest { +protected: + + /// @brief Build PostgreSQL schema for a test. + virtual void SetUp(); + + /// @brief Rollback and drop PostgreSQL schema after the test. + virtual void TearDown(); + +}; + +void +PostgreSQLHostMgrTest::SetUp() { + HostMgrTest::SetUp(); + + // Ensure schema is the correct one. + test::destroyPgSQLSchema(); + test::createPgSQLSchema(); + + // Connect to the database + try { + HostMgr::create(test::validPgSQLConnectionString()); + } catch (...) { + std::cerr << "*** ERROR: unable to open database. The test\n" + "*** environment is broken and must be fixed before\n" + "*** the PostgreSQL tests will run correctly.\n" + "*** The reason for the problem is described in the\n" + "*** accompanying exception output.\n"; + throw; + } +} + +void +PostgreSQLHostMgrTest::TearDown() { + HostDataSourceFactory::getHostDataSourcePtr()->rollback(); + HostDataSourceFactory::destroy(); + test::destroyPgSQLSchema(); +} + +// This test verifies that reservations for a particular client can +// be retrieved from the confguration file and a database simultaneously. +TEST_F(PostgreSQLHostMgrTest, getAll) { + testGetAll(*getCfgHosts(), HostMgr::instance()); +} + +// This test verifies that IPv4 reservations for a particular client can +// be retrieved from the configuration file and a database simulatneously. +TEST_F(PostgreSQLHostMgrTest, getAll4) { + testGetAll4(*getCfgHosts(), HostMgr::instance()); +} + +// This test verifies that the IPv4 reservation can be retrieved from a +// database. +TEST_F(PostgreSQLHostMgrTest, get4) { + testGet4(HostMgr::instance()); +} + +// This test verifies that the IPv6 reservation can be retrieved from a +// database. +TEST_F(PostgreSQLHostMgrTest, get6) { + testGet6(HostMgr::instance()); +} + +// This test verifies that the IPv6 prefix reservation can be retrieved +// from a configuration file and a database. +TEST_F(PostgreSQLHostMgrTest, get6ByPrefix) { + testGet6ByPrefix(*getCfgHosts(), HostMgr::instance()); +} + +#endif + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc index a01a54457c..f52f2c1bd3 100644 --- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc @@ -1883,6 +1883,16 @@ TEST_F(MemfileLeaseMgrTest, lease6ContainerIndexUpdate) { } } +// Verifies that IPv4 lease statistics can be recalculated. +TEST_F(MemfileLeaseMgrTest, recountLeaseStats4) { + startBackend(V4); + testRecountLeaseStats4(); +} +// Verifies that IPv6 lease statistics can be recalculated. +TEST_F(MemfileLeaseMgrTest, recountLeaseStats6) { + startBackend(V6); + testRecountLeaseStats6(); +} }; // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc index d5f9a846f8..60359afd89 100644 --- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc @@ -63,8 +63,13 @@ public: /// Rolls back all pending transactions. The deletion of myhdsptr_ will close /// the database. Then reopen it and delete everything created by the test. virtual ~MySqlHostDataSourceTest() { - hdsptr_->rollback(); + try { + hdsptr_->rollback(); + } catch (...) { + // Rollback may fail if backend is in read only mode. That's ok. + } HostDataSourceFactory::destroy(); + hdsptr_.reset(); destroyMySQLSchema(); } @@ -157,6 +162,9 @@ TEST(MySqlHostDataSource, OpenDatabase) { EXPECT_THROW(HostDataSourceFactory::create(connectionString( MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)), DbInvalidTimeout); + EXPECT_THROW(HostDataSourceFactory::create(connectionString( + MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, + VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly); // Check for missing parameters EXPECT_THROW(HostDataSourceFactory::create(connectionString( @@ -167,6 +175,8 @@ TEST(MySqlHostDataSource, OpenDatabase) { destroyMySQLSchema(); } + + /// @brief Check conversion functions /// /// The server works using cltt and valid_filetime. In the database, the @@ -208,6 +218,10 @@ TEST(MySqlConnection, checkTimeConversion) { EXPECT_EQ(cltt, converted_cltt); } +TEST_F(MySqlHostDataSourceTest, testReadOnlyDatabase) { + testReadOnlyDatabase(MYSQL_VALID_TYPE); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses hw address as identifier. TEST_F(MySqlHostDataSourceTest, basic4HWAddr) { diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc index 33549b415a..b5433ab332 100644 --- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc @@ -477,4 +477,14 @@ TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases4) { testDeleteExpiredReclaimedLeases4(); } +// Verifies that IPv4 lease statistics can be recalculated. +TEST_F(MySqlLeaseMgrTest, recountLeaseStats4) { + testRecountLeaseStats4(); +} + +// Verifies that IPv6 lease statistics can be recalculated. +TEST_F(MySqlLeaseMgrTest, recountLeaseStats6) { + testRecountLeaseStats6(); +} + }; // Of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc index 8f22257ddb..b5456bfb8e 100644 --- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc @@ -64,8 +64,13 @@ public: /// close the database. Then reopen it and delete everything created by /// the test. virtual ~PgSqlHostDataSourceTest() { - hdsptr_->rollback(); + try { + hdsptr_->rollback(); + } catch (...) { + // Rollback may fail if backend is in read only mode. That's ok. + } HostDataSourceFactory::destroy(); + hdsptr_.reset(); destroyPgSQLSchema(); } @@ -168,6 +173,12 @@ TEST(PgSqlHostDataSource, OpenDatabase) { destroyPgSQLSchema(); } + +// This test verifies that database backend can operate in Read-Only mode. +TEST_F(PgSqlHostDataSourceTest, testReadOnlyDatabase) { + testReadOnlyDatabase(PGSQL_VALID_TYPE); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses hw address as identifier. TEST_F(PgSqlHostDataSourceTest, basic4HWAddr) { diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc index 9234881863..0a629d5579 100644 --- a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc @@ -402,4 +402,14 @@ TEST_F(PgSqlLeaseMgrTest, getExpiredLeases6) { testGetExpiredLeases6(); } +// Verifies that IPv4 lease statistics can be recalculated. +TEST_F(PgSqlLeaseMgrTest, recountLeaseStats4) { + testRecountLeaseStats4(); +} + +// Verifies that IPv6 lease statistics can be recalculated. +TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6) { + testRecountLeaseStats6(); +} + }; // namespace diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index 7f62aa3775..de4b15a898 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -15,17 +15,7 @@ noinst_LTLIBRARIES = libdhcpsrvtest.la libdhcpsrvtest_la_SOURCES = config_result_check.cc config_result_check.h libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h -if HAVE_MYSQL -libdhcpsrvtest_la_SOURCES += schema.cc schema.h -else -if HAVE_PGSQL -libdhcpsrvtest_la_SOURCES += schema.cc schema.h -else -if HAVE_CQL libdhcpsrvtest_la_SOURCES += schema.cc schema.h -endif -endif -endif if HAVE_MYSQL libdhcpsrvtest_la_SOURCES += mysql_schema.cc mysql_schema.h diff --git a/src/lib/dhcpsrv/testutils/schema.cc b/src/lib/dhcpsrv/testutils/schema.cc index cefcbdae5c..b373363dc7 100644 --- a/src/lib/dhcpsrv/testutils/schema.cc +++ b/src/lib/dhcpsrv/testutils/schema.cc @@ -25,15 +25,19 @@ const char* INVALID_NAME = "name=invalidname"; const char* VALID_HOST = "host=localhost"; const char* INVALID_HOST = "host=invalidhost"; const char* VALID_USER = "user=keatest"; +const char* VALID_READONLY_USER = "user=keatest_readonly"; const char* INVALID_USER = "user=invaliduser"; const char* VALID_PASSWORD = "password=keatest"; const char* INVALID_PASSWORD = "password=invalid"; const char* VALID_TIMEOUT = "connect-timeout=10"; const char* INVALID_TIMEOUT_1 = "connect-timeout=foo"; const char* INVALID_TIMEOUT_2 = "connect-timeout=-17"; +const char* VALID_READONLY_DB = "readonly=true"; +const char* INVALID_READONLY_DB = "readonly=5"; string connectionString(const char* type, const char* name, const char* host, - const char* user, const char* password, const char* timeout) { + const char* user, const char* password, const char* timeout, + const char* readonly_db = NULL) { const string space = " "; string result = ""; @@ -75,6 +79,13 @@ string connectionString(const char* type, const char* name, const char* host, result += string(timeout); } + if (readonly_db != NULL) { + if (! result.empty()) { + result += space; + } + result += string(readonly_db); + } + return (result); } diff --git a/src/lib/dhcpsrv/testutils/schema.h b/src/lib/dhcpsrv/testutils/schema.h index 5a0471512f..14d04abb6e 100644 --- a/src/lib/dhcpsrv/testutils/schema.h +++ b/src/lib/dhcpsrv/testutils/schema.h @@ -21,12 +21,16 @@ extern const char* INVALID_NAME; extern const char* VALID_HOST; extern const char* INVALID_HOST; extern const char* VALID_USER; +extern const char* VALID_READONLY_USER; extern const char* INVALID_USER; extern const char* VALID_PASSWORD; extern const char* INVALID_PASSWORD; extern const char* VALID_TIMEOUT; extern const char* INVALID_TIMEOUT_1; extern const char* INVALID_TIMEOUT_2; +extern const char* VALID_READONLY_DB; +extern const char* INVALID_READONLY_DB; + /// @brief Given a combination of strings above, produce a connection string. /// /// @param type type of the database @@ -35,10 +39,12 @@ extern const char* INVALID_TIMEOUT_2; /// @param user username used to authenticate during connection attempt /// @param password password used to authenticate during connection attempt /// @param timeout timeout used during connection attempt +/// @param readonly_db specifies if database is read only /// @return string containing all specified parameters std::string connectionString(const char* type, const char* name = NULL, const char* host = NULL, const char* user = NULL, - const char* password = NULL, const char* timeout = NULL); + const char* password = NULL, const char* timeout = NULL, + const char* readonly_db = NULL); }; }; }; diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc index 4ede1dbc42..8cd13653b5 100644 --- a/src/lib/eval/eval_context.cc +++ b/src/lib/eval/eval_context.cc @@ -139,7 +139,7 @@ EvalContext::convertUint32(const std::string& number, } catch (const boost::bad_lexical_cast &) { error(loc, "Invalid value in " + number); } - if (n >= std::numeric_limits<uint32_t>::max()) { + if (n > std::numeric_limits<uint32_t>::max()) { error(loc, "Invalid value in " + number + ". Allowed range: 0..4294967295"); } @@ -147,6 +147,17 @@ EvalContext::convertUint32(const std::string& number, return (static_cast<uint32_t>(n)); } +std::string +EvalContext::fromUint32(const uint32_t integer) { + std::string tmp(4, 0); + tmp[0] = (integer >> 24) & 0xff; + tmp[1] = (integer >> 16) & 0xff; + tmp[2] = (integer >> 8) & 0xff; + tmp[3] = integer & 0xff; + + return (tmp); +} + void EvalContext::fatal (const std::string& what) { diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h index 4984554ec4..385f1a7aeb 100644 --- a/src/lib/eval/eval_context.h +++ b/src/lib/eval/eval_context.h @@ -69,13 +69,13 @@ public: /// /// @param loc location within the parsed file when experienced a problem. /// @param what string explaining the nature of the error. - void error(const isc::eval::location& loc, const std::string& what); + static void error(const isc::eval::location& loc, const std::string& what); /// @brief Error handler /// /// This is a simplified error reporting tool for possible future /// cases when the EvalParser is not able to handle the packet. - void error(const std::string& what); + static void error(const std::string& what); /// @brief Fatal error handler /// @@ -103,12 +103,14 @@ public: /// @brief Attempts to convert string to unsigned 32bit integer /// + /// For reverse conversion, see @ref fromUint32 + /// /// @param number string to be converted /// @param loc the location of the token /// @return the integer value /// @throw EvalParseError if conversion fails or the value is out of range. - uint32_t convertUint32(const std::string& number, - const isc::eval::location& loc); + static uint32_t convertUint32(const std::string& number, + const isc::eval::location& loc); /// @brief Attempts to convert string to unsigned 8bit integer /// @@ -116,8 +118,8 @@ public: /// @param loc the location of the token /// @return the integer value /// @throw EvalParseError if conversion fails or the value is out of range. - uint8_t convertUint8(const std::string& number, - const isc::eval::location& loc); + static uint8_t convertUint8(const std::string& number, + const isc::eval::location& loc); /// @brief Nest level conversion /// @@ -127,7 +129,17 @@ public: /// @throw calls the syntax error function if the value is not in /// the range 0..31 uint8_t convertNestLevelNumber(const std::string& nest_level, - const isc::eval::location& loc); + const isc::eval::location& loc); + + /// @brief Converts integer to string representation + /// + /// The integer is coded as a 4 byte long string in network order, e.g. + /// 6 is represented as 00000006. For reverse conversion, see + /// @ref convertUint32. + /// + /// @param integer value to be converted + /// @return 4 byte long string that encodes the value. + static std::string fromUint32(const uint32_t integer); /// @brief Returns the universe (v4 or v6) /// diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc index 69757ba1b3..af6de6d4bb 100644 --- a/src/lib/eval/lexer.cc +++ b/src/lib/eval/lexer.cc @@ -745,11 +745,11 @@ int yy_flex_debug = 1; static yyconst flex_int16_t yy_rule_linenum[51] = { 0, - 82, 86, 92, 102, 108, 122, 129, 143, 144, 145, - 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, - 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, - 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185 + 82, 86, 92, 102, 108, 126, 133, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, + 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189 } ; static yy_state_type *yy_state_buf=0, *yy_state_ptr=0; @@ -1286,7 +1286,11 @@ YY_RULE_SETUP std::string tmp(yytext); try { - static_cast<void>(boost::lexical_cast<int>(tmp)); + // In substring we want to use negative values (e.g. -1). + // In enterprise-id we need to use values up to 0xffffffff. + // To cover both of those use cases, we need at least + // int64_t. + static_cast<void>(boost::lexical_cast<int64_t>(tmp)); } catch (const boost::bad_lexical_cast &) { driver.error(loc, "Failed to convert " + tmp + " to an integer."); } @@ -1298,7 +1302,7 @@ YY_RULE_SETUP case 6: /* rule 6 can match eol */ YY_RULE_SETUP -#line 122 "lexer.ll" +#line 126 "lexer.ll" { // This string specifies option name starting with a letter // and further containing letters, digits, hyphens and @@ -1308,7 +1312,7 @@ YY_RULE_SETUP YY_BREAK case 7: YY_RULE_SETUP -#line 129 "lexer.ll" +#line 133 "lexer.ll" { // IPv4 or IPv6 address std::string tmp(yytext); @@ -1325,229 +1329,229 @@ YY_RULE_SETUP YY_BREAK case 8: YY_RULE_SETUP -#line 143 "lexer.ll" +#line 147 "lexer.ll" return isc::eval::EvalParser::make_EQUAL(loc); YY_BREAK case 9: YY_RULE_SETUP -#line 144 "lexer.ll" +#line 148 "lexer.ll" return isc::eval::EvalParser::make_OPTION(loc); YY_BREAK case 10: YY_RULE_SETUP -#line 145 "lexer.ll" +#line 149 "lexer.ll" return isc::eval::EvalParser::make_RELAY4(loc); YY_BREAK case 11: YY_RULE_SETUP -#line 146 "lexer.ll" +#line 150 "lexer.ll" return isc::eval::EvalParser::make_RELAY6(loc); YY_BREAK case 12: YY_RULE_SETUP -#line 147 "lexer.ll" +#line 151 "lexer.ll" return isc::eval::EvalParser::make_PEERADDR(loc); YY_BREAK case 13: YY_RULE_SETUP -#line 148 "lexer.ll" +#line 152 "lexer.ll" return isc::eval::EvalParser::make_LINKADDR(loc); YY_BREAK case 14: YY_RULE_SETUP -#line 149 "lexer.ll" +#line 153 "lexer.ll" return isc::eval::EvalParser::make_TEXT(loc); YY_BREAK case 15: YY_RULE_SETUP -#line 150 "lexer.ll" +#line 154 "lexer.ll" return isc::eval::EvalParser::make_HEX(loc); YY_BREAK case 16: YY_RULE_SETUP -#line 151 "lexer.ll" +#line 155 "lexer.ll" return isc::eval::EvalParser::make_EXISTS(loc); YY_BREAK case 17: YY_RULE_SETUP -#line 152 "lexer.ll" +#line 156 "lexer.ll" return isc::eval::EvalParser::make_PKT(loc); YY_BREAK case 18: YY_RULE_SETUP -#line 153 "lexer.ll" +#line 157 "lexer.ll" return isc::eval::EvalParser::make_IFACE(loc); YY_BREAK case 19: YY_RULE_SETUP -#line 154 "lexer.ll" +#line 158 "lexer.ll" return isc::eval::EvalParser::make_SRC(loc); YY_BREAK case 20: YY_RULE_SETUP -#line 155 "lexer.ll" +#line 159 "lexer.ll" return isc::eval::EvalParser::make_DST(loc); YY_BREAK case 21: YY_RULE_SETUP -#line 156 "lexer.ll" +#line 160 "lexer.ll" return isc::eval::EvalParser::make_LEN(loc); YY_BREAK case 22: YY_RULE_SETUP -#line 157 "lexer.ll" +#line 161 "lexer.ll" return isc::eval::EvalParser::make_PKT4(loc); YY_BREAK case 23: YY_RULE_SETUP -#line 158 "lexer.ll" +#line 162 "lexer.ll" return isc::eval::EvalParser::make_CHADDR(loc); YY_BREAK case 24: YY_RULE_SETUP -#line 159 "lexer.ll" +#line 163 "lexer.ll" return isc::eval::EvalParser::make_HLEN(loc); YY_BREAK case 25: YY_RULE_SETUP -#line 160 "lexer.ll" +#line 164 "lexer.ll" return isc::eval::EvalParser::make_HTYPE(loc); YY_BREAK case 26: YY_RULE_SETUP -#line 161 "lexer.ll" +#line 165 "lexer.ll" return isc::eval::EvalParser::make_CIADDR(loc); YY_BREAK case 27: YY_RULE_SETUP -#line 162 "lexer.ll" +#line 166 "lexer.ll" return isc::eval::EvalParser::make_GIADDR(loc); YY_BREAK case 28: YY_RULE_SETUP -#line 163 "lexer.ll" +#line 167 "lexer.ll" return isc::eval::EvalParser::make_YIADDR(loc); YY_BREAK case 29: YY_RULE_SETUP -#line 164 "lexer.ll" +#line 168 "lexer.ll" return isc::eval::EvalParser::make_SIADDR(loc); YY_BREAK case 30: YY_RULE_SETUP -#line 165 "lexer.ll" +#line 169 "lexer.ll" return isc::eval::EvalParser::make_PKT6(loc); YY_BREAK case 31: YY_RULE_SETUP -#line 166 "lexer.ll" +#line 170 "lexer.ll" return isc::eval::EvalParser::make_MSGTYPE(loc); YY_BREAK case 32: YY_RULE_SETUP -#line 167 "lexer.ll" +#line 171 "lexer.ll" return isc::eval::EvalParser::make_TRANSID(loc); YY_BREAK case 33: YY_RULE_SETUP -#line 168 "lexer.ll" +#line 172 "lexer.ll" return isc::eval::EvalParser::make_VENDOR(loc); YY_BREAK case 34: YY_RULE_SETUP -#line 169 "lexer.ll" +#line 173 "lexer.ll" return isc::eval::EvalParser::make_VENDOR_CLASS(loc); YY_BREAK case 35: YY_RULE_SETUP -#line 170 "lexer.ll" +#line 174 "lexer.ll" return isc::eval::EvalParser::make_DATA(loc); YY_BREAK case 36: YY_RULE_SETUP -#line 171 "lexer.ll" +#line 175 "lexer.ll" return isc::eval::EvalParser::make_ENTERPRISE(loc); YY_BREAK case 37: YY_RULE_SETUP -#line 172 "lexer.ll" +#line 176 "lexer.ll" return isc::eval::EvalParser::make_SUBSTRING(loc); YY_BREAK case 38: YY_RULE_SETUP -#line 173 "lexer.ll" +#line 177 "lexer.ll" return isc::eval::EvalParser::make_ALL(loc); YY_BREAK case 39: YY_RULE_SETUP -#line 174 "lexer.ll" +#line 178 "lexer.ll" return isc::eval::EvalParser::make_CONCAT(loc); YY_BREAK case 40: YY_RULE_SETUP -#line 175 "lexer.ll" +#line 179 "lexer.ll" return isc::eval::EvalParser::make_NOT(loc); YY_BREAK case 41: YY_RULE_SETUP -#line 176 "lexer.ll" +#line 180 "lexer.ll" return isc::eval::EvalParser::make_AND(loc); YY_BREAK case 42: YY_RULE_SETUP -#line 177 "lexer.ll" +#line 181 "lexer.ll" return isc::eval::EvalParser::make_OR(loc); YY_BREAK case 43: YY_RULE_SETUP -#line 178 "lexer.ll" +#line 182 "lexer.ll" return isc::eval::EvalParser::make_DOT(loc); YY_BREAK case 44: YY_RULE_SETUP -#line 179 "lexer.ll" +#line 183 "lexer.ll" return isc::eval::EvalParser::make_LPAREN(loc); YY_BREAK case 45: YY_RULE_SETUP -#line 180 "lexer.ll" +#line 184 "lexer.ll" return isc::eval::EvalParser::make_RPAREN(loc); YY_BREAK case 46: YY_RULE_SETUP -#line 181 "lexer.ll" +#line 185 "lexer.ll" return isc::eval::EvalParser::make_LBRACKET(loc); YY_BREAK case 47: YY_RULE_SETUP -#line 182 "lexer.ll" +#line 186 "lexer.ll" return isc::eval::EvalParser::make_RBRACKET(loc); YY_BREAK case 48: YY_RULE_SETUP -#line 183 "lexer.ll" +#line 187 "lexer.ll" return isc::eval::EvalParser::make_COMA(loc); YY_BREAK case 49: YY_RULE_SETUP -#line 184 "lexer.ll" +#line 188 "lexer.ll" return isc::eval::EvalParser::make_ANY(loc); YY_BREAK case 50: YY_RULE_SETUP -#line 185 "lexer.ll" +#line 189 "lexer.ll" driver.error (loc, "Invalid character: " + std::string(yytext)); YY_BREAK case YY_STATE_EOF(INITIAL): -#line 186 "lexer.ll" +#line 190 "lexer.ll" return isc::eval::EvalParser::make_END(loc); YY_BREAK case 51: YY_RULE_SETUP -#line 187 "lexer.ll" +#line 191 "lexer.ll" ECHO; YY_BREAK -#line 1551 "lexer.cc" +#line 1555 "lexer.cc" case YY_END_OF_BUFFER: { @@ -2630,7 +2634,7 @@ void yyfree (void * ptr ) /* %ok-for-header */ -#line 187 "lexer.ll" +#line 191 "lexer.ll" diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll index fdefcecd63..f7bd53593a 100644 --- a/src/lib/eval/lexer.ll +++ b/src/lib/eval/lexer.ll @@ -110,7 +110,11 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]* std::string tmp(yytext); try { - static_cast<void>(boost::lexical_cast<int>(tmp)); + // In substring we want to use negative values (e.g. -1). + // In enterprise-id we need to use values up to 0xffffffff. + // To cover both of those use cases, we need at least + // int64_t. + static_cast<void>(boost::lexical_cast<int64_t>(tmp)); } catch (const boost::bad_lexical_cast &) { driver.error(loc, "Failed to convert " + tmp + " to an integer."); } diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc index 8504c3d485..4631b44a7d 100644 --- a/src/lib/eval/parser.cc +++ b/src/lib/eval/parser.cc @@ -251,23 +251,23 @@ namespace isc { namespace eval { { switch (that.type_get ()) { - case 55: // option_repr_type + case 56: // option_repr_type value.move< TokenOption::RepresentationType > (that.value); break; - case 59: // pkt4_field + case 60: // pkt4_field value.move< TokenPkt4::FieldType > (that.value); break; - case 60: // pkt6_field + case 61: // pkt6_field value.move< TokenPkt6::FieldType > (that.value); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.move< TokenPkt::MetadataType > (that.value); break; - case 61: // relay6_field + case 62: // relay6_field value.move< TokenRelay6Field::FieldType > (that.value); break; @@ -279,15 +279,16 @@ namespace isc { namespace eval { value.move< std::string > (that.value); break; - case 54: // option_code + case 55: // option_code value.move< uint16_t > (that.value); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.move< uint32_t > (that.value); break; - case 56: // nest_level + case 57: // nest_level value.move< uint8_t > (that.value); break; @@ -306,23 +307,23 @@ namespace isc { namespace eval { state = that.state; switch (that.type_get ()) { - case 55: // option_repr_type + case 56: // option_repr_type value.copy< TokenOption::RepresentationType > (that.value); break; - case 59: // pkt4_field + case 60: // pkt4_field value.copy< TokenPkt4::FieldType > (that.value); break; - case 60: // pkt6_field + case 61: // pkt6_field value.copy< TokenPkt6::FieldType > (that.value); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.copy< TokenPkt::MetadataType > (that.value); break; - case 61: // relay6_field + case 62: // relay6_field value.copy< TokenRelay6Field::FieldType > (that.value); break; @@ -334,15 +335,16 @@ namespace isc { namespace eval { value.copy< std::string > (that.value); break; - case 54: // option_code + case 55: // option_code value.copy< uint16_t > (that.value); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.copy< uint32_t > (that.value); break; - case 56: // nest_level + case 57: // nest_level value.copy< uint8_t > (that.value); break; @@ -384,93 +386,100 @@ namespace isc { namespace eval { { case 45: // "constant string" -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 390 "parser.cc" // lalr1.cc:636 +#line 392 "parser.cc" // lalr1.cc:636 break; case 46: // "integer" -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 397 "parser.cc" // lalr1.cc:636 +#line 399 "parser.cc" // lalr1.cc:636 break; case 47: // "constant hexstring" -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 404 "parser.cc" // lalr1.cc:636 +#line 406 "parser.cc" // lalr1.cc:636 break; case 48: // "option name" -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 411 "parser.cc" // lalr1.cc:636 +#line 413 "parser.cc" // lalr1.cc:636 break; case 49: // "ip address" -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< std::string > (); } -#line 418 "parser.cc" // lalr1.cc:636 +#line 420 "parser.cc" // lalr1.cc:636 break; - case 54: // option_code + case 54: // integer_expr -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 + { yyoutput << yysym.value.template as< uint32_t > (); } +#line 427 "parser.cc" // lalr1.cc:636 + break; + + case 55: // option_code + +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< uint16_t > (); } -#line 425 "parser.cc" // lalr1.cc:636 +#line 434 "parser.cc" // lalr1.cc:636 break; - case 55: // option_repr_type + case 56: // option_repr_type -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< TokenOption::RepresentationType > (); } -#line 432 "parser.cc" // lalr1.cc:636 +#line 441 "parser.cc" // lalr1.cc:636 break; - case 56: // nest_level + case 57: // nest_level -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< uint8_t > (); } -#line 439 "parser.cc" // lalr1.cc:636 +#line 448 "parser.cc" // lalr1.cc:636 break; - case 57: // pkt_metadata + case 58: // pkt_metadata -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< TokenPkt::MetadataType > (); } -#line 446 "parser.cc" // lalr1.cc:636 +#line 455 "parser.cc" // lalr1.cc:636 break; - case 58: // enterprise_id + case 59: // enterprise_id -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< uint32_t > (); } -#line 453 "parser.cc" // lalr1.cc:636 +#line 462 "parser.cc" // lalr1.cc:636 break; - case 59: // pkt4_field + case 60: // pkt4_field -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< TokenPkt4::FieldType > (); } -#line 460 "parser.cc" // lalr1.cc:636 +#line 469 "parser.cc" // lalr1.cc:636 break; - case 60: // pkt6_field + case 61: // pkt6_field -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< TokenPkt6::FieldType > (); } -#line 467 "parser.cc" // lalr1.cc:636 +#line 476 "parser.cc" // lalr1.cc:636 break; - case 61: // relay6_field + case 62: // relay6_field -#line 103 "parser.yy" // lalr1.cc:636 +#line 104 "parser.yy" // lalr1.cc:636 { yyoutput << yysym.value.template as< TokenRelay6Field::FieldType > (); } -#line 474 "parser.cc" // lalr1.cc:636 +#line 483 "parser.cc" // lalr1.cc:636 break; @@ -670,23 +679,23 @@ namespace isc { namespace eval { when using variants. */ switch (yyr1_[yyn]) { - case 55: // option_repr_type + case 56: // option_repr_type yylhs.value.build< TokenOption::RepresentationType > (); break; - case 59: // pkt4_field + case 60: // pkt4_field yylhs.value.build< TokenPkt4::FieldType > (); break; - case 60: // pkt6_field + case 61: // pkt6_field yylhs.value.build< TokenPkt6::FieldType > (); break; - case 57: // pkt_metadata + case 58: // pkt_metadata yylhs.value.build< TokenPkt::MetadataType > (); break; - case 61: // relay6_field + case 62: // relay6_field yylhs.value.build< TokenRelay6Field::FieldType > (); break; @@ -698,15 +707,16 @@ namespace isc { namespace eval { yylhs.value.build< std::string > (); break; - case 54: // option_code + case 55: // option_code yylhs.value.build< uint16_t > (); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id yylhs.value.build< uint32_t > (); break; - case 56: // nest_level + case 57: // nest_level yylhs.value.build< uint8_t > (); break; @@ -728,52 +738,52 @@ namespace isc { namespace eval { switch (yyn) { case 4: -#line 117 "parser.yy" // lalr1.cc:859 +#line 118 "parser.yy" // lalr1.cc:859 { TokenPtr neg(new TokenNot()); ctx.expression.push_back(neg); } -#line 737 "parser.cc" // lalr1.cc:859 +#line 747 "parser.cc" // lalr1.cc:859 break; case 5: -#line 122 "parser.yy" // lalr1.cc:859 +#line 123 "parser.yy" // lalr1.cc:859 { TokenPtr neg(new TokenAnd()); ctx.expression.push_back(neg); } -#line 746 "parser.cc" // lalr1.cc:859 +#line 756 "parser.cc" // lalr1.cc:859 break; case 6: -#line 127 "parser.yy" // lalr1.cc:859 +#line 128 "parser.yy" // lalr1.cc:859 { TokenPtr neg(new TokenOr()); ctx.expression.push_back(neg); } -#line 755 "parser.cc" // lalr1.cc:859 +#line 765 "parser.cc" // lalr1.cc:859 break; case 7: -#line 132 "parser.yy" // lalr1.cc:859 +#line 133 "parser.yy" // lalr1.cc:859 { TokenPtr eq(new TokenEqual()); ctx.expression.push_back(eq); } -#line 764 "parser.cc" // lalr1.cc:859 +#line 774 "parser.cc" // lalr1.cc:859 break; case 8: -#line 137 "parser.yy" // lalr1.cc:859 +#line 138 "parser.yy" // lalr1.cc:859 { TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS)); ctx.expression.push_back(opt); } -#line 773 "parser.cc" // lalr1.cc:859 +#line 783 "parser.cc" // lalr1.cc:859 break; case 9: -#line 142 "parser.yy" // lalr1.cc:859 +#line 143 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V4: @@ -793,11 +803,11 @@ namespace isc { namespace eval { error(yystack_[5].location, "relay4 can only be used in DHCPv4."); } } -#line 797 "parser.cc" // lalr1.cc:859 +#line 807 "parser.cc" // lalr1.cc:859 break; case 10: -#line 162 "parser.yy" // lalr1.cc:859 +#line 163 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V6: @@ -811,11 +821,11 @@ namespace isc { namespace eval { error(yystack_[10].location, "relay6 can only be used in DHCPv6."); } } -#line 815 "parser.cc" // lalr1.cc:859 +#line 825 "parser.cc" // lalr1.cc:859 break; case 11: -#line 176 "parser.yy" // lalr1.cc:859 +#line 177 "parser.yy" // lalr1.cc:859 { // Expression: vendor-class[1234].exists // @@ -824,11 +834,11 @@ namespace isc { namespace eval { TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), yystack_[3].value.as< uint32_t > (), TokenOption::EXISTS)); ctx.expression.push_back(exist); } -#line 828 "parser.cc" // lalr1.cc:859 +#line 838 "parser.cc" // lalr1.cc:859 break; case 12: -#line 185 "parser.yy" // lalr1.cc:859 +#line 186 "parser.yy" // lalr1.cc:859 { // Expression: vendor[1234].exists // @@ -837,11 +847,11 @@ namespace isc { namespace eval { TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[3].value.as< uint32_t > (), TokenOption::EXISTS)); ctx.expression.push_back(exist); } -#line 841 "parser.cc" // lalr1.cc:859 +#line 851 "parser.cc" // lalr1.cc:859 break; case 13: -#line 194 "parser.yy" // lalr1.cc:859 +#line 195 "parser.yy" // lalr1.cc:859 { // Expression vendor[1234].option[123].exists // @@ -851,47 +861,47 @@ namespace isc { namespace eval { TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as< uint32_t > (), TokenOption::EXISTS, yystack_[3].value.as< uint16_t > ())); ctx.expression.push_back(exist); } -#line 855 "parser.cc" // lalr1.cc:859 +#line 865 "parser.cc" // lalr1.cc:859 break; case 14: -#line 206 "parser.yy" // lalr1.cc:859 +#line 207 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 864 "parser.cc" // lalr1.cc:859 +#line 874 "parser.cc" // lalr1.cc:859 break; case 15: -#line 211 "parser.yy" // lalr1.cc:859 +#line 212 "parser.yy" // lalr1.cc:859 { TokenPtr hex(new TokenHexString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(hex); } -#line 873 "parser.cc" // lalr1.cc:859 +#line 883 "parser.cc" // lalr1.cc:859 break; case 16: -#line 216 "parser.yy" // lalr1.cc:859 +#line 217 "parser.yy" // lalr1.cc:859 { TokenPtr ip(new TokenIpAddress(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(ip); } -#line 882 "parser.cc" // lalr1.cc:859 +#line 892 "parser.cc" // lalr1.cc:859 break; case 17: -#line 221 "parser.yy" // lalr1.cc:859 +#line 222 "parser.yy" // lalr1.cc:859 { TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ())); ctx.expression.push_back(opt); } -#line 891 "parser.cc" // lalr1.cc:859 +#line 901 "parser.cc" // lalr1.cc:859 break; case 18: -#line 226 "parser.yy" // lalr1.cc:859 +#line 227 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V4: @@ -911,11 +921,11 @@ namespace isc { namespace eval { error(yystack_[5].location, "relay4 can only be used in DHCPv4."); } } -#line 915 "parser.cc" // lalr1.cc:859 +#line 925 "parser.cc" // lalr1.cc:859 break; case 19: -#line 247 "parser.yy" // lalr1.cc:859 +#line 248 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V6: @@ -929,20 +939,20 @@ namespace isc { namespace eval { error(yystack_[10].location, "relay6 can only be used in DHCPv6."); } } -#line 933 "parser.cc" // lalr1.cc:859 +#line 943 "parser.cc" // lalr1.cc:859 break; case 20: -#line 262 "parser.yy" // lalr1.cc:859 +#line 263 "parser.yy" // lalr1.cc:859 { TokenPtr pkt_metadata(new TokenPkt(yystack_[0].value.as< TokenPkt::MetadataType > ())); ctx.expression.push_back(pkt_metadata); } -#line 942 "parser.cc" // lalr1.cc:859 +#line 952 "parser.cc" // lalr1.cc:859 break; case 21: -#line 267 "parser.yy" // lalr1.cc:859 +#line 268 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V4: @@ -956,11 +966,11 @@ namespace isc { namespace eval { error(yystack_[2].location, "pkt4 can only be used in DHCPv4."); } } -#line 960 "parser.cc" // lalr1.cc:859 +#line 970 "parser.cc" // lalr1.cc:859 break; case 22: -#line 281 "parser.yy" // lalr1.cc:859 +#line 282 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V6: @@ -974,11 +984,11 @@ namespace isc { namespace eval { error(yystack_[2].location, "pkt6 can only be used in DHCPv6."); } } -#line 978 "parser.cc" // lalr1.cc:859 +#line 988 "parser.cc" // lalr1.cc:859 break; case 23: -#line 295 "parser.yy" // lalr1.cc:859 +#line 296 "parser.yy" // lalr1.cc:859 { switch (ctx.getUniverse()) { case Option::V6: @@ -992,29 +1002,29 @@ namespace isc { namespace eval { error(yystack_[5].location, "relay6 can only be used in DHCPv6."); } } -#line 996 "parser.cc" // lalr1.cc:859 +#line 1006 "parser.cc" // lalr1.cc:859 break; case 24: -#line 310 "parser.yy" // lalr1.cc:859 +#line 311 "parser.yy" // lalr1.cc:859 { TokenPtr sub(new TokenSubstring()); ctx.expression.push_back(sub); } -#line 1005 "parser.cc" // lalr1.cc:859 +#line 1015 "parser.cc" // lalr1.cc:859 break; case 25: -#line 315 "parser.yy" // lalr1.cc:859 +#line 316 "parser.yy" // lalr1.cc:859 { TokenPtr conc(new TokenConcat()); ctx.expression.push_back(conc); } -#line 1014 "parser.cc" // lalr1.cc:859 +#line 1024 "parser.cc" // lalr1.cc:859 break; case 26: -#line 320 "parser.yy" // lalr1.cc:859 +#line 321 "parser.yy" // lalr1.cc:859 { // expression: vendor.enterprise // @@ -1023,11 +1033,11 @@ namespace isc { namespace eval { TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID)); ctx.expression.push_back(vendor); } -#line 1027 "parser.cc" // lalr1.cc:859 +#line 1037 "parser.cc" // lalr1.cc:859 break; case 27: -#line 329 "parser.yy" // lalr1.cc:859 +#line 330 "parser.yy" // lalr1.cc:859 { // expression: vendor-class.enterprise // @@ -1037,11 +1047,11 @@ namespace isc { namespace eval { TokenVendor::ENTERPRISE_ID)); ctx.expression.push_back(vendor); } -#line 1041 "parser.cc" // lalr1.cc:859 +#line 1051 "parser.cc" // lalr1.cc:859 break; case 28: -#line 339 "parser.yy" // lalr1.cc:859 +#line 340 "parser.yy" // lalr1.cc:859 { // This token will search for vendor option with // specified enterprise-id. If found, will search @@ -1050,11 +1060,11 @@ namespace isc { namespace eval { TokenPtr opt(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as< uint32_t > (), yystack_[0].value.as< TokenOption::RepresentationType > (), yystack_[3].value.as< uint16_t > ())); ctx.expression.push_back(opt); } -#line 1054 "parser.cc" // lalr1.cc:859 +#line 1064 "parser.cc" // lalr1.cc:859 break; case 29: -#line 348 "parser.yy" // lalr1.cc:859 +#line 349 "parser.yy" // lalr1.cc:859 { // expression: vendor-class[1234].data // @@ -1067,11 +1077,11 @@ namespace isc { namespace eval { TokenVendor::DATA, 0)); ctx.expression.push_back(vendor_class); } -#line 1071 "parser.cc" // lalr1.cc:859 +#line 1081 "parser.cc" // lalr1.cc:859 break; case 30: -#line 361 "parser.yy" // lalr1.cc:859 +#line 362 "parser.yy" // lalr1.cc:859 { // expression: vendor-class[1234].data[5] // @@ -1084,214 +1094,247 @@ namespace isc { namespace eval { TokenVendor::DATA, index)); ctx.expression.push_back(vendor_class); } -#line 1088 "parser.cc" // lalr1.cc:859 +#line 1098 "parser.cc" // lalr1.cc:859 break; case 31: -#line 376 "parser.yy" // lalr1.cc:859 +#line 375 "parser.yy" // lalr1.cc:859 + { + TokenPtr integer(new TokenInteger(yystack_[0].value.as< uint32_t > ())); + ctx.expression.push_back(integer); + } +#line 1107 "parser.cc" // lalr1.cc:859 + break; + + case 32: +#line 382 "parser.yy" // lalr1.cc:859 + { + yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location); + } +#line 1115 "parser.cc" // lalr1.cc:859 + break; + + case 33: +#line 388 "parser.yy" // lalr1.cc:859 { yylhs.value.as< uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as< std::string > (), yystack_[0].location); } -#line 1096 "parser.cc" // lalr1.cc:859 +#line 1123 "parser.cc" // lalr1.cc:859 break; - case 32: -#line 380 "parser.yy" // lalr1.cc:859 + case 34: +#line 392 "parser.yy" // lalr1.cc:859 { yylhs.value.as< uint16_t > () = ctx.convertOptionName(yystack_[0].value.as< std::string > (), yystack_[0].location); } -#line 1104 "parser.cc" // lalr1.cc:859 +#line 1131 "parser.cc" // lalr1.cc:859 break; - case 33: -#line 386 "parser.yy" // lalr1.cc:859 + case 35: +#line 398 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::TEXTUAL; } -#line 1112 "parser.cc" // lalr1.cc:859 +#line 1139 "parser.cc" // lalr1.cc:859 break; - case 34: -#line 390 "parser.yy" // lalr1.cc:859 + case 36: +#line 402 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL; } -#line 1120 "parser.cc" // lalr1.cc:859 +#line 1147 "parser.cc" // lalr1.cc:859 break; - case 35: -#line 396 "parser.yy" // lalr1.cc:859 + case 37: +#line 408 "parser.yy" // lalr1.cc:859 { yylhs.value.as< uint8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as< std::string > (), yystack_[0].location); } -#line 1128 "parser.cc" // lalr1.cc:859 +#line 1155 "parser.cc" // lalr1.cc:859 break; - case 36: -#line 405 "parser.yy" // lalr1.cc:859 + case 38: +#line 417 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::IFACE; } -#line 1136 "parser.cc" // lalr1.cc:859 +#line 1163 "parser.cc" // lalr1.cc:859 break; - case 37: -#line 409 "parser.yy" // lalr1.cc:859 + case 39: +#line 421 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::SRC; } -#line 1144 "parser.cc" // lalr1.cc:859 +#line 1171 "parser.cc" // lalr1.cc:859 break; - case 38: -#line 413 "parser.yy" // lalr1.cc:859 + case 40: +#line 425 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::DST; } -#line 1152 "parser.cc" // lalr1.cc:859 +#line 1179 "parser.cc" // lalr1.cc:859 break; - case 39: -#line 417 "parser.yy" // lalr1.cc:859 + case 41: +#line 429 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::LEN; } -#line 1160 "parser.cc" // lalr1.cc:859 +#line 1187 "parser.cc" // lalr1.cc:859 break; - case 40: -#line 423 "parser.yy" // lalr1.cc:859 + case 42: +#line 435 "parser.yy" // lalr1.cc:859 { yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location); } -#line 1168 "parser.cc" // lalr1.cc:859 +#line 1195 "parser.cc" // lalr1.cc:859 break; - case 41: -#line 427 "parser.yy" // lalr1.cc:859 + case 43: +#line 439 "parser.yy" // lalr1.cc:859 { yylhs.value.as< uint32_t > () = 0; } -#line 1176 "parser.cc" // lalr1.cc:859 +#line 1203 "parser.cc" // lalr1.cc:859 break; - case 42: -#line 433 "parser.yy" // lalr1.cc:859 + case 44: +#line 445 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CHADDR; } -#line 1184 "parser.cc" // lalr1.cc:859 +#line 1211 "parser.cc" // lalr1.cc:859 break; - case 43: -#line 437 "parser.yy" // lalr1.cc:859 + case 45: +#line 449 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HLEN; } -#line 1192 "parser.cc" // lalr1.cc:859 +#line 1219 "parser.cc" // lalr1.cc:859 break; - case 44: -#line 441 "parser.yy" // lalr1.cc:859 + case 46: +#line 453 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HTYPE; } -#line 1200 "parser.cc" // lalr1.cc:859 +#line 1227 "parser.cc" // lalr1.cc:859 break; - case 45: -#line 445 "parser.yy" // lalr1.cc:859 + case 47: +#line 457 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CIADDR; } -#line 1208 "parser.cc" // lalr1.cc:859 +#line 1235 "parser.cc" // lalr1.cc:859 break; - case 46: -#line 449 "parser.yy" // lalr1.cc:859 + case 48: +#line 461 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::GIADDR; } -#line 1216 "parser.cc" // lalr1.cc:859 +#line 1243 "parser.cc" // lalr1.cc:859 break; - case 47: -#line 453 "parser.yy" // lalr1.cc:859 + case 49: +#line 465 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::YIADDR; } -#line 1224 "parser.cc" // lalr1.cc:859 +#line 1251 "parser.cc" // lalr1.cc:859 break; - case 48: -#line 457 "parser.yy" // lalr1.cc:859 + case 50: +#line 469 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::SIADDR; } -#line 1232 "parser.cc" // lalr1.cc:859 +#line 1259 "parser.cc" // lalr1.cc:859 break; - case 49: -#line 463 "parser.yy" // lalr1.cc:859 + case 51: +#line 473 "parser.yy" // lalr1.cc:859 + { + yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE; + } +#line 1267 "parser.cc" // lalr1.cc:859 + break; + + case 52: +#line 477 "parser.yy" // lalr1.cc:859 + { + yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::TRANSID; + } +#line 1275 "parser.cc" // lalr1.cc:859 + break; + + case 53: +#line 483 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE; } -#line 1240 "parser.cc" // lalr1.cc:859 +#line 1283 "parser.cc" // lalr1.cc:859 break; - case 50: -#line 467 "parser.yy" // lalr1.cc:859 + case 54: +#line 487 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::TRANSID; } -#line 1248 "parser.cc" // lalr1.cc:859 +#line 1291 "parser.cc" // lalr1.cc:859 break; - case 51: -#line 473 "parser.yy" // lalr1.cc:859 + case 55: +#line 493 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR; } -#line 1256 "parser.cc" // lalr1.cc:859 +#line 1299 "parser.cc" // lalr1.cc:859 break; - case 52: -#line 477 "parser.yy" // lalr1.cc:859 + case 56: +#line 497 "parser.yy" // lalr1.cc:859 { yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR; } -#line 1264 "parser.cc" // lalr1.cc:859 +#line 1307 "parser.cc" // lalr1.cc:859 break; - case 53: -#line 483 "parser.yy" // lalr1.cc:859 + case 57: +#line 503 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 1273 "parser.cc" // lalr1.cc:859 +#line 1316 "parser.cc" // lalr1.cc:859 break; - case 54: -#line 490 "parser.yy" // lalr1.cc:859 + case 58: +#line 510 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ())); ctx.expression.push_back(str); } -#line 1282 "parser.cc" // lalr1.cc:859 +#line 1325 "parser.cc" // lalr1.cc:859 break; - case 55: -#line 495 "parser.yy" // lalr1.cc:859 + case 59: +#line 515 "parser.yy" // lalr1.cc:859 { TokenPtr str(new TokenString("all")); ctx.expression.push_back(str); } -#line 1291 "parser.cc" // lalr1.cc:859 +#line 1334 "parser.cc" // lalr1.cc:859 break; -#line 1295 "parser.cc" // lalr1.cc:859 +#line 1338 "parser.cc" // lalr1.cc:859 default: break; } @@ -1546,131 +1589,131 @@ namespace isc { namespace eval { } - const signed char EvalParser::yypact_ninf_ = -89; + const signed char EvalParser::yypact_ninf_ = -91; const signed char EvalParser::yytable_ninf_ = -1; const short int EvalParser::yypact_[] = { - 4, 4, 4, 11, 18, 46, 69, 70, 93, 94, - 85, -6, 36, -89, -89, -89, 105, 32, 100, 77, - -89, 58, 58, 71, 67, 48, 62, 62, 26, -11, - 72, -11, 76, -89, 4, 4, 62, -89, -89, -89, - 106, 107, -89, 108, -89, -89, -89, -89, -89, -89, - -89, -89, -89, -89, -89, -89, -89, 110, 111, 112, - 98, 99, 92, 95, -89, -89, -89, -89, -89, 113, - -89, 114, -89, -89, 125, -89, 116, 117, 118, 58, - 58, 71, -11, -11, 89, 62, 120, 121, 1, 29, - 14, 123, 124, 126, 127, 128, -89, 109, 136, -15, - -3, -89, -89, -89, -89, -89, -89, 131, -89, -89, - -89, 130, 132, 133, 134, 135, -29, -89, -89, 138, - 139, -89, 58, 25, 25, 21, 104, 145, -89, -89, - 151, 115, 58, 141, 143, 144, -89, 147, 148, 149, - 58, 58, -89, 150, 51, 152, 153, 75, -89, -89, - 154, 155, -89, -89, 25, 25 + 3, 3, 3, -5, 13, 23, 4, 14, 54, 80, + 68, 37, 95, -91, -91, -91, -91, 93, 35, 98, + -91, 73, -91, 67, 67, 64, 50, 69, 45, 45, + 84, -20, 74, -20, 81, -91, 3, 3, 45, -91, + -91, -91, 109, 111, -91, 112, -91, -91, -91, -91, + -91, -91, -91, -91, -91, -91, -91, -91, -91, -91, + -91, 114, 115, 116, 100, 103, 96, 97, -91, -91, + -91, -91, -91, 118, -91, 119, -91, -91, 129, -91, + 120, 121, 122, 67, 67, 64, -20, -20, 94, 45, + 123, 125, 16, 28, 6, 127, 128, 130, 131, 132, + -91, 113, 140, -14, -2, -91, -91, -91, -91, -91, + -91, 135, -91, -91, -91, 134, 136, 137, 138, 139, + -30, -91, -91, 142, 143, -91, 67, 58, 58, 12, + 108, 149, -91, -91, 155, 117, 67, 145, 147, 148, + -91, 150, 151, 152, 67, 67, -91, 153, 70, 156, + 157, 86, -91, -91, 154, 158, -91, -91, 58, 58 }; const unsigned char EvalParser::yydefact_[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 14, 15, 16, 0, 2, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 3, 31, 32, - 0, 0, 35, 0, 36, 37, 38, 39, 20, 42, - 43, 44, 45, 46, 47, 48, 21, 0, 0, 0, - 0, 0, 0, 0, 49, 50, 22, 41, 40, 0, - 27, 0, 26, 5, 6, 7, 0, 0, 0, 0, + 0, 0, 0, 14, 32, 15, 16, 0, 2, 0, + 31, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, + 33, 34, 0, 0, 37, 0, 38, 39, 40, 41, + 20, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 21, 0, 0, 0, 0, 0, 0, 0, 53, 54, + 22, 43, 42, 0, 27, 0, 26, 5, 6, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 53, 0, 0, 0, - 0, 33, 34, 8, 17, 9, 18, 0, 51, 52, - 23, 0, 0, 0, 0, 0, 0, 25, 11, 29, - 0, 12, 0, 0, 0, 0, 0, 0, 55, 54, - 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, - 0, 0, 30, 0, 0, 0, 0, 0, 10, 19, - 0, 0, 13, 28, 0, 0 + 57, 0, 0, 0, 0, 35, 36, 8, 17, 9, + 18, 0, 55, 56, 23, 0, 0, 0, 0, 0, + 0, 25, 11, 29, 0, 12, 0, 0, 0, 0, + 0, 0, 59, 58, 0, 0, 0, 0, 0, 0, + 24, 0, 0, 0, 0, 0, 30, 0, 0, 0, + 0, 0, 10, 19, 0, 0, 13, 28, 0, 0 }; const signed char EvalParser::yypgoto_[] = { - -89, -89, 20, -24, -22, -88, 78, -89, -20, -89, - -89, -89, -89, -89 + -91, -91, 30, -27, -91, -24, -90, 79, -91, -23, + -91, -91, -91, -91, -91 }; const short int EvalParser::yydefgoto_[] = { - -1, 16, 17, 18, 40, 104, 43, 48, 69, 56, - 66, 110, 97, 130 + -1, 17, 18, 19, 20, 42, 108, 45, 50, 73, + 60, 70, 114, 101, 134 }; const unsigned char EvalParser::yytable_[] = { - 41, 106, 62, 63, 118, 128, 120, 1, 29, 2, - 30, 71, 75, 3, 4, 5, 121, 129, 101, 102, - 103, 19, 20, 107, 6, 21, 108, 109, 119, 7, - 134, 67, 22, 108, 109, 68, 106, 8, 34, 35, - 9, 10, 101, 102, 11, 12, 101, 102, 105, 13, - 31, 14, 32, 15, 73, 74, 149, 91, 92, 153, - 23, 98, 94, 95, 64, 65, 149, 153, 101, 102, - 148, 57, 58, 59, 49, 50, 51, 52, 53, 54, - 55, 37, 6, 34, 35, 24, 25, 7, 44, 45, - 46, 47, 101, 102, 152, 8, 26, 27, 9, 10, - 133, 28, 60, 61, 38, 33, 39, 13, 36, 14, - 138, 15, 82, 83, 30, 32, 70, 42, 145, 146, - 72, 76, 77, 78, 79, 80, 81, 84, 86, 87, - 85, 34, 88, 89, 90, 96, 99, 100, 111, 112, - 117, 113, 114, 115, 116, 122, 123, 119, 124, 125, - 126, 127, 131, 132, 135, 136, 139, 140, 141, 93, - 0, 137, 142, 143, 0, 144, 147, 150, 151, 0, - 154, 155 + 43, 66, 67, 110, 132, 122, 1, 124, 2, 23, + 75, 79, 3, 4, 5, 111, 133, 125, 112, 113, + 26, 138, 71, 6, 112, 113, 72, 24, 7, 123, + 27, 21, 22, 105, 106, 107, 8, 25, 110, 9, + 10, 36, 37, 11, 12, 105, 106, 109, 13, 14, + 15, 31, 16, 32, 61, 62, 63, 28, 153, 95, + 96, 157, 102, 98, 99, 6, 77, 78, 153, 157, + 7, 46, 47, 48, 49, 105, 106, 39, 8, 36, + 37, 9, 10, 29, 30, 64, 65, 105, 106, 152, + 13, 14, 15, 35, 16, 51, 52, 53, 54, 55, + 56, 57, 137, 105, 106, 156, 38, 58, 59, 33, + 44, 34, 142, 40, 86, 41, 32, 87, 74, 34, + 149, 150, 68, 69, 80, 76, 81, 82, 83, 84, + 85, 88, 89, 90, 91, 36, 92, 93, 94, 103, + 100, 104, 115, 116, 121, 117, 118, 119, 120, 126, + 127, 123, 128, 129, 130, 131, 135, 136, 139, 140, + 143, 144, 145, 141, 97, 146, 147, 0, 148, 151, + 158, 154, 155, 0, 159 }; const short int EvalParser::yycheck_[] = { - 22, 89, 26, 27, 19, 34, 9, 3, 14, 5, - 16, 31, 36, 9, 10, 11, 19, 46, 17, 18, - 19, 1, 2, 9, 20, 14, 12, 13, 43, 25, - 9, 42, 14, 12, 13, 46, 124, 33, 6, 7, - 36, 37, 17, 18, 40, 41, 17, 18, 19, 45, - 14, 47, 16, 49, 34, 35, 144, 79, 80, 147, - 14, 85, 82, 83, 38, 39, 154, 155, 17, 18, - 19, 9, 10, 11, 26, 27, 28, 29, 30, 31, - 32, 4, 20, 6, 7, 16, 16, 25, 21, 22, - 23, 24, 17, 18, 19, 33, 3, 3, 36, 37, - 122, 16, 40, 41, 46, 0, 48, 45, 8, 47, - 132, 49, 14, 14, 16, 16, 44, 46, 140, 141, - 44, 15, 15, 15, 14, 14, 14, 35, 15, 15, - 35, 6, 16, 16, 16, 46, 16, 16, 15, 15, - 4, 15, 15, 15, 35, 14, 16, 43, 16, 16, - 16, 16, 14, 14, 9, 4, 15, 14, 14, 81, - -1, 46, 15, 15, -1, 16, 16, 15, 15, -1, - 16, 16 + 24, 28, 29, 93, 34, 19, 3, 9, 5, 14, + 33, 38, 9, 10, 11, 9, 46, 19, 12, 13, + 16, 9, 42, 20, 12, 13, 46, 14, 25, 43, + 16, 1, 2, 17, 18, 19, 33, 14, 128, 36, + 37, 6, 7, 40, 41, 17, 18, 19, 45, 46, + 47, 14, 49, 16, 9, 10, 11, 3, 148, 83, + 84, 151, 89, 86, 87, 20, 36, 37, 158, 159, + 25, 21, 22, 23, 24, 17, 18, 4, 33, 6, + 7, 36, 37, 3, 16, 40, 41, 17, 18, 19, + 45, 46, 47, 0, 49, 26, 27, 28, 29, 30, + 31, 32, 126, 17, 18, 19, 8, 38, 39, 14, + 46, 16, 136, 46, 14, 48, 16, 14, 44, 16, + 144, 145, 38, 39, 15, 44, 15, 15, 14, 14, + 14, 35, 35, 15, 15, 6, 16, 16, 16, 16, + 46, 16, 15, 15, 4, 15, 15, 15, 35, 14, + 16, 43, 16, 16, 16, 16, 14, 14, 9, 4, + 15, 14, 14, 46, 85, 15, 15, -1, 16, 16, + 16, 15, 15, -1, 16 }; const unsigned char EvalParser::yystos_[] = { 0, 3, 5, 9, 10, 11, 20, 25, 33, 36, - 37, 40, 41, 45, 47, 49, 51, 52, 53, 52, - 52, 14, 14, 14, 16, 16, 3, 3, 16, 14, - 16, 14, 16, 0, 6, 7, 8, 4, 46, 48, - 54, 54, 46, 56, 21, 22, 23, 24, 57, 26, - 27, 28, 29, 30, 31, 32, 59, 9, 10, 11, - 40, 41, 53, 53, 38, 39, 60, 42, 46, 58, - 44, 58, 44, 52, 52, 53, 15, 15, 15, 14, - 14, 14, 14, 14, 35, 35, 15, 15, 16, 16, - 16, 54, 54, 56, 58, 58, 46, 62, 53, 16, - 16, 17, 18, 19, 55, 19, 55, 9, 12, 13, - 61, 15, 15, 15, 15, 15, 35, 4, 19, 43, - 9, 19, 14, 16, 16, 16, 16, 16, 34, 46, - 63, 14, 14, 54, 9, 9, 4, 46, 54, 15, - 14, 14, 15, 15, 16, 54, 54, 16, 19, 55, - 15, 15, 19, 55, 16, 16 + 37, 40, 41, 45, 46, 47, 49, 51, 52, 53, + 54, 52, 52, 14, 14, 14, 16, 16, 3, 3, + 16, 14, 16, 14, 16, 0, 6, 7, 8, 4, + 46, 48, 55, 55, 46, 57, 21, 22, 23, 24, + 58, 26, 27, 28, 29, 30, 31, 32, 38, 39, + 60, 9, 10, 11, 40, 41, 53, 53, 38, 39, + 61, 42, 46, 59, 44, 59, 44, 52, 52, 53, + 15, 15, 15, 14, 14, 14, 14, 14, 35, 35, + 15, 15, 16, 16, 16, 55, 55, 57, 59, 59, + 46, 63, 53, 16, 16, 17, 18, 19, 56, 19, + 56, 9, 12, 13, 62, 15, 15, 15, 15, 15, + 35, 4, 19, 43, 9, 19, 14, 16, 16, 16, + 16, 16, 34, 46, 64, 14, 14, 55, 9, 9, + 4, 46, 55, 15, 14, 14, 15, 15, 16, 55, + 55, 16, 19, 56, 15, 15, 19, 56, 16, 16 }; const unsigned char @@ -1679,9 +1722,9 @@ namespace isc { namespace eval { 0, 50, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 54, 54, 55, 55, 56, 57, 57, 57, 57, - 58, 58, 59, 59, 59, 59, 59, 59, 59, 60, - 60, 61, 61, 62, 63, 63 + 53, 53, 54, 55, 55, 56, 56, 57, 58, 58, + 58, 58, 59, 59, 60, 60, 60, 60, 60, 60, + 60, 60, 60, 61, 61, 62, 62, 63, 64, 64 }; const unsigned char @@ -1692,7 +1735,7 @@ namespace isc { namespace eval { 3, 3, 3, 6, 8, 6, 3, 3, 11, 6, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; @@ -1712,21 +1755,21 @@ namespace isc { namespace eval { "\"vendor-class\"", "\"vendor\"", "\"*\"", "\"data\"", "\"enterprise\"", "\"constant string\"", "\"integer\"", "\"constant hexstring\"", "\"option name\"", "\"ip address\"", "$accept", "expression", - "bool_expr", "string_expr", "option_code", "option_repr_type", - "nest_level", "pkt_metadata", "enterprise_id", "pkt4_field", - "pkt6_field", "relay6_field", "start_expr", "length_expr", YY_NULLPTR + "bool_expr", "string_expr", "integer_expr", "option_code", + "option_repr_type", "nest_level", "pkt_metadata", "enterprise_id", + "pkt4_field", "pkt6_field", "relay6_field", "start_expr", "length_expr", YY_NULLPTR }; #if YYDEBUG const unsigned short int EvalParser::yyrline_[] = { - 0, 112, 112, 115, 116, 121, 126, 131, 136, 141, - 161, 175, 184, 193, 205, 210, 215, 220, 225, 246, - 261, 266, 280, 294, 309, 314, 319, 328, 338, 347, - 360, 375, 379, 385, 389, 395, 404, 408, 412, 416, - 422, 426, 432, 436, 440, 444, 448, 452, 456, 462, - 466, 472, 476, 482, 489, 494 + 0, 113, 113, 116, 117, 122, 127, 132, 137, 142, + 162, 176, 185, 194, 206, 211, 216, 221, 226, 247, + 262, 267, 281, 295, 310, 315, 320, 329, 339, 348, + 361, 374, 381, 387, 391, 397, 401, 407, 416, 420, + 424, 428, 434, 438, 444, 448, 452, 456, 460, 464, + 468, 472, 476, 482, 486, 492, 496, 502, 509, 514 }; // Print the state stack on the debug stream. @@ -1761,8 +1804,8 @@ namespace isc { namespace eval { #line 13 "parser.yy" // lalr1.cc:1167 } } // isc::eval -#line 1765 "parser.cc" // lalr1.cc:1167 -#line 501 "parser.yy" // lalr1.cc:1168 +#line 1808 "parser.cc" // lalr1.cc:1167 +#line 521 "parser.yy" // lalr1.cc:1168 void isc::eval::EvalParser::error(const location_type& loc, diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h index f61db8fff2..be22231f99 100644 --- a/src/lib/eval/parser.h +++ b/src/lib/eval/parser.h @@ -320,6 +320,7 @@ namespace isc { namespace eval { // option_code char dummy7[sizeof(uint16_t)]; + // integer_expr // enterprise_id char dummy8[sizeof(uint32_t)]; @@ -914,9 +915,9 @@ namespace isc { namespace eval { enum { yyeof_ = 0, - yylast_ = 171, ///< Last index in yytable_. - yynnts_ = 14, ///< Number of nonterminal symbols. - yyfinal_ = 33, ///< Termination state number. + yylast_ = 174, ///< Last index in yytable_. + yynnts_ = 15, ///< Number of nonterminal symbols. + yyfinal_ = 35, ///< Termination state number. yyterror_ = 1, yyerrcode_ = 256, yyntokens_ = 50 ///< Number of tokens. @@ -1001,23 +1002,23 @@ namespace isc { namespace eval { { switch (other.type_get ()) { - case 55: // option_repr_type + case 56: // option_repr_type value.copy< TokenOption::RepresentationType > (other.value); break; - case 59: // pkt4_field + case 60: // pkt4_field value.copy< TokenPkt4::FieldType > (other.value); break; - case 60: // pkt6_field + case 61: // pkt6_field value.copy< TokenPkt6::FieldType > (other.value); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.copy< TokenPkt::MetadataType > (other.value); break; - case 61: // relay6_field + case 62: // relay6_field value.copy< TokenRelay6Field::FieldType > (other.value); break; @@ -1029,15 +1030,16 @@ namespace isc { namespace eval { value.copy< std::string > (other.value); break; - case 54: // option_code + case 55: // option_code value.copy< uint16_t > (other.value); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.copy< uint32_t > (other.value); break; - case 56: // nest_level + case 57: // nest_level value.copy< uint8_t > (other.value); break; @@ -1058,23 +1060,23 @@ namespace isc { namespace eval { (void) v; switch (this->type_get ()) { - case 55: // option_repr_type + case 56: // option_repr_type value.copy< TokenOption::RepresentationType > (v); break; - case 59: // pkt4_field + case 60: // pkt4_field value.copy< TokenPkt4::FieldType > (v); break; - case 60: // pkt6_field + case 61: // pkt6_field value.copy< TokenPkt6::FieldType > (v); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.copy< TokenPkt::MetadataType > (v); break; - case 61: // relay6_field + case 62: // relay6_field value.copy< TokenRelay6Field::FieldType > (v); break; @@ -1086,15 +1088,16 @@ namespace isc { namespace eval { value.copy< std::string > (v); break; - case 54: // option_code + case 55: // option_code value.copy< uint16_t > (v); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.copy< uint32_t > (v); break; - case 56: // nest_level + case 57: // nest_level value.copy< uint8_t > (v); break; @@ -1202,23 +1205,23 @@ namespace isc { namespace eval { // Type destructor. switch (yytype) { - case 55: // option_repr_type + case 56: // option_repr_type value.template destroy< TokenOption::RepresentationType > (); break; - case 59: // pkt4_field + case 60: // pkt4_field value.template destroy< TokenPkt4::FieldType > (); break; - case 60: // pkt6_field + case 61: // pkt6_field value.template destroy< TokenPkt6::FieldType > (); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.template destroy< TokenPkt::MetadataType > (); break; - case 61: // relay6_field + case 62: // relay6_field value.template destroy< TokenRelay6Field::FieldType > (); break; @@ -1230,15 +1233,16 @@ namespace isc { namespace eval { value.template destroy< std::string > (); break; - case 54: // option_code + case 55: // option_code value.template destroy< uint16_t > (); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.template destroy< uint32_t > (); break; - case 56: // nest_level + case 57: // nest_level value.template destroy< uint8_t > (); break; @@ -1265,23 +1269,23 @@ namespace isc { namespace eval { super_type::move(s); switch (this->type_get ()) { - case 55: // option_repr_type + case 56: // option_repr_type value.move< TokenOption::RepresentationType > (s.value); break; - case 59: // pkt4_field + case 60: // pkt4_field value.move< TokenPkt4::FieldType > (s.value); break; - case 60: // pkt6_field + case 61: // pkt6_field value.move< TokenPkt6::FieldType > (s.value); break; - case 57: // pkt_metadata + case 58: // pkt_metadata value.move< TokenPkt::MetadataType > (s.value); break; - case 61: // relay6_field + case 62: // relay6_field value.move< TokenRelay6Field::FieldType > (s.value); break; @@ -1293,15 +1297,16 @@ namespace isc { namespace eval { value.move< std::string > (s.value); break; - case 54: // option_code + case 55: // option_code value.move< uint16_t > (s.value); break; - case 58: // enterprise_id + case 54: // integer_expr + case 59: // enterprise_id value.move< uint32_t > (s.value); break; - case 56: // nest_level + case 57: // nest_level value.move< uint8_t > (s.value); break; @@ -1660,7 +1665,7 @@ namespace isc { namespace eval { #line 13 "parser.yy" // lalr1.cc:377 } } // isc::eval -#line 1664 "parser.h" // lalr1.cc:377 +#line 1669 "parser.h" // lalr1.cc:377 diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy index 62a6bd270c..5bfadcfed0 100644 --- a/src/lib/eval/parser.yy +++ b/src/lib/eval/parser.yy @@ -89,6 +89,7 @@ using namespace isc::eval; %type <uint16_t> option_code %type <uint32_t> enterprise_id +%type <uint32_t> integer_expr %type <TokenOption::RepresentationType> option_repr_type %type <TokenRelay6Field::FieldType> relay6_field %type <uint8_t> nest_level @@ -370,8 +371,19 @@ string_expr : STRING TokenVendor::DATA, index)); ctx.expression.push_back(vendor_class); } + | integer_expr + { + TokenPtr integer(new TokenInteger($1)); + ctx.expression.push_back(integer); + } ; +integer_expr : INTEGER + { + $$ = ctx.convertUint32($1, @1); + } + ; + option_code : INTEGER { $$ = ctx.convertOptionCode($1, @1); @@ -457,6 +469,14 @@ pkt4_field : CHADDR { $$ = TokenPkt4::SIADDR; } + | MSGTYPE + { + $$ = TokenPkt4::MSGTYPE; + } + | TRANSID + { + $$ = TokenPkt4::TRANSID; + } ; pkt6_field : MSGTYPE diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc index d8811d1ffe..25b02b47f7 100644 --- a/src/lib/eval/tests/context_unittest.cc +++ b/src/lib/eval/tests/context_unittest.cc @@ -96,6 +96,18 @@ public: EXPECT_TRUE(eq); } + /// @brief Checks if the given token is integer with expected value + /// + /// @param token token to be inspected + /// @param exp_value expected integer value of the token + void checkTokenInteger(const TokenPtr& token, uint32_t exp_value) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenInteger> integer = + boost::dynamic_pointer_cast<TokenInteger>(token); + ASSERT_TRUE(integer); + EXPECT_EQ(exp_value, integer->getInteger()); + } + /// @brief checks if the given token is an option with the expected code /// and representation type /// @param token token to be checked @@ -929,7 +941,7 @@ TEST_F(EvalContextTest, relay6OptionLimits) { checkError("relay6[32].option[123].text == 'foo'", "<string>:1.8-9: Nest level has invalid value in 32. " "Allowed range: 0..31"); - + // next level must be a positive number checkError("relay6[-1].option[123].text == 'foo'", "<string>:1.8-9: Invalid value in -1. Allowed range: 0..255"); @@ -1003,12 +1015,12 @@ TEST_F(EvalContextTest, pkt4FieldSiaddr) { // Tests whether message type field in DHCPv6 can be accessed. TEST_F(EvalContextTest, pkt6FieldMsgtype) { - testPkt6Field("pkt6.msgtype == '1'", TokenPkt6::MSGTYPE, 3); + testPkt6Field("pkt6.msgtype == 1", TokenPkt6::MSGTYPE, 3); } // Tests whether transaction id field in DHCPv6 can be accessed. TEST_F(EvalContextTest, pkt6FieldTransid) { - testPkt6Field("pkt6.transid == '1'", TokenPkt6::TRANSID, 3); + testPkt6Field("pkt6.transid == 1", TokenPkt6::TRANSID, 3); } // Tests if the linkaddr field in a Relay6 encapsulation can be accessed. @@ -1200,10 +1212,17 @@ TEST_F(EvalContextTest, scanErrors) { TEST_F(EvalContextTest, scanParseErrors) { checkError("", "<string>:1.1: syntax error, unexpected end of file"); checkError(" ", "<string>:1.2: syntax error, unexpected end of file"); - checkError("0x", "<string>:1.1: syntax error, unexpected integer"); + checkError("0x", "<string>:1.2: Invalid character: x"); checkError("0abc", - "<string>:1.1: syntax error, unexpected integer"); - checkError("10.0.1", "<string>:1.1-2: syntax error, unexpected integer"); + "<string>:1.2: Invalid character: a"); + + // This one is a little bid odd. This is a truncated address, so it's not + // recognized as an address. Instead, the first token (10) is recognized as + // an integer. The only thing we can do with integers right now is test + // for equality, so the only possible next token is ==. There's a dot + // instead, so an error is reported. + checkError("10.0.1", "<string>:1.3: syntax error, unexpected ., expecting =="); + checkError("10.256.0.1", "<string>:1.1-10: Failed to convert 10.256.0.1 to " "an IP address."); @@ -1332,10 +1351,13 @@ TEST_F(EvalContextTest, typeErrors) { checkError("substring('foobar',0x32,1) == 'foo'", "<string>:1.20-23: syntax error, unexpected constant " "hexstring, expecting integer"); - checkError("concat('foo',3) == 'foo3'", - "<string>:1.14: syntax error, unexpected integer"); - checkError("concat(3,'foo') == '3foo'", - "<string>:1.8: syntax error, unexpected integer"); + + // With the #4483 addition, all integers are treated as 4 byte strings, + // so those checks no longer makes sense. Commeting it out. + // checkError("concat('foo',3) == 'foo3'", + // "<string>:1.14: syntax error, unexpected integer"); + // checkError("concat(3,'foo') == '3foo'", + // "<string>:1.8: syntax error, unexpected integer"); checkError("('foo' == 'bar') == 'false'", "<string>:1.18-19: syntax error, unexpected ==, " "expecting end of file"); @@ -1439,4 +1461,23 @@ TEST_F(EvalContextTest, vendorClass6DataIndex) { testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V6, 4491, 3); } +// Checks if integer expressions can be parsed and checked for equality. +TEST_F(EvalContextTest, integer1) { + + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("1 == 2")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + ASSERT_TRUE(tmp); + checkTokenInteger(tmp, 1); + tmp = eval.expression.at(1); + + ASSERT_TRUE(tmp); + checkTokenInteger(tmp, 2); +} + }; diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc index ce5ed05c40..64950c9704 100644 --- a/src/lib/eval/tests/evaluate_unittest.cc +++ b/src/lib/eval/tests/evaluate_unittest.cc @@ -6,6 +6,7 @@ #include <config.h> #include <eval/evaluate.h> +#include <eval/eval_context.h> #include <eval/token.h> #include <dhcp/pkt4.h> #include <dhcp/pkt6.h> @@ -282,4 +283,161 @@ TEST_F(EvaluateTest, complex) { EXPECT_TRUE(result_); } +/// @brief Generic class for parsing expressions and evaluating them. +/// +/// The main purpose of this class is to provide a generic interface to the +/// eval library, so everything (expression parsing and then evaluation for +/// given packets) can be done in one simple call. +/// +/// These tests may be somewhat redundant to other more specialized tests, but +/// the idea here is to mass produce tests that are trivial to write. +class ExpressionsTest : public EvaluateTest { +public: + + /// @brief Checks if expression can be parsed and evaluated + /// + /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you + /// tweak them as needed before calling this method. + /// + /// @param u universe (V4 or V6) + /// @param expr expression to be parsed + /// @param exp_result expected result (true or false) + void testExpression(const Option::Universe& u, const std::string& expr, + const bool exp_result) { + + EvalContext eval(u); + bool result = false; + bool parsed = false; + + EXPECT_NO_THROW(parsed = eval.parseString(expr)) + << " while parsing expression " << expr; + EXPECT_TRUE(parsed) << " for expression " << expr; + + switch (u) { + case Option::V4: + ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt4_)) + << " for expression " << expr; + break; + case Option::V6: + ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt6_)) + << " for expression " << expr; + break; + } + + EXPECT_EQ(exp_result, result) << " for expression " << expr; + } + + /// @brief Checks that specified expression throws expected exception. + /// + /// @tparam ex exception type expected to be thrown + /// @param expr expression to be evaluated + template<typename ex> + void testExpressionNegative(const std::string& expr, + const Option::Universe& u = Option::V4) { + EvalContext eval(u); + + EXPECT_THROW(eval.parseString(expr), ex) << "while parsing expression " + << expr; + } +}; + +// This is a quick way to check if certain expressions are valid or not and +// whether the whole expression makes sense. This particular test checks if +// integers can be used properly in expressions. There are many places where +// integers are used. This particular test checks if pkt6.msgtype returns +// something that can be compared with integers. +// +// For basic things we can take advantage of the skeleton packets created in +// EvaluateTest constructors: The packet type is DISCOVER in DHCPv4 and +// SOLICIT in DHCPv6. There is one option added with code 100 and content +// being either "hundred4" or "hundred6" depending on the universe. + +// Tests if pkt6.msgtype returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger1) { + testExpression(Option::V6, "pkt6.msgtype == 1", true); + testExpression(Option::V6, "pkt6.msgtype == 2", false); + + testExpression(Option::V6, "pkt6.msgtype == 0x00000001", true); + testExpression(Option::V6, "pkt6.msgtype == 0x00000002", false); +} + +// Tests if pkt6.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger2) { + testExpression(Option::V6, "pkt6.transid == 0", false); + testExpression(Option::V6, "pkt6.transid == 12345", true); + testExpression(Option::V6, "pkt6.transid == 12346", false); +} + +// Tests if pkt4.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger3) { + testExpression(Option::V4, "pkt4.transid == 0", false); + testExpression(Option::V4, "pkt4.transid == 12345", true); + testExpression(Option::V4, "pkt4.transid == 12346", false); +} + +// Tests if integers can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger4) { + testExpression(Option::V6, "0 == 0", true); + testExpression(Option::V6, "2 == 3", false); +} + +// Tests if pkt4.hlen and pkt4.htype return values that can be compared with integers. +TEST_F(ExpressionsTest, expressionsPkt4Hlen) { + + // By default there's no hardware set up. The default Pkt4 constructor + // creates HWAddr(), which has hlen=0 and htype set to HTYPE_ETHER. + testExpression(Option::V4, "pkt4.hlen == 0", true); + testExpression(Option::V4, "pkt4.htype == 1", true); + + // Ok, let's initialize the hardware address to something plausible. + const size_t hwaddr_len = 6; + const uint16_t expected_htype = 123; + std::vector<uint8_t> hw(hwaddr_len,0); + for (int i = 0; i < hwaddr_len; i++) { + hw[i] = i + 1; + } + pkt4_->setHWAddr(expected_htype, hwaddr_len, hw); + + testExpression(Option::V4, "pkt4.hlen == 0", false); + testExpression(Option::V4, "pkt4.hlen == 5", false); + testExpression(Option::V4, "pkt4.hlen == 6", true); + testExpression(Option::V4, "pkt4.hlen == 7", false); + + testExpression(Option::V4, "pkt4.htype == 0", false); + testExpression(Option::V4, "pkt4.htype == 122", false); + testExpression(Option::V4, "pkt4.htype == 123", true); + testExpression(Option::V4, "pkt4.htype == 124", false); + + testExpression(Option::V4, "pkt4.mac == 0x010203040506", true); +} + +// Test if expressions message type can be detected in Pkt4. +// It also doubles as a check for integer comparison here. +TEST_F(ExpressionsTest, expressionsPkt4type) { + + // We can inspect the option content directly, but + // it requires knowledge of the option type and its format. + testExpression(Option::V4, "option[53].hex == 0x0", false); + testExpression(Option::V4, "option[53].hex == 0x1", true); + testExpression(Option::V4, "option[53].hex == 0x2", false); + + // It's easier to simply use the pkt4.msgtype + testExpression(Option::V4, "pkt4.msgtype == 0", false); + testExpression(Option::V4, "pkt4.msgtype == 1", true); + testExpression(Option::V4, "pkt4.msgtype == 2", false); +} + +// This tests if inappropriate values (negative, too large) are +// rejected, but extreme values still allowed for uint32_t are ok. +TEST_F(ExpressionsTest, invalidIntegers) { + + // These are the extreme uint32_t values that still should be accepted. + testExpression(Option::V4, "4294967295 == 0", false); + + // Negative integers should be rejected. + testExpressionNegative<EvalParseError>("4294967295 == -1"); + + // Oops, one too much. + testExpressionNegative<EvalParseError>("4294967296 == 0"); +} }; diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc index 96b52137e1..2fcf1c549d 100644 --- a/src/lib/eval/tests/token_unittest.cc +++ b/src/lib/eval/tests/token_unittest.cc @@ -7,6 +7,7 @@ #include <config.h> #include <fstream> #include <eval/token.h> +#include <eval/eval_context.h> #include <dhcp/pkt4.h> #include <dhcp/pkt6.h> #include <dhcp/dhcp4.h> @@ -183,25 +184,22 @@ public: } /// @brief Convenience function. Removes token and values stacks. - void clearStack() { + /// @param token specifies if the convenience token should be removed or not + void clearStack(bool token = true) { while (!values_.empty()) { values_.pop(); } - - t_.reset(); + if (token) { + t_.reset(); + } } - /// @brief Aux. function that stores integer values as 4 bytes string. + /// @brief Aux. function that stores integer values as 4 byte string. /// /// @param value integer value to be stored - /// @return 4 bytes long string with encoded value. + /// @return 4 byte long string with encoded value. string encode(uint32_t value) { - string tmp(4,0); - tmp[0] = value >> 24; - tmp[1] = value >> 16; - tmp[2] = value >> 8; - tmp[3] = value; - return (tmp); + return EvalContext::fromUint32(value); } TokenPtr t_; ///< Just a convenience pointer @@ -484,6 +482,26 @@ public: evaluate(u, expected); } + + /// @brief Tests if TokenInteger evaluates to the proper value + /// + /// @param expected expected string representation on stack after evaluation + /// @param value integer value passed to constructor + void testInteger(const std::string& expected, uint32_t value) { + + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenInteger(value))); + + // The universe (v4 or v6) shouldn't have any impact on this, + // but let's check it anyway. + evaluate(Option::V4, expected); + + clearStack(false); + evaluate(Option::V6, expected); + + clearStack(true); + } }; // This tests the toBool() conversions @@ -1329,6 +1347,24 @@ TEST_F(TokenTest, pkt4Fields) { ASSERT_EQ(4, values_.top().size()); EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + // Check msgtype. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::MSGTYPE))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + string exp_msgtype = encode(DHCPDISCOVER); + EXPECT_EQ(0, memcmp(&exp_msgtype[0], &values_.top()[0], 4)); + + // Check transaction-id + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::TRANSID))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + string exp_transid = encode(12345); + EXPECT_EQ(0, memcmp(&exp_transid[0], &values_.top()[0], 4)); + // Check a DHCPv6 packet throws. clearStack(); ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN))); @@ -1349,6 +1385,8 @@ TEST_F(TokenTest, pkt4Fields) { addString("EVAL_DEBUG_PKT4 Pushing PKT4 field ciaddr with value 0xC0000202"); addString("EVAL_DEBUG_PKT4 Pushing PKT4 field yiaddr with value 0xC0000203"); addString("EVAL_DEBUG_PKT4 Pushing PKT4 field siaddr with value 0xC0000204"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field msgtype with value 0x00000001"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field transid with value 0x00003039"); EXPECT_TRUE(checkFile()); } @@ -2721,4 +2759,14 @@ TEST_F(TokenTest, vendorClass6DataIndex) { EXPECT_TRUE(checkFile()); } +// Checks if various values can be represented as integer tokens +TEST_F(TokenTest, integer) { + testInteger(encode(0), 0); + testInteger(encode(6), 6); + testInteger(encode(255), 255); + testInteger(encode(256), 256); + testInteger(encode(1410), 1410); + testInteger(encode(4294967295), 4294967295); +} + }; diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc index 7ef16a0348..a6471929a2 100644 --- a/src/lib/eval/token.cc +++ b/src/lib/eval/token.cc @@ -6,6 +6,7 @@ #include <eval/token.h> #include <eval/eval_log.h> +#include <eval/eval_context.h> #include <util/encode/hex.h> #include <util/io_utilities.h> #include <asiolink/io_address.h> @@ -200,11 +201,12 @@ TokenPkt::evaluate(Pkt& pkt, ValueStack& values) { string value; vector<uint8_t> binary; string type_str; - uint32_t len; bool is_binary = true; + bool print_hex = true; switch (type_) { case IFACE: is_binary = false; + print_hex = false; value = pkt.getIface(); type_str = "iface"; break; @@ -221,9 +223,8 @@ TokenPkt::evaluate(Pkt& pkt, ValueStack& values) { // (with UDP transport it fits in 16 bits) // the len() method is not const because of DHCPv6 relays. // We assume here it has no bad side effects... - len = static_cast<uint32_t>(const_cast<Pkt&>(pkt).len()); - binary.resize(sizeof(uint32_t)); - static_cast<void>(writeUint32(len, &binary[0], binary.size())); + value = EvalContext::fromUint32(static_cast<uint32_t>(const_cast<Pkt&>(pkt).len())); + is_binary = false; type_str = "len"; break; @@ -243,13 +244,14 @@ TokenPkt::evaluate(Pkt& pkt, ValueStack& values) { // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT) .arg(type_str) - .arg(is_binary ? toHex(value) : value); + .arg(print_hex ? toHex(value) : value); } void TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { vector<uint8_t> binary; + string value; string type_str; try { // Check if it's a Pkt4. If it's not, the dynamic_cast will throw @@ -292,21 +294,23 @@ TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { case HLEN: // Pad the uint8_t field to 4 bytes. - binary.push_back(0); - binary.push_back(0); - binary.push_back(0); - binary.push_back(pkt4.getHlen()); + value = EvalContext::fromUint32(pkt4.getHlen()); type_str = "hlen"; break; case HTYPE: // Pad the uint8_t field to 4 bytes. - binary.push_back(0); - binary.push_back(0); - binary.push_back(0); - binary.push_back(pkt4.getHtype()); + value = EvalContext::fromUint32(pkt4.getHtype()); type_str = "htype"; break; + case MSGTYPE: + value = EvalContext::fromUint32(pkt4.getType()); + type_str = "msgtype"; + break; + case TRANSID: + value = EvalContext::fromUint32(pkt4.getTransid()); + type_str = "transid"; + break; default: isc_throw(EvalTypeError, "Bad field specified: " @@ -317,9 +321,8 @@ TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { isc_throw(EvalTypeError, "Specified packet is not a Pkt4"); } - string value; - value.resize(binary.size()); if (!binary.empty()) { + value.resize(binary.size()); memmove(&value[0], &binary[0], binary.size()); } values.push(value); @@ -333,7 +336,7 @@ TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { void TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) { - vector<uint8_t> binary; + string value; string type_str; try { // Check if it's a Pkt6. If it's not the dynamic_cast will throw @@ -344,20 +347,13 @@ TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) { switch (type_) { case MSGTYPE: { // msg type is an uint8_t integer. We want a 4 byte string so 0 pad. - binary.push_back(0); - binary.push_back(0); - binary.push_back(0); - binary.push_back(pkt6.getType()); + value = EvalContext::fromUint32(pkt6.getType()); type_str = "msgtype"; break; } case TRANSID: { // transaction id is an uint32_t integer. We want a 4 byte string so copy - uint32_t transid = pkt6.getTransid(); - binary.push_back(transid >> 24); - binary.push_back((transid >> 16) & 0xFF); - binary.push_back((transid >> 8) & 0xFF); - binary.push_back(transid & 0xFF); + value = EvalContext::fromUint32(pkt6.getTransid()); type_str = "transid"; break; } @@ -370,9 +366,6 @@ TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) { isc_throw(EvalTypeError, "Specified packet is not Pkt6"); } - string value; - value.resize(binary.size()); - memmove(&value[0], &binary[0], binary.size()); values.push(value); // Log what we pushed @@ -879,3 +872,8 @@ void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) { isc_throw(EvalTypeError, "Invalid field specified." << field_); } } + +TokenInteger::TokenInteger(const uint32_t value) + :TokenString(EvalContext::fromUint32(value)), int_value_(value) { + +} diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h index aabca84397..37af93c468 100644 --- a/src/lib/eval/token.h +++ b/src/lib/eval/token.h @@ -155,6 +155,35 @@ protected: std::string value_; ///< Constant value }; +/// @brief Token representing an unsigned 32 bit integer +/// +/// For performance reasons, the constant integer value is converted to a string +/// just once (in the constructor). Afterwards, this effectively works as a constant +/// 4 byte long string. Hence this class is derived from TokenString and +/// does not even need its own evaluate() method. +class TokenInteger : public TokenString { +public: + /// @brief Integer value set during construction. + /// + /// The value is converted to string and stored in value_ provided by the + /// base class. + /// + /// @param value integer value to be stored. + TokenInteger(const uint32_t value); + + /// @brief Returns integer value + /// + /// Used in tests only. + /// + /// @return integer value + uint32_t getInteger() const { + return (int_value_); + } + +protected: + uint32_t int_value_; ///< value as integer (stored for testing only) +}; + /// @brief Token representing an IP address as a constant string /// /// This token holds the value of an IP address as a constant string, @@ -413,7 +442,9 @@ public: YIADDR, ///< yiaddr (IPv4 address) SIADDR, ///< siaddr (IPv4 address) HLEN, ///< hlen (hardware address length) - HTYPE ///< htype (hardware address type) + HTYPE, ///< htype (hardware address type) + MSGTYPE, ///< message type (not really a field, content of option 53) + TRANSID, ///< transaction-id (xid) }; /// @brief Constructor (does nothing) diff --git a/src/lib/log/logger_level_impl.cc b/src/lib/log/logger_level_impl.cc index d5cff064d0..ae8a3ed568 100644 --- a/src/lib/log/logger_level_impl.cc +++ b/src/lib/log/logger_level_impl.cc @@ -179,20 +179,18 @@ LoggerLevelImpl::logLevelFromString(const log4cplus::tstring& level) { // return the string DEBUG, else return the empty string. LoggerLevelImpl::LogLevelString LoggerLevelImpl::logLevelToString(log4cplus::LogLevel level) { - static const tstring debug_string("DEBUG"); - static const tstring empty_string; Level bindlevel = convertToBindLevel(level); Severity& severity = bindlevel.severity; int& dbglevel = bindlevel.dbglevel; if ((severity == DEBUG) && ((dbglevel >= MIN_DEBUG_LEVEL) && (dbglevel <= MAX_DEBUG_LEVEL))) { - return (debug_string); + return (tstring("DEBUG")); } // Unknown, so return empty string for log4cplus to try other conversion // functions. - return (empty_string); + return (tstring()); } // Initialization. Register the conversion functions with the LogLevelManager. diff --git a/src/lib/log/logger_level_impl.h b/src/lib/log/logger_level_impl.h index 9d4dd4b0aa..20321bd328 100644 --- a/src/lib/log/logger_level_impl.h +++ b/src/lib/log/logger_level_impl.h @@ -58,11 +58,7 @@ namespace log { class LoggerLevelImpl { public: -#if (LOG4CPLUS_VERSION >= LOG4CPLUS_MAKE_VERSION(1, 1, 0)) - typedef log4cplus::tstring const & LogLevelString; -#else - typedef log4cplus::tstring LogLevelString; -#endif +typedef log4cplus::tstring LogLevelString; /// \brief Convert Kea level to log4cplus logging level /// diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc index f9c5ee3b20..3d670e256a 100644 --- a/src/lib/log/message_dictionary.cc +++ b/src/lib/log/message_dictionary.cc @@ -13,6 +13,11 @@ using namespace std; namespace isc { namespace log { +// Constructor + +MessageDictionary::MessageDictionary() : dictionary_(), empty_("") { +} + // (Virtual) Destructor MessageDictionary::~MessageDictionary() { @@ -91,10 +96,9 @@ MessageDictionary::load(const char* messages[]) { const string& MessageDictionary::getText(const std::string& ident) const { - static const string empty(""); Dictionary::const_iterator i = dictionary_.find(ident); if (i == dictionary_.end()) { - return (empty); + return (empty_); } else { return (i->second); diff --git a/src/lib/log/message_dictionary.h b/src/lib/log/message_dictionary.h index ac8541a863..7863dc6a3e 100644 --- a/src/lib/log/message_dictionary.h +++ b/src/lib/log/message_dictionary.h @@ -50,7 +50,8 @@ public: typedef std::map<std::string, std::string> Dictionary; typedef Dictionary::const_iterator const_iterator; - // Default constructor and assignment operator are OK for this class + /// \brief Constructor + MessageDictionary(); /// \brief Virtual Destructor virtual ~MessageDictionary(); @@ -198,6 +199,7 @@ public: private: Dictionary dictionary_; ///< Holds the ID to text lookups + const std::string empty_; ///< Empty string }; } // namespace log |