From: Andrei Pavel Date: Wed, 15 Jun 2016 14:42:00 +0000 (+0300) Subject: solved Cassandra unit tests & added documentation X-Git-Tag: trac4283_base~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=08343129b42635653e248f13000897be181baf39;p=thirdparty%2Fkea.git solved Cassandra unit tests & added documentation --- diff --git a/configure.ac b/configure.ac index a5e390ee80..150fffc772 100755 --- a/configure.ac +++ b/configure.ac @@ -1329,11 +1329,11 @@ if test "x$enable_generate_docs" != xno ; then else AC_MSG_CHECKING([if $XSLTPROC works]) # run xsltproc to see if works - $XSLTPROC --novalid --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl + $XSLTPROC --novalid --xinclude http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl if test $? -ne 0 ; then AC_MSG_ERROR("Error with $XSLTPROC using release/xsl/current/manpages/docbook.xsl") fi - $XSLTPROC --novalid --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl + $XSLTPROC --novalid --xinclude http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl if test $? -ne 0 ; then AC_MSG_ERROR("Error with $XSLTPROC using release/xsl/current/html/docbook.xsl") fi diff --git a/doc/guide/admin.xml b/doc/guide/admin.xml index dd5ac7391a..4557e09daa 100644 --- a/doc/guide/admin.xml +++ b/doc/guide/admin.xml @@ -94,10 +94,10 @@ lease-dump — - Dumps the contents of the lease database (for MySQL or PostgreSQL - backends) to CSV text file. The first line of the file contains - the column names. This is meant to be used as a diagnostic - tool that provides a portable, human-readable form of lease data. + Dumps the contents of the lease database (for MySQL, PostgreSQL or + CQL backends) to CSV text file. The first line of the file contains + the column names. This is meant to be used as a diagnostic tool + that provides a portable, human-readable form of lease data. @@ -128,6 +128,14 @@ database. + + + + cql — + Lease information is stored in a CQL database. + + + Additional parameters may be needed, depending on your setup @@ -484,6 +492,122 @@ $ kea-admin lease-init pgsql -u database-user + +
+ CQL + + + The CQL database must be properly set up if you want Kea to store + information in CQL. This section can be safely ignored if you chose to + store the data in other backends. + + +
+ First Time Creation of Kea Database + + + If you are setting up the CQL database for the first time, you need to + create the keyspace area within CQL. This needs to be done manually: + kea-admin is not able to do this for you. + + + + To create the database: + + + + Export CQLSH_HOST environemnt variable: + +$ export CQLSH_HOST=localhost + + + + + + Log into CQL: + +$ cqlsh +cql> + + + + + + + Create the CQL keyspace: + +cql> CREATE KEYSPACE keyspace-name WITH replication = {'class' : 'SimpleStrategy','replication_factor' : 1}; + + (keyspace-name is the name you have + chosen for the keyspace) + + + + + + At this point, you may elect to create the database tables. + (Alternatively, you can exit CQL and create the tables using the + kea-admin tool, as explained below) To do this: + +cqslh -k keyspace-name -f path-to-kea/share/kea/scripts/cql/dhcpdb_create.cql + + (path-to-kea is the location where you + installed Kea) + + + + + + + If you elected not to create the tables in step 4, you can do + so now by running the kea-admin tool: + +$ kea-admin lease-init cql -n database-name + + (Do not do this if you did create the tables in step 4.) + kea-admin implements rudimentary checks: + it will refuse to initialize a database that contains any + existing tables. If you want to start from scratch, you + must remove all data manually. (This process is a manual + operation on purpose to avoid possibly irretrievable mistakes + by kea-admin) + +
+ +
+ Upgrading a CQL Database from an Earlier Version of Kea + + + Sometimes a new Kea version may use newer database schema, so + there will be a need to upgrade the existing database. This can + be done using the kea-admin lease-upgrade + command. + + + + To check the current version of the database, use the following command: + +$ kea-admin lease-version cql -n database-name + + (See for a discussion + about versioning) If the version does not match the minimum + required for the new version of Kea (as described in the + release notes), the database needs to be upgraded. + + + + Before upgrading, please make sure that the database is + backed up. The upgrade process does not discard any data but, + depending on the nature of the changes, it may be impossible + to subsequently downgrade to an earlier version. To perform + an upgrade, issue the following command: + +$ kea-admin lease-upgrade cql -n database-name + + +
+
+
Limitations related to the use of the SQL databases diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml index 0489ff1528..a0f422e37c 100644 --- a/doc/guide/dhcp4-srv.xml +++ b/doc/guide/dhcp4-srv.xml @@ -301,8 +301,8 @@ be followed by a comma and another object definition.
Lease Storage All leases issued by the server are stored in the lease database. - Currently there are three database backends available: - memfile (which is the default backend), MySQL and PostgreSQL. + Currently there are four database backends available: memfile (which is the + default backend), MySQL, PostgreSQL and CQL.
Memfile, Basic Storage for Leases @@ -441,8 +441,10 @@ be followed by a comma and another object definition. "Dhcp4": { "lease-database": { "type": "mysql", ... }, ... } Next, the name of the database to hold the leases must be set: this is the - name used when the lease database was created (see - or ). + name used when the lease database was created + (see , + or + ). "Dhcp4": { "lease-database": { "name": "database-name" , ... }, ... } diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 5eec3e79da..aad1159738 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -306,8 +306,8 @@ be followed by a comma and another object definition.
Lease Storage All leases issued by the server are stored in the lease database. - Currently there are three database backends available: - memfile (which is the default backend), MySQL and PostgreSQL. + Currently there are four database backends available: memfile (which is the + default backend), MySQL, PostgreSQL and CQL.
Memfile, Basic Storage for Leases @@ -435,13 +435,15 @@ be followed by a comma and another object definition. Lease database configuration is controlled through the Dhcp6/lease-database parameters. The type of the database must be set to - "memfile", "mysql" or "postgresql", e.g. + "memfile", "mysql", "postgresql" or "cql", e.g. "Dhcp6": { "lease-database": { "type": "mysql", ... }, ... } Next, the name of the database is to hold the leases must be set: this is the - name used when the lease database was created (see - or ). + name used when the lease database was created + (see , + + or ). "Dhcp6": { "lease-database": { "name": "database-name" , ... }, ... } @@ -3568,8 +3570,8 @@ should include options from the isc option space: that are stored in the lease database. Removing non-last subnet will cause the configuration information to mismatch data in the lease database. It is possible to manually update subnet-id fields in - MySQL or PostgreSQL database, but it is awkward and error prone - process. A better reconfiguration support is planned. + MySQL, PostgreSQL or CQL database, but it is awkward and + error prone process. A better reconfiguration support is planned. diff --git a/doc/guide/install.xml b/doc/guide/install.xml index 61ab7a0239..b1f4772e80 100644 --- a/doc/guide/install.xml +++ b/doc/guide/install.xml @@ -367,7 +367,7 @@ Debian and Ubuntu: ./configure when it succeeds displays a report - with the building parameters. This report is saved into + with the building parameters. This report is saved into config.report and embedded into executable binaries, e.g., kea-dhcp4. diff --git a/doc/guide/intro.xml b/doc/guide/intro.xml index ae5dcef4b0..424673fc2e 100644 --- a/doc/guide/intro.xml +++ b/doc/guide/intro.xml @@ -88,6 +88,14 @@ built without PostgreSQL support. + + + + In order to store lease information in a CQL database, Kea requires CQL + headers and libraries. This is an optional dependency in that Kea can be + built without CQL support. + +
diff --git a/src/bin/admin/kea-admin.in b/src/bin/admin/kea-admin.in index e21ca87e81..153252e6df 100644 --- a/src/bin/admin/kea-admin.in +++ b/src/bin/admin/kea-admin.in @@ -489,10 +489,10 @@ cql_dump() { select_where_clause="" if [ $dump_type -eq 4 ]; then - dump_qry="SELECT address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state FROM keatest.lease4" + dump_qry="SELECT address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state FROM lease4" select_where_clause=" WHERE address = 0" # invalid address elif [ $dump_type -eq 6 ]; then - dump_qry="SELECT address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state FROM keatest.lease6" + dump_qry="SELECT address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state FROM lease6" select_where_clause=" WHERE address = '::'" # invalid address fi diff --git a/src/lib/dhcpsrv/cql_connection.cc b/src/lib/dhcpsrv/cql_connection.cc index 0841ecacca..ef5c33f479 100644 --- a/src/lib/dhcpsrv/cql_connection.cc +++ b/src/lib/dhcpsrv/cql_connection.cc @@ -98,7 +98,7 @@ CqlConnection::openDatabase() { skeyspace = getParameter("keyspace"); keyspace = skeyspace.c_str(); } catch (...) { - // No database name. Fine, we'll use default "keatest". + // No keyspace name. Fine, we'll use default "keatest". } cluster_ = cass_cluster_new(); diff --git a/src/lib/dhcpsrv/cql_lease_mgr.cc b/src/lib/dhcpsrv/cql_lease_mgr.cc index b186aecf06..630110c57c 100644 --- a/src/lib/dhcpsrv/cql_lease_mgr.cc +++ b/src/lib/dhcpsrv/cql_lease_mgr.cc @@ -258,7 +258,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { // DELETE_LEASE4 { delete_lease4_params, "delete_lease4", - "DELETE FROM lease4 WHERE address = ?" }, + "DELETE FROM lease4 WHERE address = ? " + "IF EXISTS" }, // DELETE_LEASE4_STATE_EXPIRED { delete_expired_lease4_params, @@ -267,12 +268,14 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "valid_lifetime, expire, subnet_id, " "fqdn_fwd, fqdn_rev, hostname, state " "FROM lease4 " - "WHERE state = ? AND expire < ? ALLOW FILTERING" }, + "WHERE state = ? AND expire < ? " + "ALLOW FILTERING" }, // DELETE_LEASE6 { delete_lease6_params, "delete_lease6", - "DELETE FROM lease6 WHERE address = ?" }, + "DELETE FROM lease6 WHERE address = ? " + "IF EXISTS" }, // DELETE_LEASE6_STATE_EXPIRED { delete_expired_lease6_params, @@ -282,7 +285,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " "hwaddr, hwtype, hwaddr_source, state " "FROM lease6 " - "WHERE state = ? AND expire < ? ALLOW FILTERING" }, + "WHERE state = ? AND expire < ? " + "ALLOW FILTERING" }, // GET_LEASE4_ADDR { get_lease4_addr_params, @@ -309,7 +313,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "valid_lifetime, expire, subnet_id, " "fqdn_fwd, fqdn_rev, hostname, state " "FROM lease4 " - "WHERE client_id = ? AND subnet_id = ? ALLOW FILTERING" }, + "WHERE client_id = ? AND subnet_id = ? " + "ALLOW FILTERING" }, // GET_LEASE4_HWADDR { get_lease4_hwaddr_params, @@ -327,7 +332,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "valid_lifetime, expire, subnet_id, " "fqdn_fwd, fqdn_rev, hostname, state " "FROM lease4 " - "WHERE hwaddr = ? AND subnet_id = ? ALLOW FILTERING" }, + "WHERE hwaddr = ? AND subnet_id = ? " + "ALLOW FILTERING" }, // GET_LEASE4_EXPIRE { get_lease4_expired_params, @@ -337,7 +343,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "fqdn_fwd, fqdn_rev, hostname, state " "FROM lease4 " "WHERE state = ? AND expire < ? " - "LIMIT ? ALLOW FILTERING" }, + "LIMIT ? " + "ALLOW FILTERING" }, // GET_LEASE6_ADDR { get_lease6_addr_params, @@ -347,7 +354,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " "hwaddr, hwtype, hwaddr_source, state " "FROM lease6 " - "WHERE address = ? AND lease_type = ? ALLOW FILTERING" }, + "WHERE address = ? AND lease_type = ? " + "ALLOW FILTERING" }, // GET_LEASE6_DUID_IAID { get_lease6_duid_iaid_params, @@ -357,7 +365,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " "hwaddr, hwtype, hwaddr_source, state " "FROM lease6 " - "WHERE duid = ? AND iaid = ? AND lease_type = ? ALLOW FILTERING" }, + "WHERE duid = ? AND iaid = ? AND lease_type = ? " + "ALLOW FILTERING" }, // GET_LEASE6_DUID_IAID_SUBID { get_lease6_duid_iaid_subid_params, @@ -367,7 +376,8 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " "hwaddr, hwtype, hwaddr_source, state " "FROM lease6 " - "WHERE duid = ? AND iaid = ? AND subnet_id = ? AND lease_type = ? ALLOW FILTERING" }, + "WHERE duid = ? AND iaid = ? AND subnet_id = ? AND lease_type = ? " + "ALLOW FILTERING" }, // GET_LEASE6_EXPIRE { get_lease6_expired_params, @@ -377,9 +387,9 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, " "hwaddr, hwtype, hwaddr_source, state " "FROM lease6 " - //"WHERE state != ? AND expire < ? ORDER BY expire ASC " "WHERE state = ? AND expire < ? " - "LIMIT ? ALLOW FILTERING" }, + "LIMIT ? " + "ALLOW FILTERING" }, // GET_VERSION { get_version_params, @@ -393,7 +403,7 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, " "state) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " - }, + "IF NOT EXISTS" }, // INSERT_LEASE6 { insert_lease6_params, @@ -403,7 +413,7 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, hwaddr, " "hwtype, hwaddr_source, state) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " - }, + "IF NOT EXISTS" }, // UPDATE_LEASE4 { update_lease4_params, @@ -412,7 +422,7 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "client_id = ?, valid_lifetime = ?, expire = ?, " "subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, hostname = ?, state = ? " "WHERE address = ? " - }, + "IF EXISTS" }, // UPDATE_LEASE6 { update_lease6_params, @@ -423,7 +433,7 @@ CqlTaggedStatement CqlLeaseMgr::tagged_statements_[] = { "prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, hostname = ?, " "hwaddr = ?, hwtype = ?, hwaddr_source = ?, state = ? " "WHERE address = ? " - }, + "IF EXISTS" }, // End of list sentinel { NULL, NULL, NULL } @@ -501,12 +511,12 @@ public: /// all variables are initialized/set in the methods before they are used. CqlLease4Exchange() : addr4_(0), client_id_length_(0), client_id_null_(false) { - const size_t MAX_COLUMNS = 11U; + const size_t MAX_COLUMNS = 12U; memset(client_id_buffer_, 0, sizeof(client_id_buffer_)); // Set the column names size_t offset = 0U; - BOOST_STATIC_ASSERT(11U == MAX_COLUMNS); + BOOST_STATIC_ASSERT(12U == MAX_COLUMNS); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("address", offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("hwaddr", @@ -529,6 +539,8 @@ public: offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("limit", offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32))); + parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("[applied]", + offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL))); BOOST_ASSERT(parameters_.size() == MAX_COLUMNS); } @@ -553,7 +565,18 @@ public: data.add(&addr4_); // hwaddr: blob - hwaddr_ = lease_->hwaddr_->hwaddr_; + HWAddrPtr hwaddr = lease_->hwaddr_; + if (hwaddr) { + if (hwaddr->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) { + isc_throw(DbOperationError, "Hardware address length " << + lease_->hwaddr_->hwaddr_.size() << + " exceeds maximum allowed of " << + HWAddr::MAX_HWADDR_LEN); + } + hwaddr_ = hwaddr->hwaddr_; + } else { + hwaddr_.clear(); + } hwaddr_length_ = hwaddr_.size(); data.add(&hwaddr_); @@ -622,7 +645,6 @@ public: /// Creates a CQL_BIND array to receive Lease4 data from the database. Lease4Ptr createBindForReceive(const CassRow* row) { try { - unsigned char* hwaddr_buffer = NULL; const char* client_id_buffer = NULL; const char* hostname_buffer = NULL; @@ -669,8 +691,8 @@ public: data.add(reinterpret_cast(&state_)); size.add(NULL); - for (int i = 0; i < 10; i++) { - CqlLeaseMgr::getData(row, i, data, size, *this); + for (size_t i = 0; i < data.size(); i++) { + CqlLeaseMgr::getData(row, i, data, size, i, *this); } // hwaddr: blob @@ -756,17 +778,17 @@ public: CqlLease6Exchange() : addr6_length_(0), duid_length_(0), iaid_(0), lease_type_(0), prefixlen_(0), pref_lifetime_(0), hwaddr_null_(false), hwtype_(0), hwaddr_source_(0) { - const size_t MAX_COLUMNS = 17U; + const size_t MAX_COLUMNS = 18U; memset(addr6_buffer_, 0, sizeof(addr6_buffer_)); memset(duid_buffer_, 0, sizeof(duid_buffer_)); // Set the column names size_t offset = 0U; - BOOST_STATIC_ASSERT(17U == MAX_COLUMNS); + BOOST_STATIC_ASSERT(18U == MAX_COLUMNS); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("address", offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("duid", - offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING))); + offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BYTES))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("valid_lifetime", offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT64))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("expire", @@ -797,6 +819,8 @@ public: offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32))); parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("limit", offset++, EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32))); + parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo("[applied]", + offset++, EXCHANGE_DATA_TYPE_IO_OUT, EXCHANGE_DATA_TYPE_BOOL))); BOOST_ASSERT(parameters_.size() == MAX_COLUMNS); } @@ -899,6 +923,12 @@ public: // hwaddr: blob HWAddrPtr hwaddr = lease_->hwaddr_; if (hwaddr) { + if (hwaddr->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) { + isc_throw(DbOperationError, "Hardware address length : " + << lease_->hwaddr_->hwaddr_.size() + << " exceeds maximum allowed of: " + << HWAddr::MAX_HWADDR_LEN); + } hwaddr_ = hwaddr->hwaddr_; } else { hwaddr_.clear(); @@ -1009,8 +1039,8 @@ public: data.add(reinterpret_cast(&state_)); size.add(NULL); - for (int i = 0; i < 16; i++) { - CqlLeaseMgr::getData(row, i, data, size, *this); + for (size_t i = 0; i < data.size(); i++) { + CqlLeaseMgr::getData(row, i, data, size, i, *this); } // address: varchar @@ -1151,8 +1181,8 @@ CqlLeaseMgr::bindData(CassStatement* statement, const StatementIndex stindex, } void -CqlLeaseMgr::getData(const CassRow* row, int pindex, CqlDataArray& data, - CqlDataArray& size, const SqlExchange& exchange) { +CqlLeaseMgr::getData(const CassRow* row, const int pindex, CqlDataArray& data, + CqlDataArray& size, const int dindex, const SqlExchange& exchange) { const CassValue* value; if (pindex >= exchange.parameters_.size()) { return; @@ -1169,8 +1199,8 @@ CqlLeaseMgr::getData(const CassRow* row, int pindex, CqlDataArray& data, if (type >= sizeof(CqlFunctions) / sizeof(CqlFunctions[0])) { isc_throw(BadValue, "index " << type << " out of bounds"); } - CqlFunctions[type].sqlGetFunction_(value, data.values_[pindex], - reinterpret_cast(size.values_[pindex])); + CqlFunctions[type].sqlGetFunction_(value, data.values_[dindex], + reinterpret_cast(size.values_[dindex])); } } @@ -1200,14 +1230,31 @@ CqlLeaseMgr::addLeaseCommon(StatementIndex stindex, if (rc != CASS_OK) { cass_future_free(future); cass_statement_free(statement); - isc_throw(DbOperationError, error); + return false; } + + // Check if statement has been applied. const CassResult* resultCollection = cass_future_get_result(future); + CassIterator* rows = cass_iterator_from_result(resultCollection); + CqlDataArray appliedData; + CqlDataArray appliedSize; + bool applied = false; + while (cass_iterator_next(rows)) { + const CassRow* row = cass_iterator_get_row(rows); + // [applied]: bool + appliedData.add(reinterpret_cast(&applied)); + appliedSize.add(NULL); + CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData, + appliedSize, 0, exchange); + } + + // Free resources. + cass_iterator_free(rows); cass_result_free(resultCollection); cass_future_free(future); cass_statement_free(statement); - return (true); + return applied; } bool @@ -1268,7 +1315,6 @@ void CqlLeaseMgr::getLeaseCollection(StatementIndex stindex, const CassResult* resultCollection = cass_future_get_result(future); CassIterator* rows = cass_iterator_from_result(resultCollection); - int rowCount = 0; while (cass_iterator_next(rows)) { rowCount++; @@ -1555,14 +1601,13 @@ CqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases, const size_t max_leases, StatementIndex statement_index) const { // Set up the WHERE clause value - //"WHERE state != ? AND expire < ? ORDER BY expire ASC " uint32_t keepState = Lease::STATE_EXPIRED_RECLAIMED; uint64_t timestamp = static_cast(time(NULL)); // If the number of leases is 0, we will return all leases. This is // achieved by setting the limit to a very high value. - uint32_t limit = max_leases > 0 ? static_cast(max_leases) : - std::numeric_limits::max(); + uint32_t limit = max_leases > 0 ? static_cast(max_leases) : + std::numeric_limits::max(); for (uint32_t state = Lease::STATE_DEFAULT; state <= Lease::STATE_EXPIRED_RECLAIMED; state++) { @@ -1622,10 +1667,30 @@ CqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, isc_throw(DbOperationError, error); } + // Check if statement has been applied. const CassResult* resultCollection = cass_future_get_result(future); + CassIterator* rows = cass_iterator_from_result(resultCollection); + CqlDataArray appliedData; + CqlDataArray appliedSize; + bool applied = false; + while (cass_iterator_next(rows)) { + const CassRow* row = cass_iterator_get_row(rows); + // [applied]: bool + appliedData.add(reinterpret_cast(&applied)); + appliedSize.add(NULL); + CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData, + appliedSize, 0, exchange); + } + + // Free resources. + cass_iterator_free(rows); cass_result_free(resultCollection); cass_future_free(future); cass_statement_free(statement); + + if (!applied) { + isc_throw(NoSuchLease, "Statement has not been applied."); + } } void @@ -1700,13 +1765,34 @@ CqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, std::string error; dbconn_.checkStatementError(error, future, stindex, "unable to DELETE"); rc = cass_future_error_code(future); - cass_future_free(future); - cass_statement_free(statement); if (rc != CASS_OK) { - isc_throw(DbOperationError, error); + cass_future_free(future); + cass_statement_free(statement); + isc_throw(DbOperationError, error); + } + + // Check if statement has been applied. + const CassResult* resultCollection = cass_future_get_result(future); + CassIterator* rows = cass_iterator_from_result(resultCollection); + CqlDataArray appliedData; + CqlDataArray appliedSize; + bool applied = false; + while (cass_iterator_next(rows)) { + const CassRow* row = cass_iterator_get_row(rows); + // [applied]: bool + appliedData.add(reinterpret_cast(&applied)); + appliedSize.add(NULL); + CqlLeaseMgr::getData(row, exchange.parameters_.size() - 1, appliedData, + appliedSize, 0, exchange); } - return (true); + // Free resources. + cass_iterator_free(rows); + cass_result_free(resultCollection); + cass_future_free(future); + cass_statement_free(statement); + + return applied; } bool @@ -1757,7 +1843,6 @@ uint64_t CqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs, StatementIndex statement_index) { // Set up the WHERE clause value - //"WHERE state = ? AND expire < ? ALLOW FILTERING" CqlDataArray data; uint32_t result = 0; @@ -1845,13 +1930,12 @@ CqlLeaseMgr::getVersion() const { isc_throw(DbOperationError, error); } + // Get major and minor versions. const CassResult* resultCollection = cass_future_get_result(future); CassIterator* rows = cass_iterator_from_result(resultCollection); CqlDataArray data; CqlDataArray size; - int rowCount = 0; while (cass_iterator_next(rows)) { - rowCount++; const CassRow* row = cass_iterator_get_row(rows); // version: uint32_t data.add(reinterpret_cast(&version)); @@ -1859,9 +1943,8 @@ CqlLeaseMgr::getVersion() const { // minor: uint32_t data.add(reinterpret_cast(&minor)); size.add(NULL); - - for (int i = 0; i < 2; i++) { - CqlLeaseMgr::getData(row, i, data, size, *versionExchange_); + for (size_t i = 0; i < data.size(); i++) { + CqlLeaseMgr::getData(row, i, data, size, i, *versionExchange_); } } diff --git a/src/lib/dhcpsrv/cql_lease_mgr.h b/src/lib/dhcpsrv/cql_lease_mgr.h index 618b864e7f..7e5ce3b36f 100644 --- a/src/lib/dhcpsrv/cql_lease_mgr.h +++ b/src/lib/dhcpsrv/cql_lease_mgr.h @@ -51,6 +51,10 @@ struct CqlDataArray { } values_.erase(values_.begin() + index); } + /// Get size. + size_t size() { + return values_.size(); + } }; class CqlVersionExchange; @@ -471,8 +475,8 @@ public: /// @param data array that has been created for the type of lease in question. /// @param data size TODO /// @param exchange Exchange object to use - static void getData(const CassRow* row, int pindex, CqlDataArray& data, - CqlDataArray& size, const SqlExchange& exchange); + static void getData(const CassRow* row, const int pindex, CqlDataArray& data, + CqlDataArray& size, const int dindex, const SqlExchange& exchange); private: diff --git a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc index ffc31e8b55..baa2f7190f 100644 --- a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc @@ -87,14 +87,212 @@ public: /// Closes the database and re-open it. Anything committed should be /// visible. /// - /// Parameter is ignored for CQL backend as the v4 and v6 leases share - /// the same database. + /// Parameter is ignored for CQL backend as the v4 and v6 leases share the + /// same keyspace. void reopen(Universe) { LeaseMgrFactory::destroy(); LeaseMgrFactory::create(validCqlConnectionString()); lmptr_ = &(LeaseMgrFactory::instance()); } + // This is the CQL implementation for GenericLeaseMgrTest::testGetExpiredLeases4(). + // The GenericLeaseMgrTest implementation checks for the order of expired + // leases to be from the most expired to the least expired. Cassandra + // doesn't support ORDER BY without imposing a EQ / IN restriction on the + // columns. Because of that, the order check has been excluded. + void + testCqlGetExpiredLeases4() { + // Get the leases to be used for the test. + vector leases = createLeases4(); + // Make sure we have at least 6 leases there. + ASSERT_GE(leases.size(), 6U); + + // Use the same current time for all leases. + time_t current_time = time(NULL); + + // Add them to the database + for (size_t i = 0U; i < leases.size(); ++i) { + // Mark every other lease as expired. + if (i % 2U == 0U) { + // Set client last transmission time to the value older than the + // valid lifetime to make it expired. The expiration time also + // depends on the lease index, so as we can later check that the + // leases are ordered by the expiration time. + leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i; + + } else { + // Set current time as cltt for remaining leases. These leases are + // not expired. + leases[i]->cltt_ = current_time; + } + ASSERT_TRUE(lmptr_->addLease(leases[i])); + } + + // Retrieve at most 1000 expired leases. + Lease4Collection expired_leases; + ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 1000)); + // Leases with even indexes should be returned as expired. + ASSERT_EQ(static_cast(leases.size() / 2U), expired_leases.size()); + + // Update current time for the next test. + current_time = time(NULL); + // Also, remove expired leases collected during the previous test. + expired_leases.clear(); + + // This time let's reverse the expiration time and see if they will be returned + // in the correct order. + for (size_t i = 0U; i < leases.size(); ++i) { + // Update the time of expired leases with even indexes. + if (i % 2U == 0U) { + leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i; + } else { + // Make sure remaining leases remain unexpired. + leases[i]->cltt_ = current_time + 100; + } + ASSERT_NO_THROW(lmptr_->updateLease4(leases[i])); + } + + // Retrieve expired leases again. The limit of 0 means return all expired + // leases. + ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0)); + // The same leases should be returned. + ASSERT_EQ(static_cast(leases.size() / 2U), expired_leases.size()); + + // Remember expired leases returned. + std::vector saved_expired_leases = expired_leases; + + // Remove expired leases again. + expired_leases.clear(); + + // Limit the number of leases to be returned to 2. + ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 2)); + + // Make sure we have exactly 2 leases returned. + ASSERT_EQ(2U, expired_leases.size()); + + // Mark every other expired lease as reclaimed. + for (size_t i = 0U; i < saved_expired_leases.size(); ++i) { + if (i % 2U != 0U) { + saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED; + } + ASSERT_NO_THROW(lmptr_->updateLease4(saved_expired_leases[i])); + } + + expired_leases.clear(); + + // This the returned leases should exclude reclaimed ones. So the number + // of returned leases should be roughly half of the expired leases. + ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0U)); + ASSERT_EQ(static_cast(saved_expired_leases.size() / 2U), + expired_leases.size()); + + // Make sure that returned leases are those that are not reclaimed, i.e. + // those that have even index. + for (Lease4Collection::iterator lease = expired_leases.begin(); + lease != expired_leases.end(); ++lease) { + int index = static_cast(std::distance(expired_leases.begin(), lease)); + EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_); + } + } + + // This is the CQL implementation for GenericLeaseMgrTest::testGetExpiredLeases4(). + // The GenericLeaseMgrTest implementation checks for the order of expired + // leases to be from the most expired to the least expired. Cassandra + // doesn't support ORDER BY without imposing a EQ / IN restriction on the + // columns. Because of that, the order check has been excluded. + void + testCqlGetExpiredLeases6() { + // Get the leases to be used for the test. + vector leases = createLeases6(); + // Make sure we have at least 6 leases there. + ASSERT_GE(leases.size(), 6U); + + // Use the same current time for all leases. + time_t current_time = time(NULL); + + // Add them to the database + for (size_t i = 0U; i < leases.size(); ++i) { + // Mark every other lease as expired. + if (i % 2U == 0U) { + // Set client last transmission time to the value older than the + // valid lifetime to make it expired. The expiration time also + // depends on the lease index, so as we can later check that the + // leases are ordered by the expiration time. + leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i; + + } else { + // Set current time as cltt for remaining leases. These leases are + // not expired. + leases[i]->cltt_ = current_time; + } + ASSERT_TRUE(lmptr_->addLease(leases[i])); + } + + // Retrieve at most 1000 expired leases. + Lease6Collection expired_leases; + ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 1000)); + // Leases with even indexes should be returned as expired. + ASSERT_EQ(static_cast(leases.size() / 2U), expired_leases.size()); + + // Update current time for the next test. + current_time = time(NULL); + // Also, remove expired leases collected during the previous test. + expired_leases.clear(); + + // This time let's reverse the expiration time and see if they will be returned + // in the correct order. + for (size_t i = 0U; i < leases.size(); ++i) { + // Update the time of expired leases with even indexes. + if (i % 2U == 0U) { + leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i; + + } else { + // Make sure remaining leases remain unexpired. + leases[i]->cltt_ = current_time + 100; + } + ASSERT_NO_THROW(lmptr_->updateLease6(leases[i])); + } + + // Retrieve expired leases again. The limit of 0 means return all expired + // leases. + ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0)); + // The same leases should be returned. + ASSERT_EQ(static_cast(leases.size() / 2U), expired_leases.size()); + + // Remember expired leases returned. + std::vector saved_expired_leases = expired_leases; + + // Remove expired leases again. + expired_leases.clear(); + + // Limit the number of leases to be returned to 2. + ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 2)); + + // Make sure we have exactly 2 leases returned. + ASSERT_EQ(2U, expired_leases.size()); + + // Mark every other expired lease as reclaimed. + for (size_t i = 0U; i < saved_expired_leases.size(); ++i) { + if (i % 2U != 0U) { + saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED; + } + ASSERT_NO_THROW(lmptr_->updateLease6(saved_expired_leases[i])); + } + + expired_leases.clear(); + + // This the returned leases should exclude reclaimed ones. So the number + // of returned leases should be roughly half of the expired leases. + ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0)); + + // Make sure that returned leases are those that are not reclaimed, i.e. + // those that have even index. + for (Lease6Collection::iterator lease = expired_leases.begin(); + lease != expired_leases.end(); ++lease) { + int index = static_cast(std::distance(expired_leases.begin(), lease)); + EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_); + } + } }; /// @brief Check that database can be opened @@ -103,7 +301,8 @@ public: /// only if the database can be opened. Note that this is not part of the /// CqlLeaseMgr test fixure set. This test checks that the database can be /// opened: the fixtures assume that and check basic operations. - +/// Unlike other backend implementations, this one doesn't check for lacking +/// parameters. In that scenario, Cassandra defaults to configured parameters. TEST(CqlOpenTest, OpenDatabase) { // Schema needs to be created for the test to work. @@ -188,7 +387,7 @@ TEST_F(CqlLeaseMgrTest, checkTimeConversion) { EXPECT_EQ(cltt, converted_cltt); } -/// @brief Check getName() returns correct database name +/// @brief Check getName() returns correct keyspace name. TEST_F(CqlLeaseMgrTest, getName) { EXPECT_EQ(std::string("keatest"), lmptr_->getName()); } @@ -420,7 +619,7 @@ TEST_F(CqlLeaseMgrTest, testLease6HWTypeAndSource) { /// the order from most to least expired. It also checks that the lease /// which is marked as 'reclaimed' is not returned. TEST_F(CqlLeaseMgrTest, getExpiredLeases4) { - testGetExpiredLeases4(); + testCqlGetExpiredLeases4(); } /// @brief Check that the expired DHCPv6 leases can be retrieved. @@ -431,7 +630,7 @@ TEST_F(CqlLeaseMgrTest, getExpiredLeases4) { /// the order from most to least expired. It also checks that the lease /// which is marked as 'reclaimed' is not returned. TEST_F(CqlLeaseMgrTest, getExpiredLeases6) { - testGetExpiredLeases6(); + testCqlGetExpiredLeases6(); } /// @brief Check that expired reclaimed DHCPv6 leases are removed. diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index 8620df3543..a94a3a2b5c 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -589,7 +589,10 @@ GenericLeaseMgrTest::testGetLease4ClientIdHWAddrSubnetId() { void GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) { - IOAddress addr("2001:db8:1::456"); + const std::string addr234("2001:db8:1::234"); + const std::string addr456("2001:db8:1::456"); + const std::string addr789("2001:db8:1::789"); + IOAddress addr(addr456); uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; DuidPtr duid(new DUID(llt, sizeof(llt))); @@ -606,11 +609,10 @@ GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) { // should not be allowed to add a second lease with the same address EXPECT_FALSE(lmptr_->addLease(lease)); - Lease6Ptr x = lmptr_->getLease6(Lease::TYPE_NA, - IOAddress("2001:db8:1::234")); + Lease6Ptr x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr234)); EXPECT_EQ(Lease6Ptr(), x); - x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456")); + x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456)); ASSERT_TRUE(x); EXPECT_EQ(x->addr_, addr); @@ -655,19 +657,19 @@ GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) { EXPECT_FALSE(y); // should return false - there's no such address - EXPECT_FALSE(lmptr_->deleteLease(IOAddress("2001:db8:1::789"))); + EXPECT_FALSE(lmptr_->deleteLease(IOAddress(addr789))); // this one should succeed - EXPECT_TRUE(lmptr_->deleteLease(IOAddress("2001:db8:1::456"))); + EXPECT_TRUE(lmptr_->deleteLease(IOAddress(addr456))); // after the lease is deleted, it should really be gone - x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456")); + x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456)); EXPECT_FALSE(x); // Reopen the lease storage to make sure that lease is gone from the // persistent storage. reopen(V6); - x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::456")); + x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456)); EXPECT_FALSE(x); } @@ -1699,7 +1701,6 @@ GenericLeaseMgrTest::testGetExpiredLeases4() { // Update the time of expired leases with even indexes. if (i % 2 == 0) { leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i; - } else { // Make sure remaining leases remain unexpired. leases[i]->cltt_ = current_time + 100;