Browse Source

solved Cassandra unit tests & added documentation

Andrei Pavel 9 years ago
parent
commit
08343129b4

+ 2 - 2
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

+ 128 - 4
doc/guide/admin.xml

@@ -94,10 +94,10 @@
         <listitem>
           <simpara>
             <command>lease-dump</command> &mdash;
-            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.
           </simpara>
         </listitem>
       </itemizedlist>
@@ -128,6 +128,14 @@
             database.
           </simpara>
         </listitem>
+
+        <listitem>
+          <simpara>
+            <command>cql</command> &mdash;
+            Lease information is stored in a CQL database.
+          </simpara>
+        </listitem>
+
       </itemizedlist>
 
       Additional parameters may be needed, depending on your setup
@@ -484,6 +492,122 @@ $ <userinput>kea-admin lease-init pgsql -u <replaceable>database-user</replaceab
         </para>
       </section>
     </section> <!-- end of PostgreSQL sections -->
+
+    <section>
+      <title>CQL</title>
+
+      <para>
+        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.
+      </para>
+
+    <section id="cql-database-create">
+      <title>First Time Creation of Kea Database</title>
+
+      <para>
+        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:
+        <command>kea-admin</command> is not able to do this for you.
+      </para>
+
+      <para>
+        To create the database:
+        <orderedlist>
+          <listitem>
+            <para>
+              Export CQLSH_HOST environemnt variable:
+<screen>
+$ <userinput>export CQLSH_HOST=localhost</userinput>
+</screen>
+              </para>
+            </listitem>
+          <listitem>
+            <para>
+              Log into CQL:
+<screen>
+$ <userinput>cqlsh</userinput>
+cql>
+</screen>
+              </para>
+            </listitem>
+
+          <listitem>
+            <para>
+              Create the CQL keyspace:
+<screen>
+cql> <userinput>CREATE KEYSPACE keyspace-name WITH replication = {'class' : 'SimpleStrategy','replication_factor' : 1};</userinput>
+</screen>
+              (<replaceable>keyspace-name</replaceable> is the name you have
+              chosen for the keyspace)
+            </para>
+          </listitem>
+
+         <listitem>
+            <para>
+              At this point, you may elect to create the database tables.
+              (Alternatively, you can exit CQL and create the tables using the
+              <command>kea-admin</command> tool, as explained below)  To do this:
+<screen>
+<userinput>cqslh -k <replaceable>keyspace-name</replaceable> -f <replaceable>path-to-kea</replaceable>/share/kea/scripts/cql/dhcpdb_create.cql</userinput>
+</screen>
+              (<replaceable>path-to-kea</replaceable> is the location where you
+              installed Kea)
+            </para>
+          </listitem>
+        </orderedlist>
+      </para>
+
+      <para>
+        If you elected not to create the tables in step 4, you can do
+        so now by running the <command>kea-admin</command> tool:
+<screen>
+$ <userinput>kea-admin lease-init cql -n <replaceable>database-name</replaceable></userinput>
+</screen>
+        (Do not do this if you did create the tables in step 4.)
+        <command>kea-admin</command> 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 <command>kea-admin</command>)
+      </para>
+    </section>
+
+    <section id="cql-upgrade">
+      <title>Upgrading a CQL Database from an Earlier Version of Kea</title>
+
+      <para>
+        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 <command>kea-admin lease-upgrade</command>
+        command.
+      </para>
+
+      <para>
+        To check the current version of the database, use the following command:
+<screen>
+$ <userinput>kea-admin lease-version cql -n <replaceable>database-name</replaceable></userinput>
+</screen>
+        (See <xref linkend="kea-database-version"/> 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.
+      </para>
+
+      <para>
+        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:
+<screen>
+$ <userinput>kea-admin lease-upgrade cql -n <replaceable>database-name</replaceable></userinput>
+</screen>
+      </para>
+    </section>
+  </section> <!-- end of CQL sections -->
+
     <section>
       <title>Limitations related to the use of the SQL databases</title>
 

+ 6 - 4
doc/guide/dhcp4-srv.xml

@@ -301,8 +301,8 @@ be followed by a comma and another object definition.</para>
 <section>
   <title>Lease Storage</title>
   <para>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.</para>
+  Currently there are four database backends available:  memfile (which is the
+  default backend), MySQL, PostgreSQL and CQL.</para>
 <section>
   <title>Memfile, Basic Storage for Leases</title>
 
@@ -441,8 +441,10 @@ be followed by a comma and another object definition.</para>
 "Dhcp4": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 </screen>
   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 <xref linkend="mysql-database-create"/>
-  or <xref linkend="pgsql-database-create"/>).
+  name used when the lease database was created
+  (see <xref linkend="mysql-database-create"/>,
+  <xref linkend="pgsql-database-create"/> or
+  <xref linkend="cql-database-create"/>).
 <screen>
 "Dhcp4": { "lease-database": { <userinput>"name": "<replaceable>database-name</replaceable>" </userinput>, ... }, ... }
 </screen>

+ 9 - 7
doc/guide/dhcp6-srv.xml

@@ -306,8 +306,8 @@ be followed by a comma and another object definition.</para>
 <section>
   <title>Lease Storage</title>
   <para>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.</para>
+  Currently there are four database backends available:  memfile (which is the
+  default backend), MySQL, PostgreSQL and CQL.</para>
 <section>
   <title>Memfile, Basic Storage for Leases</title>
 
@@ -435,13 +435,15 @@ be followed by a comma and another object definition.</para>
 
   <para>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.
 <screen>
 "Dhcp6": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 </screen>
   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 <xref linkend="mysql-database-create"/>
-  or <xref linkend="pgsql-database-create"/>).
+  name used when the lease database was created
+  (see <xref linkend="mysql-database-create"/>,
+  <xref linkend="pgsql-database-create"/>
+  or <xref linkend="cql-database-create"/>).
 <screen>
 "Dhcp6": { "lease-database": { <userinput>"name": "<replaceable>database-name</replaceable>" </userinput>, ... }, ... }
 </screen>
@@ -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.
             </para>
           </listitem>
 

+ 1 - 1
doc/guide/install.xml

@@ -367,7 +367,7 @@ Debian and Ubuntu:
 
         <para>
           <userinput>./configure</userinput> when it succeeds displays a report
-          with the building parameters. This report is saved into 
+          with the building parameters. This report is saved into
           <filename>config.report</filename> and embedded into executable
           binaries, e.g., <userinput>kea-dhcp4</userinput>.
         </para>

+ 8 - 0
doc/guide/intro.xml

@@ -88,6 +88,14 @@
     built without PostgreSQL support.
             </simpara>
         </listitem>
+
+        <listitem>
+            <simpara>
+	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.
+            </simpara>
+        </listitem>
       </itemizedlist>
     </section>
 

+ 2 - 2
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
 

+ 1 - 1
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();

+ 130 - 47
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<void*>(&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<void*>(&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_t *>(size.values_[pindex]));
+        CqlFunctions[type].sqlGetFunction_(value, data.values_[dindex],
+            reinterpret_cast<size_t *>(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<void*>(&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<int64_t>(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<uint32_t>(max_leases) :
-        std::numeric_limits<uint32_t>::max();
+    uint32_t limit = max_leases > 0 ? static_cast<int32_t>(max_leases) :
+        std::numeric_limits<int32_t>::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<void*>(&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<void*>(&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<void*>(&version));
@@ -1859,9 +1943,8 @@ CqlLeaseMgr::getVersion() const {
         // minor: uint32_t
         data.add(reinterpret_cast<void*>(&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_);
         }
     }
 

+ 6 - 2
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:
 

+ 205 - 6
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<Lease4Ptr> 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<size_t>(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<size_t>(leases.size() / 2U), expired_leases.size());
+
+        // Remember expired leases returned.
+        std::vector<Lease4Ptr> 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<size_t>(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<int>(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<Lease6Ptr> 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<size_t>(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<size_t>(leases.size() / 2U), expired_leases.size());
+
+        // Remember expired leases returned.
+        std::vector<Lease6Ptr> 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<int>(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.

+ 10 - 9
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;