]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
Cassandra Host Data Source
authorAndrei Pavel <andrei.pavel@qualitance.com>
Wed, 14 Dec 2016 13:54:32 +0000 (15:54 +0200)
committerAndrei Pavel <andrei.pavel@qualitance.com>
Wed, 14 Dec 2016 13:54:32 +0000 (15:54 +0200)
Added src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh to include host_reservations table in Cassandra.
Updated documentation to indicate that Cassandra now supports host reservations.
Added src/lib/dhcpsrv/cql_host_data_source.cc and cql_host_data_source.h.
Fixed a bug in CfgOption::mergeInternal() where formatted_value_ was not taken into consideration when merging, this is needed for merging the denormalized results on host retrieval in Cassandra. The method is not used elsewhere so there is no regression impact.
Added Cassandra support in HostDataSourceFactory.
Implemented a todo in mysql_host_data_source.cc
Added cql_host_data_source_unittest.cc
Functionality that Cassandra is not capable of like ORDER BY clause is
adjusted in generic_host_data_source_unittest.cc.

19 files changed:
doc/guide/admin.xml
doc/guide/dhcp4-srv.xml
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cql_connection.h
src/lib/dhcpsrv/cql_host_data_source.cc [new file with mode: 0644]
src/lib/dhcpsrv/cql_host_data_source.h [new file with mode: 0644]
src/lib/dhcpsrv/dhcpsrv_messages.mes
src/lib/dhcpsrv/host.h
src/lib/dhcpsrv/host_data_source_factory.cc
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
src/share/database/scripts/cql/.gitignore
src/share/database/scripts/cql/dhcpdb_create.cql
src/share/database/scripts/cql/dhcpdb_drop.cql
src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in [new file with mode: 0644]

index a8d8324851b429caf056e276188fc5f447622ee4..f2b7c88e9b86b7f130e22ae36efc3cd747a202e8 100644 (file)
@@ -72,7 +72,7 @@
         <listitem>
           <simpara>
             <command>lease-init</command> &mdash;
-            Initializes a new lease database. This is useful during a new 
+            Initializes a new lease database. This is useful during a new
             Kea installation. The database is initialized to the
             latest version supported by the version of the software being
             installed.
     <para>
       <table frame="all" id="backends">
         <title>List of available backends</title>
-        <tgroup cols='2'>
+        <tgroup cols='5'>
           <colspec colname='feature'/>
           <colspec colname='memfile'/>
           <colspec colname='mysql'/>
           <colspec colname='pgsql'/>
-          <colspec colname='cql'/>
+          <colspec colname='cql' colwidth='1.5*'/>
           <thead>
             <row>
               <entry>Feature</entry>
               <entry>no</entry>
               <entry>yes</entry>
               <entry>yes</entry>
-              <entry>no</entry>
+              <entry>yes</entry>
             </row>
 
             <row>
@@ -591,9 +591,8 @@ $ <userinput>kea-admin lease-upgrade pgsql -u <replaceable>database-user</replac
         Cassandra, or Cassandra Query Language (CQL), is the newest backend
         added to Kea. Since it was added recently and has not undergone as much
         testing as other backends, it is considered experimental: please use
-        with caution. The CQL backend is currently able to store leases only. The
-        ability to store host reservations will likely be added some time in the
-        future.
+        with caution. The Casandra backend is currently able to store leases,
+        host reservations and options defined on a per host basis.
       </para>
 
       <para>
index c2798e7a023faa96117332307d5314f2e3237111..b59f58c2d47a268f9f32c8202d07df47ee5b2817 100644 (file)
@@ -2973,8 +2973,8 @@ It is merely echoed by the server
     with classification using expressions.</para>
     </section>
 
-    <section id="reservations4-mysql-pgsql">
-      <title>Storing Host Reservations in MySQL or PostgreSQL</title>
+    <section id="reservations4-mysql-pgsql-cql">
+      <title>Storing Host Reservations in MySQL, PostgreSQL or CQL (Cassandra)</title>
 
       <para>
         It is possible to store host reservations in MySQL or PostgreSQL database. See
@@ -2990,13 +2990,6 @@ It is merely echoed by the server
       arbitrarily set to 4096 bytes.</simpara></note>
     </section>
 
-    <section id="reservations4-cql">
-      <title>Storing host reservations in CQL (Cassandra)</title>
-      <para>Kea currently does not support storing reservations in
-      Cassandra (CQL).</para>
-    </section>
-
-
     <section id="reservations4-tuning">
       <title>Fine Tuning DHCPv4 Host Reservation</title>
 
index b62f9231371a1c5b8623c3abd9bac84a58599a57..c102851892377375ef4c7987099c61bc368a8342 100644 (file)
@@ -144,6 +144,7 @@ endif
 if HAVE_CQL
 libkea_dhcpsrv_la_SOURCES += cql_connection.cc cql_connection.h
 libkea_dhcpsrv_la_SOURCES += cql_exchange.cc cql_exchange.h
+libkea_dhcpsrv_la_SOURCES += cql_host_data_source.cc cql_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += cql_lease_mgr.cc cql_lease_mgr.h
 endif
 
index 8947dd5335d3e063130cddab64cd62ce7a43f15a..e8250cbcbde3ec3503d7ae46026eaf33f0792702 100644 (file)
@@ -164,9 +164,9 @@ CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
             // If there is no such option in the destination container,
             // add one.
             if (std::distance(range.first, range.second) == 0) {
-                dest_container.addItem(OptionDescriptor(src_opt->option_,
-                                                        src_opt->persistent_),
-                                       *it);
+                 dest_container.addItem(OptionDescriptor(
+                    src_opt->option_, src_opt->persistent_,
+                    src_opt->formatted_value_), *it);
             }
         }
     }
index 7abcfbc5e3a4c98e30d3f15ed0aee85bd6c00c0a..9adcbc9c9083d575209589af77bfce4dcdce7bdf 100644 (file)
@@ -49,7 +49,7 @@ const uint32_t CQL_DRIVER_VERSION_MAJOR = CASS_VERSION_MAJOR;
 const uint32_t CQL_DRIVER_VERSION_MINOR = CASS_VERSION_MINOR;
 
 /// Define CQL schema version: 1.0
-const uint32_t CQL_SCHEMA_VERSION_MAJOR = 1;
+const uint32_t CQL_SCHEMA_VERSION_MAJOR = 2;
 const uint32_t CQL_SCHEMA_VERSION_MINOR = 0;
 
 /// @brief Common CQL connector pool
diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc
new file mode 100644 (file)
index 0000000..e6b86c5
--- /dev/null
@@ -0,0 +1,1982 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: Andrei Pavel <andrei.pavel@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//           http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <config.h>
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cql_host_data_source.h>
+#include <dhcpsrv/cql_lease_mgr.h>
+#include <dhcpsrv/db_exceptions.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <util/buffer.h>
+#include <util/optional_value.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/unordered_map.hpp>
+
+#include <openssl/md5.h>
+
+#include <list>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Host identifier consisting of DUID or hardware address
+typedef std::vector<uint8_t> HostIdentifier;
+
+/// @brief Host identifier converted to Cassandra data type
+typedef std::vector<cass_byte_t> CassHostIdentifier;
+
+/// @brief Host identifier converted to Cassandra data type
+typedef std::vector<cass_byte_t> CassOptionBuffer;
+
+/// @brief key for @ref HostMap containing objects which uniquely identify a
+///     host: host identifier, host identifier type, subnets for IPv4 and IPv6
+typedef boost::tuple<HostIdentifier,
+                     Host::IdentifierType,
+                     SubnetID,
+                     SubnetID,
+                     asiolink::IOAddress>
+    HostKey;
+
+/// @brief Map used to merge reservations and options into a single host on
+///     retrieve from database
+typedef boost::unordered_map<HostKey, HostPtr, boost::hash<HostKey> > HostMap;
+
+/// @brief Maximum size of an IPv4 address represented as a text string. 12
+///     digits plus 3 full stops (dots).
+static const size_t ADDRESS4_TEXT_MAX_LENGTH = 15U;
+
+/// @brief Maximum size of an IPv6 address represented as a text string. 32
+///     hexadecimal characters written in 8 groups of four, plus 7 colon
+///     separators.
+static const size_t ADDRESS6_TEXT_MAX_LENGTH = 39U;
+
+/// @brief Maximum length of classes stored in a host_ipv4/6_client_classes
+///     column.
+static const size_t CLIENT_CLASSES_MAX_LENGTH = 255U;
+
+/// @brief Maximum length of the hostname stored in DNS. This length is
+///     restricted by the length of the domain-name carried in the Client FQDN
+///     Option (see RFC4702 and RFC4704).
+static const size_t HOSTNAME_MAX_LENGTH = 255U;
+
+/// @brief Maximum length of option value
+static const size_t OPTION_VALUE_MAX_LENGTH = 4096U;
+
+/// @brief Maximum length of option value specified in textual format
+static const size_t OPTION_FORMATTED_VALUE_MAX_LENGTH = 8192U;
+
+/// @brief Maximum length of option space name
+static const size_t OPTION_SPACE_MAX_LENGTH = 128U;
+
+/// @brief Numeric value representing the last supported identifier. This value
+///     is used to validate whether the identifier type stored in a database is
+///     within bounds of supported identifiers.
+static const cass_int8_t MAX_IDENTIFIER_TYPE =
+    static_cast<cass_int8_t>(Host::IDENT_CIRCUIT_ID);
+
+/// @{
+/// @brief Invalid values in the Cassandra database
+static const char NULL_RESERVED_IPV6_PREFIX_ADDRESS[] = "";
+static const cass_int32_t NULL_RESERVED_IPV6_PREFIX_LENGTH = 0;
+static const cass_int32_t NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE = -1;
+static const cass_int32_t NULL_IAID = -1;
+static const cass_int32_t NULL_OPTION_UNIVERSE = -1;
+static const cass_int32_t NULL_OPTION_CODE = -1;
+static const CassOptionBuffer NULL_OPTION_VALUE = CassOptionBuffer();
+static const char NULL_OPTION_FORMATTED_VALUE[] = "";
+static const char NULL_OPTION_SPACE[] = "";
+static const cass_bool_t NULL_OPTION_IS_PERSISTENT = cass_false;
+static const char NULL_OPTION_CLIENT_CLASS[] = "";
+static const cass_int32_t NULL_OPTION_SUBNET_ID = -1;
+/// @}
+
+/// @brief Invalid reservation used to check for an invalid IPv6Resrv formed
+///     from database values.
+static const IPv6Resrv NULL_IPV6_RESERVATION =
+    IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("::"), 128);
+
+}  // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @{
+/// @brief Statement parameters
+// INSERT_HOST
+static const char* INSERT_HOST_PARAMS[] = {
+    static_cast<const char*>("id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("host_ipv4_address"),
+    static_cast<const char*>("hostname"),
+    static_cast<const char*>("host_ipv4_client_classes"),
+    static_cast<const char*>("host_ipv6_client_classes"),
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    static_cast<const char*>("reserved_ipv6_prefix_length"),
+    static_cast<const char*>("reserved_ipv6_prefix_address_type"),
+    static_cast<const char*>("iaid"),
+    static_cast<const char*>("option_universe"),
+    static_cast<const char*>("option_code"),
+    static_cast<const char*>("option_value"),
+    static_cast<const char*>("option_formatted_value"),
+    static_cast<const char*>("option_space"),
+    static_cast<const char*>("option_is_persistent"),
+    static_cast<const char*>("option_client_class"),
+    static_cast<const char*>("option_subnet_id"),
+    NULL};
+// GET_HOST_BY_HOST_ID
+static const char* GET_HOST_BY_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV4_ADDRESS
+static const char* GET_HOST_BY_IPV4_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_address"),
+    NULL};
+// GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID
+static const char* GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID,
+static const char* GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID_PARAMS[] = {
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("host_identifier"),
+    static_cast<const char*>("host_identifier_type"),
+    NULL};
+// GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS,
+static const char* GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv4_subnet_id"),
+    static_cast<const char*>("host_ipv4_address"),
+    NULL};
+// GET_HOST_BY_IPV6_PREFIX
+static const char* GET_HOST_BY_IPV6_PREFIX_PARAMS[] = {
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    static_cast<const char*>("reserved_ipv6_prefix_length"),
+    NULL};
+// GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS
+static const char* GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS_PARAMS[] = {
+    static_cast<const char*>("host_ipv6_subnet_id"),
+    static_cast<const char*>("reserved_ipv6_prefix_address"),
+    NULL};
+// GET_VERSION
+static const char* GET_VERSION_PARAMS[] = {NULL};
+/// @}
+
+/// @brief Prepared CQL statements used by the backend to insert and retrieve
+///     hosts from the database.
+static CqlTaggedStatement tagged_statements[] = {
+    // INSERT_HOST
+    {INSERT_HOST_PARAMS, "INSERT_HOST",
+     "INSERT INTO host_reservations ( "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     ") "
+     "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
+     "IF NOT EXISTS "
+    },
+
+    // GET_HOST_BY_HOST_ID
+    {GET_HOST_BY_HOST_ID_PARAMS, "GET_HOST_BY_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_ADDRESS
+    {GET_HOST_BY_IPV4_ADDRESS_PARAMS, "GET_HOST_BY_IPV4_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID
+    {GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID_PARAMS,
+     "GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_subnet_id = ? "
+     "AND host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID
+    {GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID_PARAMS,
+     "GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv6_subnet_id = ? "
+     "AND host_identifier = ? "
+     "AND host_identifier_type = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS
+    {GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS_PARAMS,
+     "GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv4_subnet_id = ? "
+     "AND host_ipv4_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_PREFIX
+    {GET_HOST_BY_IPV6_PREFIX_PARAMS, "GET_HOST_BY_IPV6_PREFIX",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE reserved_ipv6_prefix_address = ? "
+     "AND reserved_ipv6_prefix_length = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS
+    {GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS_PARAMS,
+     "GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS",
+     "SELECT "
+     "id, "
+     "host_identifier, "
+     "host_identifier_type, "
+     "host_ipv4_subnet_id, "
+     "host_ipv6_subnet_id, "
+     "host_ipv4_address, "
+     "hostname, "
+     "host_ipv4_client_classes, "
+     "host_ipv6_client_classes, "
+     "reserved_ipv6_prefix_address, "
+     "reserved_ipv6_prefix_length, "
+     "reserved_ipv6_prefix_address_type, "
+     "iaid, "
+     "option_universe, "
+     "option_code, "
+     "option_value, "
+     "option_formatted_value, "
+     "option_space, "
+     "option_is_persistent, "
+     "option_client_class, "
+     "option_subnet_id "
+     "FROM host_reservations "
+     "WHERE host_ipv6_subnet_id = ? "
+     "AND reserved_ipv6_prefix_address = ? "
+     "ALLOW FILTERING "
+    },
+
+    // GET_VERSION
+    {GET_VERSION_PARAMS, "GET_VERSION",
+     "SELECT version, minor FROM schema_version"},
+
+    // End of list sentinel
+    {NULL, NULL, NULL}};  // tagged_statements
+
+/// @brief Provides mechanisms for sending and retrieving data from the
+///     host_reservations table.
+class CqlHostExchange : public virtual CqlExchange {
+public:
+    /// @brief Statement Tags
+    ///
+    /// The contents of the enum are indexes into the list of CQL statements
+    enum StatementIndex {
+        // Inserts all options belonging to any reservation from a single host.
+        INSERT_HOST,
+        // Retrieves host information, IPv6 reservations and both IPv4 and IPv6
+        // options associated with the host.
+        GET_HOST_BY_HOST_ID,
+        // Retrieves host information along with the IPv4 options associated
+        // with it.
+        GET_HOST_BY_IPV4_ADDRESS,
+        // Retrieves host information and IPv4 options using subnet identifier
+        // and client's identifier (i.e. hardware address or DUID).
+        GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID,
+        // Retrieves host information, IPv6 reservations and IPv6 options
+        // associated with a host using subnet identifier and client's
+        // identifier (i.e. hardware address or DUID).
+        GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID,
+        // Retrieves host information and IPv4 options for the host using subnet
+        // identifier and IPv4 reservation.
+        GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS,
+        // Retrieves host information, IPv6 reservations and IPv6 options
+        // associated with a host using prefix and prefix length. This query
+        // returns host information for a single host. However, multiple rows
+        // are returned due to left joining IPv6 reservations and IPv6 options.
+        // The number of rows returned is multiplication of number of existing
+        // IPv6 reservations and IPv6 options.
+        GET_HOST_BY_IPV6_PREFIX,
+        // Retrieves host information and IPv6 options for the host using subnet
+        // identifier and IPv6 reservation.
+        GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS,
+        // Get CQL schema version.
+        GET_VERSION,
+        // Number of statements
+        NUM_STATEMENTS
+    };
+
+    /// @brief Constructor
+    ///
+    /// Specifies table columns.
+    CqlHostExchange();
+
+    /// @brief Virtual destructor.
+    virtual ~CqlHostExchange();
+
+    /// @brief Binds member variables to data array to receive @ref Host data.
+    ///
+    /// Creates a bind array to receive @ref Host data from the Cassandra
+    /// database. After data is successfully received, @ref retrieve() can be
+    /// called to retrieve the @ref Host object. Called in @ref
+    /// CqlExchange::executeRead().
+    ///
+    /// @param data array of objects representing data being retrieved
+    /// @param statement_index prepared statement being executed; defaults to an
+    ///     invalid statement
+    virtual void createBindForReceive(CqlDataArray& data,
+                                      const int statement_index = -1);
+
+    /// @brief Binds @ref Host to data array to send data to the Cassandra
+    ///     database.
+    ///
+    /// Fills in the bind array for sending data stored in the @ref Host object
+    /// to the database.
+    ///
+    /// @param host @ref Host object being added to the Cassandra database
+    /// @param subnet_id identifier of the subnet to which the host belongs
+    /// @param reservation reservation belonging to the host
+    /// @param option_space option space
+    /// @param option_descriptor structure used to hold option information
+    /// @param data array being filled with data from to the Host object
+    void createBindForSend(const HostPtr& host,
+                           const OptionalValue<SubnetID>& subnet_id,
+                           const IPv6Resrv* const reservation,
+                           const std::string& option_space,
+                           const OptionDescriptor& option_descriptor,
+                           CqlDataArray& data);
+
+    /// @brief Create unique hash for storage in table id.
+    ///
+    /// Hash function used for creating a pseudo-unique hash from member
+    /// values which uniquely determine an entry in the table. Uses OpenSSL's
+    /// MD5 implementation.
+    ///
+    /// The primary key aggregates: host_ipv4_subnet_id, host_ipv6_subnet_id,
+    /// host_ipv4_address, reserved_ipv6_prefix_address,
+    /// reserved_ipv6_prefix_length, option_code, option_space.
+    cass_int64_t hashIntoId() const;
+
+    /// @brief Copy received data into Host object
+    ///
+    /// Copies information about the host into a newly created @ref Host object
+    /// Called in @ref executeRead after @ref createBindForReceive().
+    ///
+    /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
+    /// @ref Host object returned.
+    virtual void* retrieve();
+
+    /// @brief Creates IPv6 reservation from the data contained in the
+    /// currently processed row.
+    ///
+    /// Called after createBindForReceive().
+    ///
+    /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
+    const IPv6Resrv retrieveReservation() const;
+
+    /// @brief Retrieves option from members.
+    OptionDescriptorPtr retrieveOption() const;
+
+private:
+    /// Pointer to Host object holding information being inserted into database.
+    HostPtr host_;
+
+    /// @brief Primary key. Aggregates: host_identifier, host_identifier_type,
+    /// reserved_ipv6_prefix_address, reserved_ipv6_prefix_length, option_code,
+    /// option_space.
+    cass_int64_t id_;
+
+    /// @brief Client's identifier (e.g. DUID, HW address) in binary format
+    CassHostIdentifier host_identifier_;
+
+    /// @brief Type of the identifier in the host_identifier_
+    /// This value corresponds to the @ref Host::IdentifierType value.
+    cass_int32_t host_identifier_type_;
+
+    /// @brief IPv4 subnet identifier
+    cass_int32_t host_ipv4_subnet_id_;
+
+    /// @brief Ipv6 subnet identifier
+    cass_int32_t host_ipv6_subnet_id_;
+
+    /// @brief Reserved IPv4 address
+    cass_int32_t host_ipv4_address_;
+
+    /// @brief Name reserved for the host
+    std::string hostname_;
+
+    /// @brief A string holding comma separated list of IPv4 client classes
+    std::string host_ipv4_client_classes_;
+
+    /// @brief A string holding comma separated list of IPv6 client classes
+    std::string host_ipv6_client_classes_;
+
+    /// @brief Address belonging to the reserved IPv6 prefix
+    std::string reserved_ipv6_prefix_address_;
+
+    /// @brief Length of the reserved IPv6 prefix
+    cass_int32_t reserved_ipv6_prefix_length_;
+
+    /// @brief Reserver IPv6 prefix type
+    /// This value corresponds to the @ref Host::IdentifierType value.
+    cass_int32_t reserved_ipv6_prefix_address_type_;
+
+    /// @brief The reservation's IAID
+    cass_int32_t iaid_;
+
+    /// @brief Version of DHCP (i.e. 0 for DHCPv4 and 1 for DHCPv6) to which the
+    ///     option belongs.
+    cass_int32_t option_universe_;
+
+    /// @brief Option code
+    cass_int32_t option_code_;
+
+    /// @brief Option value
+    CassOptionBuffer option_value_;
+
+    /// @brief The textual value of an option
+    std::string option_formatted_value_;
+
+    /// @brief Option space name
+    std::string option_space_;
+
+    /// @brief Flag indicating if option is always sent or only on request
+    cass_bool_t option_is_persistent_;
+
+    /// @brief Option client class
+    std::string option_client_class_;
+
+    /// @brief Subnet identifier
+    cass_int32_t option_subnet_id_;
+};  // CqlHostExchange
+
+CqlHostExchange::CqlHostExchange()
+    : host_identifier_type_(0), host_ipv4_subnet_id_(0),
+      host_ipv6_subnet_id_(0), host_ipv4_address_(0),
+      reserved_ipv6_prefix_length_(0), reserved_ipv6_prefix_address_type_(0),
+      iaid_(0), option_universe_(0), option_code_(0),
+      option_is_persistent_(cass_false), option_subnet_id_(0) {
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT64)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_identifier", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BYTES)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_identifier_type", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv6_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_address", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "hostname", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv4_client_classes", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "host_ipv6_client_classes", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_address", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_length", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "reserved_ipv6_prefix_address_type", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "iaid", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_universe", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_code", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_value", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BYTES)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_formatted_value", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_space", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_is_persistent", parameters_.size(),
+        EXCHANGE_DATA_TYPE_IO_IN_OUT, EXCHANGE_DATA_TYPE_BOOL)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_client_class", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_STRING)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "option_subnet_id", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_INT32)));
+    parameters_.push_back(ExchangeColumnInfoPtr(new ExchangeColumnInfo(
+        "[applied]", parameters_.size(), EXCHANGE_DATA_TYPE_IO_IN_OUT,
+        EXCHANGE_DATA_TYPE_BOOL)));
+    BOOST_ASSERT(parameters_.size() == 22U);
+}
+
+CqlHostExchange::~CqlHostExchange() {
+}
+
+void
+CqlHostExchange::createBindForReceive(CqlDataArray& data,
+                                      const int /* statement_index = -1 */) {
+    // Start with a fresh array.
+    data.clear();
+    // id: blob
+    data.add(reinterpret_cast<void*>(&id_));
+    // host_identifier: blob
+    data.add(reinterpret_cast<void*>(&host_identifier_));
+    // host_identifier_type: int
+    data.add(reinterpret_cast<void*>(&host_identifier_type_));
+    // host_ipv4_subnet_id: int
+    data.add(reinterpret_cast<void*>(&host_ipv4_subnet_id_));
+    // host_ipv6_subnet_id: int
+    data.add(reinterpret_cast<void*>(&host_ipv6_subnet_id_));
+    // host_ipv4_address: int
+    data.add(reinterpret_cast<void*>(&host_ipv4_address_));
+    // hostname: text
+    data.add(reinterpret_cast<void*>(&hostname_));
+    // host_ipv4_client_classes: text
+    data.add(reinterpret_cast<void*>(&host_ipv4_client_classes_));
+    // host_ipv6_client_classes: text
+    data.add(reinterpret_cast<void*>(&host_ipv6_client_classes_));
+    // reserved_ipv6_prefix_address: text
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_));
+    // reserved_ipv6_prefix_length: int
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length_));
+    // reserved_ipv6_prefix_length: int
+    data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_type_));
+    // iaid: int
+    data.add(reinterpret_cast<void*>(&iaid_));
+    // option_universe: int
+    data.add(reinterpret_cast<void*>(&option_universe_));
+    // option_code: int
+    data.add(reinterpret_cast<void*>(&option_code_));
+    // option_value: blob
+    data.add(reinterpret_cast<void*>(&option_value_));
+    // option_formatted_value: text
+    data.add(reinterpret_cast<void*>(&option_formatted_value_));
+    // option_space: text
+    data.add(reinterpret_cast<void*>(&option_space_));
+    // option_is_persistent: boolean
+    data.add(reinterpret_cast<void*>(&option_is_persistent_));
+    // option_client_class: text
+    data.add(reinterpret_cast<void*>(&option_client_class_));
+    // option_subnet_id: int
+    data.add(reinterpret_cast<void*>(&option_subnet_id_));
+}
+
+void
+CqlHostExchange::createBindForSend(const HostPtr& host,
+                                   const OptionalValue<SubnetID>& subnet_id,
+                                   const IPv6Resrv* const reservation,
+                                   const std::string& option_space,
+                                   const OptionDescriptor& option_descriptor,
+                                   CqlDataArray& data) {
+    // Store host object to ensure it remains valid.
+    host_ = host;
+
+    // Set up the structures for the various components of the host
+    // structure.
+    try {
+        // host_identifier: blob
+        // Convert from std::vector<uint8_t> to std::vector<cass_byte_t>.
+        HostIdentifier host_identifier = host->getIdentifier();
+        host_identifier_ = CassHostIdentifier(host_identifier.begin(),
+                                              host_identifier.end());
+        if (host_identifier_.size() > DUID::MAX_DUID_LEN) {
+            isc_throw(BadValue, "host identifier "
+                                    << host_identifier_.data() << " of length "
+                                    << host_identifier_.size()
+                                    << " is greater than allowed of "
+                                    << DUID::MAX_DUID_LEN);
+        }
+
+        // host_identifier_type: int
+        host_identifier_type_ =
+            static_cast<cass_int32_t>(host->getIdentifierType());
+        if (host_identifier_type_ > MAX_IDENTIFIER_TYPE) {
+            isc_throw(BadValue, "invalid host identifier type returned: "
+                                    << host_identifier_type_);
+        }
+
+        // host_ipv4_subnet_id: int
+        host_ipv4_subnet_id_ =
+            static_cast<cass_int32_t>(host->getIPv4SubnetID());
+
+        // host_ipv6_subnet_id: int
+        host_ipv6_subnet_id_ =
+            static_cast<cass_int32_t>(host->getIPv6SubnetID());
+
+        // host_ipv4_address: int
+        host_ipv4_address_ =
+            static_cast<cass_int32_t>(host->getIPv4Reservation().toUint32());
+
+        // hostname: text
+        hostname_ = host->getHostname();
+        if (hostname_.size() > HOSTNAME_MAX_LENGTH) {
+            isc_throw(BadValue, "hostname " << hostname_ << " of length "
+                                            << hostname_.size()
+                                            << " is greater than allowed of "
+                                            << HOSTNAME_MAX_LENGTH);
+        }
+
+        // host_ipv4_client_classes: text
+        host_ipv4_client_classes_ = host->getClientClasses4().toText(",");
+        if (host_ipv4_client_classes_.size() > CLIENT_CLASSES_MAX_LENGTH) {
+            isc_throw(BadValue, "IPv4 client classes "
+                                    << host_ipv4_client_classes_
+                                    << " of length "
+                                    << host_ipv4_client_classes_.size()
+                                    << " is greater than allowed of "
+                                    << CLIENT_CLASSES_MAX_LENGTH);
+        }
+
+        // host_ipv6_client_classes: text
+        host_ipv6_client_classes_ = host->getClientClasses6().toText(",");
+        if (host_ipv6_client_classes_.size() > CLIENT_CLASSES_MAX_LENGTH) {
+            isc_throw(BadValue, "IPv6 client classes "
+                                    << host_ipv6_client_classes_
+                                    << " of length "
+                                    << host_ipv6_client_classes_.size()
+                                    << " is greater than allowed of "
+                                    << CLIENT_CLASSES_MAX_LENGTH);
+        }
+
+        if (reservation == NULL) {
+            reserved_ipv6_prefix_address_ = NULL_RESERVED_IPV6_PREFIX_ADDRESS;
+            reserved_ipv6_prefix_length_ = NULL_RESERVED_IPV6_PREFIX_LENGTH;
+            reserved_ipv6_prefix_address_type_ =
+                NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE;
+            iaid_ = NULL_IAID;
+        } else {
+            // reserved_ipv6_prefix_address: text
+            reserved_ipv6_prefix_address_ = reservation->getPrefix().toText();
+
+            // reserved_ipv6_prefix_length: int
+            reserved_ipv6_prefix_length_ = static_cast<cass_int32_t>(reservation->getPrefixLen());
+
+            // reserved_ipv6_prefix_address: int
+            reserved_ipv6_prefix_address_type_ =
+                reservation->getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
+
+            // iaid: int
+            /// @todo: We don't support iaid in the IPv6Resrv yet.
+            iaid_ = 0;
+        }
+
+        if (option_descriptor.option_ == NULL) {
+            option_universe_ = NULL_OPTION_UNIVERSE;
+            option_code_ = NULL_OPTION_CODE;
+            option_value_ = NULL_OPTION_VALUE;
+            option_formatted_value_ = NULL_OPTION_FORMATTED_VALUE;
+            option_space_ = NULL_OPTION_SPACE;
+            option_is_persistent_ = NULL_OPTION_IS_PERSISTENT;
+            option_client_class_ = NULL_OPTION_CLIENT_CLASS;
+            option_subnet_id_ = NULL_OPTION_SUBNET_ID;
+        } else {
+            // option_universe: int
+            option_universe_ = option_descriptor.option_->getUniverse();
+
+            // option_code: int
+            option_code_ = option_descriptor.option_->getType();
+
+            // option_value: blob
+            // option_formatted_value: text
+            if (option_descriptor.formatted_value_.empty()) {
+                if (option_descriptor.option_->len() >
+                    option_descriptor.option_->getHeaderLen()) {
+                    // The formatted_value is empty and the option value
+                    // is not empty so we need to prepare on-wire format
+                    // for the option and store it in the database as a
+                    // blob.
+                    OutputBuffer buffer(option_descriptor.option_->len());
+                    option_descriptor.option_->pack(buffer);
+                    const char* buffer_ptr =
+                        static_cast<const char*>(buffer.getData());
+                    option_value_.assign(
+                        buffer_ptr + option_descriptor.option_->getHeaderLen(),
+                        buffer_ptr + buffer.getLength());
+                } else {
+                    option_value_.clear();
+                }
+                option_formatted_value_.clear();
+            } else {
+                option_value_.clear();
+                option_formatted_value_ = option_descriptor.formatted_value_;
+            }
+
+            // option_space: text
+            option_space_ = option_space;
+
+            // option_is_persistent: boolean
+            option_is_persistent_ =
+                option_descriptor.persistent_ ? cass_true : cass_false;
+
+            // option_client_class: text
+            /// @todo Assign actual value to client class string.
+            option_client_class_.clear();
+
+            // option_subnet_id: int
+            if (subnet_id.isSpecified()) {
+                option_subnet_id_ = subnet_id;
+            } else {
+                option_subnet_id_ = 0;
+            }
+        }
+
+        // id: bigint
+        id_ = hashIntoId();
+
+        // Add all parameters to bind array.
+        data.clear();
+        data.add(reinterpret_cast<void*>(&id_));
+        data.add(reinterpret_cast<void*>(&host_identifier_));
+        data.add(reinterpret_cast<void*>(&host_identifier_type_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_subnet_id_));
+        data.add(reinterpret_cast<void*>(&host_ipv6_subnet_id_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_address_));
+        data.add(reinterpret_cast<void*>(&hostname_));
+        data.add(reinterpret_cast<void*>(&host_ipv4_client_classes_));
+        data.add(reinterpret_cast<void*>(&host_ipv6_client_classes_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length_));
+        data.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address_type_));
+        data.add(reinterpret_cast<void*>(&iaid_));
+        data.add(reinterpret_cast<void*>(&option_universe_));
+        data.add(reinterpret_cast<void*>(&option_code_));
+        data.add(reinterpret_cast<void*>(&option_value_));
+        data.add(reinterpret_cast<void*>(&option_formatted_value_));
+        data.add(reinterpret_cast<void*>(&option_space_));
+        data.add(reinterpret_cast<void*>(&option_is_persistent_));
+        data.add(reinterpret_cast<void*>(&option_client_class_));
+        data.add(reinterpret_cast<void*>(&option_subnet_id_));
+    } catch (const std::exception& ex) {
+        isc_throw(DbOperationError, "could not create bind array from host "
+                                        << host->getHostname()
+                                        << ", reason: " << ex.what());
+    }
+}
+
+uint64_t
+md5Hash(const std::string& input) {
+    // Prepare structures for MD5().
+    const size_t word_size = MD5_DIGEST_LENGTH / sizeof(uint64_t);
+    uint64_t hash[word_size];
+    unsigned char* digest = reinterpret_cast<unsigned char*>(hash);
+    unsigned char* string =
+        reinterpret_cast<unsigned char*>(const_cast<char*>(input.c_str()));
+    std::fill(hash, hash + word_size, 0);
+
+    // Get MD5 hash value.
+    MD5(string, input.size(), digest);
+
+    // Return the first part of the hash value which still retains all
+    // properties of the full hash value.
+    return hash[0];
+}
+
+cass_int64_t
+CqlHostExchange::hashIntoId() const {
+    // Allocates a fixed maximum length in the stringstream for each
+    // aggregated field to avoid collisions between distinct entries.
+
+    // Get key.
+    std::stringstream key_stream;
+    key_stream << std::setw(10) << std::setfill('0') << host_ipv4_subnet_id_;
+    key_stream << std::setw(10) << std::setfill('0') << host_ipv6_subnet_id_;
+    key_stream << std::setw(ADDRESS4_TEXT_MAX_LENGTH) << host_ipv4_address_;
+    key_stream << std::setw(ADDRESS6_TEXT_MAX_LENGTH)
+               << reserved_ipv6_prefix_address_;
+    key_stream << std::setw(4) << std::setfill('0')
+               << reserved_ipv6_prefix_length_;
+    key_stream << std::setw(4) << std::setfill('0') << option_code_;
+    key_stream << std::setw(OPTION_SPACE_MAX_LENGTH) << option_space_;
+    const std::string key = key_stream.str();
+
+    const uint64_t md5 = md5Hash(key);
+
+    return static_cast<cass_int64_t>(md5);
+}
+
+void*
+CqlHostExchange::retrieve() {
+    uint64_t id = static_cast<uint64_t>(id_);
+
+    HostIdentifier host_identifier =
+        HostIdentifier(host_identifier_.begin(), host_identifier_.end());
+
+    // Set the host identifier type in a variable of the appropriate data type.
+    Host::IdentifierType host_identifier_type =
+        static_cast<Host::IdentifierType>(host_identifier_type_);
+
+    // Set IPv4 subnet ID to the value returned.
+    SubnetID ipv4_subnet_id = static_cast<SubnetID>(host_ipv4_subnet_id_);
+
+    // Set IPv6 subnet ID to the value returned.
+    SubnetID ipv6_subnet_id = static_cast<SubnetID>(host_ipv6_subnet_id_);
+
+    // Set IPv4 address reservation.
+    asiolink::IOAddress ipv4_reservation =
+        asiolink::IOAddress(host_ipv4_address_);
+
+    Host* host = new Host(host_identifier.data(), host_identifier.size(),
+                          host_identifier_type, ipv4_subnet_id, ipv6_subnet_id,
+                          ipv4_reservation, hostname_,
+                          host_ipv4_client_classes_, host_ipv6_client_classes_);
+    try{
+        host->setHostId(id);
+
+        const IPv6Resrv reservation = retrieveReservation();
+        if (reservation != NULL_IPV6_RESERVATION &&
+            !host->hasReservation(reservation)) {
+            host->addReservation(reservation);
+        }
+
+        OptionDescriptorPtr option_descriptor_ptr = retrieveOption();
+        if (option_descriptor_ptr) {
+            if (option_descriptor_ptr->option_->getUniverse() == Option::V4) {
+                host->getCfgOption4()->add(*option_descriptor_ptr, option_space_);
+            } else if (option_descriptor_ptr->option_->getUniverse() ==
+                       Option::V6) {
+                host->getCfgOption6()->add(*option_descriptor_ptr, option_space_);
+            }
+        }
+    } catch (const std::exception& exception) {
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_CQL_HOST_RETRIEVE_ERROR)
+            .arg(exception.what());
+        throw;
+    }
+
+    return reinterpret_cast<void*>(host);
+}
+
+const IPv6Resrv
+CqlHostExchange::retrieveReservation() const {
+    // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
+    IPv6Resrv::Type type;
+    switch (reserved_ipv6_prefix_address_type_) {
+    case 0:
+        type = IPv6Resrv::TYPE_NA;
+        break;
+    case 2:
+        type = IPv6Resrv::TYPE_PD;
+        break;
+    case NULL_RESERVED_IPV6_PREFIX_ADDRESS_TYPE:
+        return NULL_IPV6_RESERVATION;
+    default:
+        isc_throw(BadValue,
+                  "invalid IPv6 reservation type returned: "
+                      << reserved_ipv6_prefix_address_type_
+                      << ". Only 0 (IA_NA) or 2 (IA_PD) are allowed.");
+    }
+
+    return IPv6Resrv(type, IOAddress(reserved_ipv6_prefix_address_),
+                     reserved_ipv6_prefix_length_);
+}
+
+OptionDescriptorPtr
+CqlHostExchange::retrieveOption() const {
+    // Options are held in a binary or textual format in the database.
+    // This is similar to having an option specified in a server
+    // configuration file. Such option is converted to appropriate C++
+    // class, using option definition. Thus, we need to find the
+    // option definition for this option code and option space.
+
+    // If the option space is a standard DHCPv4 or DHCPv6 option space,
+    // this is most likely a standard option, for which we have a
+    // definition created within libdhcp++.
+    if (option_space_.empty() || option_universe_ == NULL_OPTION_UNIVERSE) {
+        return OptionDescriptorPtr();
+    }
+
+    OptionDefinitionPtr option_definition_ptr =
+        LibDHCP::getOptionDef(option_space_, option_code_);
+
+    // Otherwise, we may check if this an option encapsulated within the
+    // vendor space.
+    if (!option_definition_ptr && option_space_ != DHCP4_OPTION_SPACE &&
+        option_space_ != DHCP6_OPTION_SPACE) {
+        uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space_);
+        if (vendor_id > 0) {
+            option_definition_ptr = LibDHCP::getVendorOptionDef(
+                static_cast<Option::Universe>(option_universe_), vendor_id,
+                option_code_);
+        }
+    }
+
+    // In all other cases, we use runtime option definitions, which
+    // should be also registered within the libdhcp++.
+    if (!option_definition_ptr) {
+        option_definition_ptr =
+            LibDHCP::getRuntimeOptionDef(option_space_, option_code_);
+    }
+
+    OptionPtr option;
+    if (!option_definition_ptr) {
+        // If no definition found, we use generic option type.
+        OptionBuffer option_buffer(option_value_.begin(), option_value_.end());
+        option.reset(new Option(static_cast<Option::Universe>(option_universe_),
+                                static_cast<uint16_t>(option_code_),
+                                option_buffer.begin(), option_buffer.end()));
+    } else {
+        // The option value may be specified in textual or binary format in the
+        // database. If formatted_value is empty, the binary format is used.
+        // Depending on the format we use a different variant of @ref
+        // optionFactory().
+        if (option_formatted_value_.empty()) {
+            OptionBuffer option_buffer(option_value_.begin(),
+                                       option_value_.end());
+            option = option_definition_ptr->optionFactory(
+                static_cast<Option::Universe>(option_universe_),
+                static_cast<uint16_t>(option_code_), option_buffer.begin(),
+                option_buffer.end());
+        } else {
+            // Spit the value specified in comma separated values format.
+            std::vector<std::string> split_vector;
+            boost::split(split_vector, option_formatted_value_,
+                         boost::is_any_of(","));
+            option = option_definition_ptr->optionFactory(
+                static_cast<Option::Universe>(option_universe_),
+                static_cast<uint16_t>(option_code_), split_vector);
+        }
+    }
+
+    return OptionDescriptorPtr(new OptionDescriptor(
+        option, option_is_persistent_, option_formatted_value_));
+}
+
+/// @brief Implementation of the @ref CqlHostDataSource.
+class CqlHostDataSourceImpl {
+public:
+    /// @brief Constructor.
+    ///
+    /// This constructor opens database connection and initializes prepared
+    /// statements used in the queries.
+    explicit CqlHostDataSourceImpl(
+        const CqlConnection::ParameterMap& parameters);
+
+    /// @brief Destructor.
+    virtual ~CqlHostDataSourceImpl();
+
+    /// @brief Implementation of @ref CqlHostDataSource::add()
+    virtual void add(const HostPtr& host);
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get4()
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+                              const uint8_t prefix_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const DuidPtr& duid,
+                              const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::get6()
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll
+    virtual ConstHostCollection getAll(const HWAddrPtr& hwaddr,
+                                       const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll()
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getAll4()
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getName()
+    virtual std::string getName() const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::getVersion()
+    virtual VersionPair getVersion() const;
+
+    /// @brief Implementation of @ref CqlHostDataSource::commit()
+    virtual void commit();
+
+    /// @brief Implementation of @ref CqlHostDataSource::rollback()
+    virtual void rollback();
+
+protected:
+    /// @brief Adds any options found in the @ref Host object to a separate
+    ///     table entry.
+    ///
+    /// @param host @ref Host object from which options are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_spaces list of option spaces to search for
+    /// @param cfg_option option configuration used to match option spaces in
+    ///     order to obtain actual options
+    virtual void insertHostWithOptions(
+        const HostPtr& host,
+        const IPv6Resrv* const reservation = NULL,
+        const std::list<std::string>& option_spaces = std::list<std::string>(),
+        const ConstCfgOptionPtr cfg_option = ConstCfgOptionPtr());
+
+    /// @brief Adds any reservations found in the @ref Host object to a separate
+    ///     table entry.
+    ///
+    /// @param host @ref Host object from which reservations are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_spaces4 list of option spaces for universe Option::V4 to
+    ///     search in
+    /// @param cfg_option4 option configuration for universe Option::V4 used to
+    ///     match option spaces in order to obtain actual options
+    /// @param option_spaces6 list of option spaces for universe Option::V6 to
+    ///     search in
+    /// @param cfg_option6 option configuration for universe Option::V6 used to
+    ///     match option spaces in order to obtain actual options
+    virtual void
+    insertHostWithReservations(const HostPtr& host,
+                               const IPv6Resrv* const reservation,
+                               const std::list<std::string>& option_spaces4,
+                               const ConstCfgOptionPtr cfg_option4,
+                               const std::list<std::string>& option_spaces6,
+                               const ConstCfgOptionPtr cfg_option6);
+
+    /// @brief Retrieves a single host.
+    ///
+    /// Calls @ref getHostCollection() and checks if a single host was returned.
+    ///
+    /// @param where_values array of bound objects used to filter the results
+    /// @param statement_index prepared statement being executed
+    ///
+    /// @return one host or a null pointer to a host
+    ///
+    /// @throw MultipleRecords exception if two or more hosts are returned
+    virtual ConstHostPtr
+    getHost(const CqlDataArray& where_values,
+            const CqlHostExchange::StatementIndex statement_index) const;
+
+    /// @brief Retrieves a collection of hosts.
+    ///
+    /// Calls @ref CqlExchange::executeRead().
+    ///
+    /// @param where_values array of bound objects used to filter the results
+    /// @param statement_index prepared statement being executed
+    ///
+    /// @return a collection of hosts containing one or more hosts
+    virtual ConstHostCollection getHostCollection(
+        const CqlDataArray& where_values,
+        const CqlHostExchange::StatementIndex statement_index) const;
+
+    /// @brief Inserts a single host.
+    ///
+    /// All information is available here. Calls @ref
+    /// CqlExchange::executeWrite().
+    ///
+    /// @param host @ref Host object from which options are retrieved and
+    ///     inserted into the Cassandra database
+    /// @param subnet_id identifier of the subnet to which the host belongs
+    /// @param reservation reservation for the current denormalized table entry
+    /// @param option_space option space for the current denormalized table
+    ///     entry's option
+    /// @param option_descriptor option descriptor containing information for
+    /// the current denormalized table entry's option
+    virtual void insertHost(
+        const HostPtr& host,
+        const OptionalValue<SubnetID>& subnet_id = OptionalValue<SubnetID>(),
+        const IPv6Resrv* const reservation = NULL,
+        const std::string& option_space = NULL_OPTION_SPACE,
+        const OptionDescriptor& option_descriptor = OptionDescriptor(false));
+
+    /// @brief Merge denormalized table entries that belong to the same host
+    /// into a single host, one by one.
+    ///
+    /// @param target_host host which can contain multiple reservations and
+    ///     options to which other distinct reservations and options are added.
+    /// @param source_host host that is being search for new reservations and
+    ///     options that will be merged into the old host.
+    virtual void mergeHosts(HostPtr& target_host,
+                            const ConstHostPtr& source_host) const;
+
+private:
+    /// @brief pointer to the exchange responsible for trading host information
+    boost::shared_ptr<CqlHostExchange> host_exchange_;
+    /// @brief pointer to the exchange responsible for retrieving schema version
+    boost::shared_ptr<CqlVersionExchange> version_exchange_;
+
+    /// @brief CQL connection
+    mutable CqlConnection dbconn_;
+};  // class CqlHostDataSourceImpl
+
+
+/// @brief hash function for @ref HostMap
+///
+/// @param key being hashed
+///
+/// @return hash value
+std::size_t
+hash_value(const HostKey& key) {
+    // Get key.
+    std::stringstream key_stream;
+    HostIdentifier host_identifier = key.get<0>();
+    key_stream << std::setw(DUID::MAX_DUID_LEN) << std::setfill('0')
+               << DUID(host_identifier).toText();
+    key_stream << std::setw(2) << std::setfill('0') << key.get<1>();
+    key_stream << std::setw(10) << std::setfill('0') << key.get<2>();
+    key_stream << std::setw(10) << std::setfill('0') << key.get<3>();
+    key_stream << std::setw(ADDRESS4_TEXT_MAX_LENGTH) << std::setfill('0')
+               << key.get<4>();
+    const std::string key_string = key_stream.str();
+
+    const uint64_t md5 = md5Hash(key_string);
+
+    return static_cast<std::size_t>(md5);
+}
+
+/// @brief equals operator for @ref HostKey
+///
+/// @param key1 left hand side operand
+/// @param key2 right hand side operand
+///
+/// @return true if keys are equal. Deep comparison is made.
+bool
+operator==(const HostKey& key1, const HostKey& key2) {
+    return key1.get<0>() == key2.get<0>() && key1.get<1>() == key2.get<1>() &&
+           key1.get<2>() == key2.get<2>() && key1.get<3>() == key2.get<3>() &&
+           key1.get<4>() == key2.get<4>();
+}
+
+CqlHostDataSourceImpl::CqlHostDataSourceImpl(
+    const CqlConnection::ParameterMap& parameters)
+    : host_exchange_(new CqlHostExchange()),
+      version_exchange_(new CqlVersionExchange()), dbconn_(parameters) {
+    // Open the database.
+    dbconn_.openDatabase();
+    // Prepare all possible statements.
+    dbconn_.prepareStatements(tagged_statements);
+}
+
+CqlHostDataSourceImpl::~CqlHostDataSourceImpl() {
+    // There is no need to close the database in this destructor: it is
+    // closed in the destructor of the dbconn_ member variable.
+}
+
+void
+CqlHostDataSourceImpl::add(const HostPtr& host) {
+    // Get option space names and vendor space names and combine them within a
+    // single list.
+    // For IPv4:
+    ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+    std::list<std::string> option_spaces4 = cfg_option4->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces4 =
+        cfg_option4->getVendorIdsSpaceNames();
+    option_spaces4.insert(option_spaces4.end(), vendor_spaces4.begin(),
+                          vendor_spaces4.end());
+
+    // For IPv6:
+    ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+    std::list<std::string> option_spaces6 = cfg_option6->getOptionSpaceNames();
+    std::list<std::string> vendor_spaces6 =
+        cfg_option6->getVendorIdsSpaceNames();
+    option_spaces6.insert(option_spaces6.end(), vendor_spaces6.begin(),
+                          vendor_spaces6.end());
+
+    // For every IPv6 reservation, add each of their options to the database.
+    IPv6ResrvRange reservations = host->getIPv6Reservations();
+    if (std::distance(reservations.first, reservations.second) > 0) {
+        for (IPv6ResrvIterator it = reservations.first;
+             it != reservations.second; ++it) {
+            insertHostWithReservations(host, &it->second, option_spaces4,
+                                       cfg_option4, option_spaces6,
+                                       cfg_option6);
+        }
+    } else {
+        // If host has no reservation, add entries with null reservation.
+        // Options could still be present.
+        insertHostWithReservations(host, NULL, option_spaces4, cfg_option4,
+                                   option_spaces6, cfg_option6);
+    }
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const asiolink::IOAddress& address) const {
+    if (!address.isV4()) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(2): wrong address type, "
+                            "address supplied is not an IPv4 address");
+    }
+
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    cass_int32_t host_ipv4_address =
+        static_cast<cass_int32_t>(address.toUint32());
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_address));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID_AND_ADDRESS);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const {
+    /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr,
+    /// duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(3) called with both "
+                            "hwaddr and duid, only one of them is allowed");
+    } else if (!hwaddr && !duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get4(3) called with neither "
+                            "hwaddr or duid specified, one of them is "
+                            "required");
+    } else if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        // Delegate to get4(4).
+        return get4(subnet_id, Host::IDENT_DUID, duid_vector.data(),
+                    duid_vector.size());
+    } else if (hwaddr) {
+        // Delegate to get4(4).
+        return get4(subnet_id, Host::IDENT_HWADDR, hwaddr->hwaddr_.data(),
+                    hwaddr->hwaddr_.size());
+    }
+    return ConstHostPtr();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get4(const SubnetID& subnet_id,
+                            const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    CassHostIdentifier host_identifier(identifier_begin,
+                                       identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV4_SUBNET_ID_AND_HOST_ID);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const asiolink::IOAddress& prefix,
+                            const uint8_t prefix_len) const {
+    // Convert to CQL data types.
+    std::string reserved_ipv6_prefix_address = prefix.toText();
+    cass_int32_t reserved_ipv6_prefix_length = prefix_len;
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address));
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_length));
+
+    // Get host id.
+    ConstHostPtr host = getHost(*const_cast<const CqlDataArray*>(&where_values),
+                                CqlHostExchange::GET_HOST_BY_IPV6_PREFIX);
+
+    if (host == ConstHostPtr()) {
+        return ConstHostPtr();
+    }
+
+    // Get host.
+    HostIdentifier host_identifier = host->getIdentifier();
+    ConstHostCollection collection =
+        getAll(host->getIdentifierType(), host_identifier.data(),
+               host_identifier.size());
+
+    if (collection.empty()) {
+        return ConstHostPtr();
+    }
+
+    if (collection.size() >= 2U) {
+        isc_throw(
+            MultipleRecords,
+            "CqlHostDataSource::get6(2): multiple records were found in the "
+            "database where only one was expected for statement "
+                << dbconn_
+                       .tagged_statements_[CqlHostExchange::GET_HOST_BY_HOST_ID]
+                       .name_);
+    }
+
+    return *collection.begin();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const DuidPtr& duid,
+                            const HWAddrPtr& hwaddr) const {
+    /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr,
+    /// duid)
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get6(3): both hardware address "
+                            "and DUID are specified, only one of them is "
+                            "allowed");
+    } else if (!hwaddr && !duid) {
+        isc_throw(BadValue, "CqlHostDataSource::get6(3): both hardware address "
+                            "and DUID are specified, one of them is required");
+    } else if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        // Delegate to get6(4).
+        return get6(subnet_id, Host::IDENT_DUID, duid_vector.data(),
+                    duid_vector.size());
+    } else if (hwaddr) {
+        // Delegate to get6(4).
+        return get6(subnet_id, Host::IDENT_HWADDR, hwaddr->hwaddr_.data(),
+                    hwaddr->hwaddr_.size());
+    }
+    return ConstHostPtr();
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const Host::IdentifierType& identifier_type,
+                            const uint8_t* identifier_begin,
+                            const size_t identifier_len) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv6_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    CassHostIdentifier host_identifier(identifier_begin,
+                                   identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv6_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID_AND_HOST_ID);
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::get6(const SubnetID& subnet_id,
+                            const IOAddress& address) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv6_subnet_id = static_cast<cass_int32_t>(subnet_id);
+    std::string reserved_ipv6_prefix_address = address.toText();
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv6_subnet_id));
+    where_values.add(reinterpret_cast<void*>(&reserved_ipv6_prefix_address));
+
+    // Run statement.
+    return getHost(*const_cast<const CqlDataArray*>(&where_values),
+                   CqlHostExchange::GET_HOST_BY_IPV6_SUBNET_ID_AND_ADDRESS);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll(const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid) const {
+    if (!duid && !hwaddr) {
+        return ConstHostCollection();
+    }
+
+    // Convert to CQL data types.
+    CassHostIdentifier host_identifier;
+    cass_int32_t host_identifier_type;
+    if (duid) {
+        HostIdentifier duid_vector = duid->getDuid();
+        host_identifier =
+            CassHostIdentifier(duid_vector.begin(), duid_vector.end());
+        host_identifier_type = static_cast<cass_int32_t>(Host::IDENT_DUID);
+    } else if (hwaddr) {
+        host_identifier = CassHostIdentifier(hwaddr->hwaddr_.begin(),
+                                                   hwaddr->hwaddr_.end());
+        host_identifier_type = static_cast<cass_int32_t>(Host::IDENT_HWADDR);
+    }
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_HOST_ID);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll(const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const {
+    // Convert to CQL data types.
+    CassHostIdentifier host_identifier(identifier_begin,
+                                             identifier_begin + identifier_len);
+    cass_int32_t host_identifier_type =
+        static_cast<cass_int32_t>(identifier_type);
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_identifier));
+    where_values.add(reinterpret_cast<void*>(&host_identifier_type));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_HOST_ID);
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getAll4(const asiolink::IOAddress& address) const {
+    // Convert to CQL data types.
+    cass_int32_t host_ipv4_address =
+        static_cast<cass_int32_t>(address.toUint32());
+
+    // Bind to array.
+    CqlDataArray where_values;
+    where_values.add(reinterpret_cast<void*>(&host_ipv4_address));
+
+    // Run statement.
+    return getHostCollection(*const_cast<const CqlDataArray*>(&where_values),
+                             CqlHostExchange::GET_HOST_BY_IPV4_ADDRESS);
+}
+
+std::string
+CqlHostDataSourceImpl::getName() const {
+    std::string name = "";
+    try {
+        name = dbconn_.getParameter("name");
+    } catch (...) {
+        // Return an empty name
+    }
+    return name;
+}
+
+VersionPair
+CqlHostDataSourceImpl::getVersion() const {
+    return version_exchange_->retrieveVersion(dbconn_,
+                                              CqlHostExchange::GET_VERSION);
+}
+
+void
+CqlHostDataSourceImpl::commit() {
+    dbconn_.commit();
+}
+
+void
+CqlHostDataSourceImpl::rollback() {
+    dbconn_.rollback();
+}
+
+void
+CqlHostDataSourceImpl::insertHostWithOptions(
+    const HostPtr& host,
+    const IPv6Resrv* const reservation /* = NULL */,
+    const std::list<std::string>& option_spaces
+    /* = std::list<std::string>() */,
+    const ConstCfgOptionPtr cfg_option /* = ConstCfgOptionPtr() */) {
+    // For each option space retrieve all options and insert them into the
+    // database.
+    bool option_found = false;
+    for (std::list<std::string>::const_iterator space = option_spaces.begin();
+         space != option_spaces.end(); ++space) {
+        OptionContainerPtr options = cfg_option->getAll(*space);
+        if (options && !options->empty()) {
+            for (OptionContainer::const_iterator option = options->begin();
+                 option != options->end(); ++option) {
+                option_found = true;
+                insertHost(host, OptionalValue<SubnetID>(), reservation, *space,
+                           *option);
+            }
+        }
+    }
+    if (!option_found) {
+        insertHost(host, OptionalValue<SubnetID>(), reservation);
+    }
+}
+
+void
+CqlHostDataSourceImpl::insertHostWithReservations(
+    const HostPtr& host,
+    const IPv6Resrv* const reservation,
+    const std::list<std::string>& option_spaces4,
+    const ConstCfgOptionPtr cfg_option4,
+    const std::list<std::string>& option_spaces6,
+    const ConstCfgOptionPtr cfg_option6) {
+    // If host has no reservation, add entries with null reservation.
+    // Options could still be present.
+    if (cfg_option4 && !cfg_option4->empty()) {
+        insertHostWithOptions(host, reservation, option_spaces4, cfg_option4);
+    }
+    if (cfg_option6 && !cfg_option6->empty()) {
+        insertHostWithOptions(host, reservation, option_spaces6, cfg_option6);
+    }
+    if ((!cfg_option4 || cfg_option4->empty()) &&
+        (!cfg_option6 || cfg_option6->empty())) {
+        insertHostWithOptions(host, reservation);
+    }
+}
+
+ConstHostPtr
+CqlHostDataSourceImpl::getHost(
+    const CqlDataArray& where_values,
+    const CqlHostExchange::StatementIndex statement_index) const {
+    ConstHostCollection collection =
+        getHostCollection(where_values, statement_index);
+
+    if (collection.empty()) {
+        return ConstHostPtr();
+    }
+
+    if (collection.size() >= 2U) {
+        isc_throw(MultipleRecords,
+                  "multiple records were found in the database where only one "
+                  "was expected for statement "
+                      << dbconn_.tagged_statements_[statement_index].name_);
+    }
+
+    return *collection.begin();
+}
+
+ConstHostCollection
+CqlHostDataSourceImpl::getHostCollection(
+    const CqlDataArray& where_values,
+    const CqlHostExchange::StatementIndex statement_index) const {
+
+    // Run statement.
+    CqlDataArray collection = host_exchange_->executeRead(
+        dbconn_, where_values, statement_index, false);
+
+    // Form HostPtr objects.
+    HostCollection host_collection;
+    for (std::vector<void*>::const_iterator it = collection.begin();
+         it != collection.end(); ++it) {
+        host_collection.push_back(HostPtr(reinterpret_cast<Host*>(*it)));
+    }
+
+    // Merge the denormalized table entries that belong to the same host into a
+    // single host.
+    HostMap map;
+    for (HostCollection::iterator it = host_collection.begin();
+         it != host_collection.end(); ++it) {
+        HostKey key =
+            HostKey((*it)->getIdentifier(), (*it)->getIdentifierType(),
+                    (*it)->getIPv4SubnetID(), (*it)->getIPv6SubnetID(),
+                    (*it)->getIPv4Reservation());
+        if (map.find(key) == map.end()) {
+            map[key] = *it;
+        } else {
+            mergeHosts(map[key], *it);
+        }
+    }
+
+    ConstHostCollection result_collection;
+    for (HostMap::const_iterator it = map.begin(); it != map.end(); ++it) {
+        result_collection.push_back(it->second);
+    }
+    return result_collection;
+}
+
+void
+CqlHostDataSourceImpl::insertHost(
+    const HostPtr& host,
+    const OptionalValue<SubnetID>& subnet_id
+    /* = OptionalValue<SubnetID>() */,
+    const IPv6Resrv* const reservation /* = NULL */,
+    const std::string& option_space /* = NULL_OPTION_SPACE */,
+    const OptionDescriptor& option_descriptor
+    /* = OptionDescriptorPtr */) {
+    CqlDataArray assigned_values;
+    // @todo: Assign actual value to subnet id.
+    host_exchange_->createBindForSend(host, subnet_id, reservation,
+                                      option_space, option_descriptor,
+                                      assigned_values);
+    host_exchange_->executeWrite(dbconn_, assigned_values,
+                                 CqlHostExchange::INSERT_HOST);
+}
+
+void
+CqlHostDataSourceImpl::mergeHosts(HostPtr& target_host,
+                                  const ConstHostPtr& source_host) const {
+    // Merge reservations.
+    const IPv6ResrvRange reservations_range =
+        source_host->getIPv6Reservations();
+    if (std::distance(reservations_range.first, reservations_range.second) >
+        0) {
+        for (IPv6ResrvIterator reservations_iterator = reservations_range.first;
+             reservations_iterator != reservations_range.second;
+             ++reservations_iterator) {
+            if (!target_host->hasReservation(reservations_iterator->second)) {
+                target_host->addReservation(reservations_iterator->second);
+            }
+        }
+    }
+
+    // Merge DHCPv4 options.
+    source_host->getCfgOption4()->mergeTo(*target_host->getCfgOption4());
+
+    // Merge DHCPv6 options.
+    source_host->getCfgOption6()->mergeTo(*target_host->getCfgOption6());
+}
+
+CqlHostDataSource::CqlHostDataSource(
+    const CqlConnection::ParameterMap& parameters)
+    : impl_(new CqlHostDataSourceImpl(parameters)) {
+}
+
+CqlHostDataSource::~CqlHostDataSource() {
+    delete impl_;
+}
+
+void
+CqlHostDataSource::add(const HostPtr& host) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_ADD);
+
+    impl_->add(host);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll(hwaddr, duid);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll(identifier_type, identifier_begin, identifier_len);
+}
+
+ConstHostCollection
+CqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_GET_ALL);
+
+    return impl_->getAll4(address);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const HWAddrPtr& hwaddr,
+                        const DuidPtr& duid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, hwaddr, duid);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const Host::IdentifierType& identifier_type,
+                        const uint8_t* identifier_begin,
+                        const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, identifier_type, identifier_begin,
+                       identifier_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get4(const SubnetID& subnet_id,
+                        const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET4);
+
+    return impl_->get4(subnet_id, address);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const DuidPtr& duid,
+                        const HWAddrPtr& hwaddr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, duid, hwaddr);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const Host::IdentifierType& identifier_type,
+                        const uint8_t* identifier_begin,
+                        const size_t identifier_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, identifier_type, identifier_begin,
+                       identifier_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+                        const uint8_t prefix_len) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(prefix, prefix_len);
+}
+
+ConstHostPtr
+CqlHostDataSource::get6(const SubnetID& subnet_id,
+                        const asiolink::IOAddress& address) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_HOST_GET6);
+
+    return impl_->get6(subnet_id, address);
+}
+
+std::string
+CqlHostDataSource::getType() const {
+    return std::string("cql");
+}
+
+std::string
+CqlHostDataSource::getName() const {
+    return impl_->getName();
+}
+
+std::string
+CqlHostDataSource::getDescription() const {
+    return std::string(
+        "Host data source that stores host information in the CQL database");
+}
+
+VersionPair
+CqlHostDataSource::getVersion() const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_CQL_HOST_DB_GET_VERSION);
+
+    return impl_->getVersion();
+}
+
+void
+CqlHostDataSource::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_COMMIT);
+
+    impl_->commit();
+}
+
+void
+CqlHostDataSource::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ROLLBACK);
+
+    impl_->rollback();
+}
+
+};  // namespace dhcp
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/cql_host_data_source.h b/src/lib/dhcpsrv/cql_host_data_source.h
new file mode 100644 (file)
index 0000000..e22e1ed
--- /dev/null
@@ -0,0 +1,291 @@
+// Copyright (C) 2016 Deutsche Telekom AG.
+//
+// Author: Andrei Pavel <andrei.pavel@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//           http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CQL_HOST_DATA_SOURCE_H
+#define CQL_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/cql_connection.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the implementation of @ref CqlHostDataSource.
+class CqlHostDataSourceImpl;
+
+/// @brief Cassandra host data source
+///
+/// Implements @ref isc::dhcp::BaseHostDataSource interface customized to
+/// Cassandra. Use of this backend implies that a Cassandra database is
+/// available and that the Kea schema has been created within it.
+class CqlHostDataSource : public BaseHostDataSource {
+public:
+    /// @brief Constructor
+    ///
+    /// Uses the following keywords in the parameters passed to it to connect
+    /// to the database:
+    /// - name - Name of the database to which to connect (mandatory)
+    /// - host - Host to which to connect (optional, defaults to "localhost")
+    /// - user - Username under which to connect (optional)
+    /// - password - Password for "user" on the database (optional)
+    ///
+    /// If the database is successfully opened, the version number in the
+    /// schema_version table will be checked against hard-coded value in the
+    /// implementation file.
+    ///
+    /// Finally, all the CQL commands are pre-compiled.
+    ///
+    /// @param parameters A data structure relating keywords and values
+    ///        concerned with the database.
+    ///
+    /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+    /// @throw isc::dhcp::DbOpenError Error opening the database
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    explicit CqlHostDataSource(
+        const DatabaseConnection::ParameterMap& parameters);
+
+    /// @brief Virtual destructor.
+    ///
+    /// Releases prepared CQL statements used by the backend.
+    virtual ~CqlHostDataSource();
+
+    /// @brief Adds a new @ref Host to the Cassandra database along with it's
+    ///     reservations and options.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same @ref Host, where possible. For example, when
+    /// the reservation for the same @ref HWAddr and @ref SubnetID is added
+    /// twice, @ref add() should throw a @ref DuplicateEntry exception. Note,
+    /// that usually it is impossible to guard against adding duplicated @ref
+    /// Host, where one instance is identified by @ref HWAddr, another one by
+    /// @ref DUID.
+    ///
+    /// @param host pointer to the new @ref Host being added.
+    virtual void add(const HostPtr& host);
+
+    /// @brief Retrieves a single @ref Host connected to an IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// multiple instances of the @ref Host are present, e.g. when two @ref
+    /// Host objects are found, one for the @ref DUID, another one for the @ref
+    /// HWAddr. In such case, throw a @ref MultipleRecords exception.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param hwaddr hardware address of the client to filter by or NULL if not
+    ///     available
+    /// @param duid client identifier to filter by or NULL if not available
+    ///
+    /// @return @ref ConstHostPtr to a @ref Host object using a specified @ref
+    ///     HWAddr or @ref DUID
+    ///
+    /// @throw BadValue if both or neither of subnet_id and duid are specified
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const HWAddrPtr& hwaddr,
+                              const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv4 subnet.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param identifier_type identifier type to filter by
+    /// @param identifier_begin pointer to the beginning of a buffer containing a
+    ///     host identifier to filter by
+    /// @param identifier_len length of the host identifier buffer
+    ///
+    /// @return @ref Host object for which a reservation has been made using the
+    ///     specified identifier
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv4 subnet.
+    ///
+    /// Note that dynamically allocated addresses and reserved addresses can
+    /// come into conflict. When the new address is assigned to a client, the
+    /// allocation mechanism should check if this address is not reserved for
+    /// some other @ref Host and not allocate it if a reservation is present.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv4 address.
+    ///
+    /// @return Const @ref Host object
+    ///
+    /// @throw BadValue if address in not a valid IPv4address
+    virtual ConstHostPtr get4(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Retrieves a @ref Host connected to an IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// multiple instances of the @ref Host are present, e.g. when two
+    /// @ref Host objects are found, one for the @ref DUID, another one for the
+    /// @ref HWAddr. In such case, throw a @ref MultipleRecords exception.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param hwaddr hardware address of the client to filter by or NULL if not
+    ///     available
+    /// @param duid client identifier to filter by or NULL if not available
+    ///
+    /// @return @ref Host object using a specified @ref HWAddr or @ref DUID
+    ///
+    /// @throw BadValue if both or neither of subnet_id and duid are specified
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const DuidPtr& duid,
+                              const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a @ref Host connected to an IPv6 subnet.
+    ///
+    /// @param subnet_id subnet identifier to filter by
+    /// @param identifier_type identifier type to filter by
+    /// @param identifier_begin pointer to the beginning of a buffer containing a
+    ///     host identifier to filter by
+    /// @param identifier_len length of the host identifier buffer
+    ///
+    /// @return Const @ref Host object for which reservation has been made using
+    /// the specified identifier.
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const Host::IdentifierType& identifier_type,
+                              const uint8_t* identifier_begin,
+                              const size_t identifier_len) const;
+
+    /// @brief Returns a @ref Host with the specified reservation prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @ref Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @ref Host object using a specified HW address or DUID.
+    ///
+    /// @throw MultipleRecords if two or more rows are returned from the
+    ///     Cassandra database
+    virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+                              const uint8_t prefix_len) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet and having
+    /// a reservation for a specified IPv6 address or prefix.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param address reserved IPv6 address/prefix.
+    ///
+    /// @return Const @c Host object using a specified IPv6 address/prefix.
+    virtual ConstHostPtr get6(const SubnetID& subnet_id,
+                              const asiolink::IOAddress& address) const;
+
+    /// @brief Return all @ref Host objects for the specified @ref HWAddr or
+    /// @ref DUID.
+    ///
+    /// Returns all @ref Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// Specifying both @ref HWAddr and @ref DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one @ref Host is associated with the
+    /// specified @ref HWAddr and another @ref Host is associated with the
+    /// specified @ref DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return collection of const @ref Host objects.
+    virtual ConstHostCollection getAll(const HWAddrPtr& hwaddr,
+                                       const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Return all hosts connected to any subnet for which reservations
+    /// have been made using a specified identifier.
+    ///
+    /// This method returns all @ref Host objects which represent reservations
+    /// for a specified identifier. This method may return multiple hosts
+    /// because a particular client may have reservations in multiple subnets.
+    ///
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a beginning of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    ///
+    /// @return Collection of const @ref Host objects.
+    virtual ConstHostCollection
+    getAll(const Host::IdentifierType& identifier_type,
+           const uint8_t* identifier_begin,
+           const size_t identifier_len) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @ref Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @ref Host object is searched.
+    ///
+    /// @return Collection of const @ref Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    ///
+    /// @return Description of the backend.
+    virtual std::string getDescription() const;
+
+    /// @brief Returns the name of the database.
+    ///
+    /// @return database name
+    virtual std::string getName() const;
+
+    /// @brief Return backend type
+    ///
+    /// @return backend type "cql"
+    virtual std::string getType() const;
+
+    /// @brief Retrieves schema version.
+    ///
+    /// @return Version number stored in the database, as a pair of unsigned
+    ///         integers. "first" is the major version number, "second" is the
+    ///         minor version number.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database
+    ///        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 CqlHostDataSource.
+    CqlHostDataSourceImpl* impl_;
+};  // class CqlHostDataSource
+
+};  // namespace dhcp
+};  // namespace isc
+
+#endif  // CQL_HOST_DATA_SOURCE_H
index 8bb91008d8e8b66e00dc5c2bce6173aa9425b2e6..ca5f9c7eb23736c79851b422ea4643a33b27bff4 100644 (file)
@@ -175,9 +175,6 @@ with the specified address to the Cassandra backend database.
 % DHCPSRV_CQL_COMMIT committing to Cassandra database
 A commit call been issued on the server. For Cassandra, this is a no-op.
 
-% DHCPSRV_CQL_BEGIN_TRANSACTION committing to Cassandra database.
-The server has issued a begin transaction call.
-
 % DHCPSRV_CQL_DB opening Cassandra lease database: %1
 This informational message is logged when a DHCP server (either V4 or
 V6) is about to open a Cassandra lease database.  The parameters of
@@ -264,6 +261,10 @@ subnet ID and hardware address.
 A debug message issued when the server is about to obtain schema version
 information from the Cassandra database.
 
+% DHCPSRV_CQL_HOST_RETRIEVE_ERROR Unable to retrieve host reservation or option: %1
+A host couldn't be fully retrieved. An exception was thrown, but the server
+should continue running.
+
 % DHCPSRV_CQL_ROLLBACK rolling back Cassandra database
 The code has issued a rollback call. For Cassandra, this is
 a no-op.
@@ -276,6 +277,30 @@ lease from the Cassandra database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 lease from the Cassandra database for the specified address.
 
+% DHCPSRV_CQL_HOST_DB Connecting to CQL hosts database: %1
+An informational message logged when the CQL hosts database is about to be
+connected to. The parameters of the connection including database name and
+username needed to access it (but not the password if any) are logged.
+
+% DHCPSRV_CQL_HOST_DB_GET_VERSION obtaining schema version information for the CQL hosts database
+A debug message issued when the server is about to obtain schema version
+information from the CQL hosts database.
+
+% DHCPSRV_CQL_HOST_ADD Adding host information to the database
+An informational message logged when options belonging to any reservation from a
+single host are inserted.
+
+% DHCPSRV_CQL_HOST_GET_ALL Retrieving multiple hosts from a CQL database
+An informational message logged when multiple hosts from a CQL database are retrieved.
+
+% DHCPSRV_CQL_HOST_GET4 Retrieving one DHCPv4 host from a CQL database
+An informational message logged when a DHCP server is about to retrieve one
+host from a CQL database by IPv4 criteria.
+
+% DHCPSRV_CQL_HOST_GET6 Retrieving one DHCPv6 host from a CQL database
+An informational message logged when a DHCP server is about to retrieve one
+host from a CQL database by IPv6 criteria.
+
 % DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET received bad DHCPv4o6 packet: %1
 A bad DHCPv4o6 packet was received.
 
index fa080e67ce5d41aaad188b57b7ec2c4376b031e5..40619a81cc5f721d3eb5b9f9c405e004f03f51ca 100644 (file)
@@ -22,7 +22,7 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief HostID (used only when storing in MySQL or Postgres)
+/// @brief HostID (used only when storing in MySQL, PostgreSQL or Cassandra)
 typedef uint64_t HostID;
 
 /// @brief IPv6 reservation for a host.
@@ -531,13 +531,13 @@ public:
     /// @brief Returns information about the host in the textual format.
     std::string toText() const;
 
-    /// @brief Sets Host ID (primary key in MySQL and Postgres backends)
+    /// @brief Sets Host ID (primary key in MySQL, PostgreSQL and Cassandra backends)
     /// @param id HostId value
     void setHostId(HostID id) {
         host_id_ = id;
     }
 
-    /// @brief Returns Host ID (primary key in MySQL and Postgres backends)
+    /// @brief Returns Host ID (primary key in MySQL, PostgreSQL and Cassandra backends)
     /// @return id HostId value (or 0 if not set)
     HostID getHostId() const {
         return (host_id_);
@@ -584,7 +584,7 @@ private:
     std::string boot_file_name_;
 
     /// @brief HostID (a unique identifier assigned when the host is stored in
-    ///                MySQL or Pgsql)
+    ///     MySQL, PostgreSQL or Cassandra)
     uint64_t host_id_;
 
     /// @brief Pointer to the DHCPv4 option data configuration for this host.
index 380d6c8a3b3392cf904213209e6e5dd6b869b1b6..d8e9bf18697e8362fd1c9cb321f714ce4fb654fc 100644 (file)
 #include <dhcpsrv/pgsql_host_data_source.h>
 #endif
 
+#ifdef HAVE_CQL
+#include <dhcpsrv/cql_host_data_source.h>
+#endif
+
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -76,8 +80,10 @@ HostDataSourceFactory::create(const std::string& dbaccess) {
 
 #ifdef HAVE_CQL
     if (db_type == "cql") {
-        isc_throw(NotImplemented, "Sorry, CQL backend for host reservations "
-                  "is not implemented yet.");
+        LOG_INFO(dhcpsrv_logger, DHCPSRV_CQL_HOST_DB)
+            .arg(DatabaseConnection::redactedAccessString(parameters));
+        getHostDataSourcePtr().reset(new CqlHostDataSource(parameters));
+        return;
     }
 #endif
 
index b829748c93a63e330bcb94d4069338e9c4b1c479..74b9bcf432350f3ec6964892d7f89bee6373fceb 100644 (file)
@@ -2519,7 +2519,11 @@ MySqlHostDataSource::get4(const SubnetID& subnet_id,
 ConstHostPtr
 MySqlHostDataSource::get4(const SubnetID& subnet_id,
                           const asiolink::IOAddress& address) const {
-    /// @todo: check that address is really v4, not v6.
+    // Check that address is IPv4, not IPv6.
+    if (!address.isV4()) {
+        isc_throw(BadValue, "MySqlHostDataSource::get4(2): wrong address type, "
+                            "address supplied is not an IPv4 address");
+    }
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
index 8729de987d38668a598d8d44766320df6fe232c6..70aca24d02fbae938caefef3236dced37bb749c0 100644 (file)
@@ -119,6 +119,7 @@ libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
 endif
 if HAVE_CQL
 libdhcpsrv_unittests_SOURCES += cql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += cql_host_data_source_unittest.cc
 endif
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
 libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc
new file mode 100644 (file)
index 0000000..24d0ba4
--- /dev/null
@@ -0,0 +1,505 @@
+// 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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/cql_connection.h>
+#include <dhcpsrv/cql_host_data_source.h>
+#include <dhcpsrv/cql_lease_mgr.h>
+#include <dhcpsrv/host.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/cql_schema.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace std;
+
+namespace {
+
+class CqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+    /// @brief Constructor
+    ///
+    /// Deletes everything from the database and opens it.
+    CqlHostDataSourceTest() {
+        // Ensure schema is the correct one.
+        destroyCqlSchema(false, true);
+        createCqlSchema(false, true);
+
+        // Connect to the database
+        try {
+            HostDataSourceFactory::create(validCqlConnectionString());
+        } catch (...) {
+            std::cerr << "*** ERROR: unable to open database. The test"
+                         "*** environment is broken and must be fixed before"
+                         "*** the CQL tests will run correctly."
+                         "*** The reason for the problem is described in the"
+                         "*** accompanying exception output.";
+            throw;
+        }
+
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+
+    /// @brief Destructor
+    ///
+    /// Rolls back all pending transactions.  The deletion of myhdsptr_ will
+    /// close the database.  Then reopen it and delete everything created by the
+    /// test.
+    virtual ~CqlHostDataSourceTest() {
+        hdsptr_->rollback();
+        HostDataSourceFactory::destroy();
+        destroyCqlSchema(false, true);
+    }
+
+    /// @brief Reopen the database
+    ///
+    /// 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.
+    void reopen(Universe) {
+        HostDataSourceFactory::destroy();
+        HostDataSourceFactory::create(validCqlConnectionString());
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
+    }
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the CqlHostDataSource can be instantiated.  This happens
+/// 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.
+
+TEST(CqlHostDataSource, OpenDatabase) {
+    // Schema needs to be created for the test to work.
+    destroyCqlSchema(false, true);
+    createCqlSchema(false, true);
+
+    // Check that lease manager open the database opens correctly and tidy up.
+    //  If it fails, print the error message.
+    try {
+        HostDataSourceFactory::create(validCqlConnectionString());
+        EXPECT_NO_THROW((void)HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::destroy();
+    } catch (const isc::Exception& ex) {
+        FAIL() << "*** ERROR: unable to open database, reason:\n"
+               << "    " << ex.what() << "\n"
+               << "*** The test environment is broken and must be fixed\n"
+               << "*** before the CQL tests will run correctly.\n";
+    }
+
+    // Check that lease manager open the database opens correctly with a longer
+    // timeout.  If it fails, print the error message.
+    try {
+        std::string connection_string = validCqlConnectionString() +
+                                        std::string(" ") +
+                                        std::string(VALID_TIMEOUT);
+        HostDataSourceFactory::create(connection_string);
+        EXPECT_NO_THROW((void)HostDataSourceFactory::getHostDataSourcePtr());
+        HostDataSourceFactory::destroy();
+    } catch (const isc::Exception& ex) {
+        FAIL() << "*** ERROR: unable to open database, reason:\n"
+               << "    " << ex.what() << "\n"
+               << "*** The test environment is broken and must be fixed\n"
+               << "*** before the CQL tests will run correctly.\n";
+    }
+
+    // Check that attempting to get an instance of the host data source when
+    // none is set throws an exception.
+    EXPECT_FALSE(HostDataSourceFactory::getHostDataSourcePtr());
+
+    // Check that wrong specification of backend throws an exception.
+    // (This is really a check on HostDataSourceFactory, but is convenient to
+    // perform here.)
+    EXPECT_THROW(
+        HostDataSourceFactory::create(connectionString(
+            NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+        InvalidParameter);
+    EXPECT_THROW(
+        HostDataSourceFactory::create(connectionString(
+            INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+        InvalidType);
+
+    // Check that invalid login data does not cause an exception, CQL should use
+    // default values.
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)));
+
+    // Check that invalid timeouts throw DbOperationError.
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_1)),
+                 DbOperationError);
+    EXPECT_THROW(HostDataSourceFactory::create(connectionString(
+                     CQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER,
+                     VALID_PASSWORD, INVALID_TIMEOUT_2)),
+                 DbOperationError);
+
+    // Check that CQL allows the hostname to not be specified.
+    EXPECT_NO_THROW(HostDataSourceFactory::create(connectionString(
+        CQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
+
+    // Tidy up after the test
+    destroyCqlSchema(false, true);
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime.  In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct.  It does not check that the
+/// data is entered into the database correctly, only that the CQL_TIME
+/// structure used for the entry is correctly set up.
+TEST(CqlConnection, checkTimeConversion) {
+    const time_t cltt = time(NULL);
+    const uint32_t valid_lft = 86400;  // 1 day
+    cass_int64_t cql_expire;
+
+    // Convert to the database time
+    CqlExchange::convertToDatabaseTime(cltt, valid_lft, cql_expire);
+
+    // Convert back
+    time_t converted_cltt = 0;
+    CqlExchange::convertFromDatabaseTime(cql_expire, valid_lft, converted_cltt);
+    EXPECT_EQ(cltt, converted_cltt);
+}
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses hw address as identifier.
+TEST_F(CqlHostDataSourceTest, basic4HWAddr) {
+    testBasic4(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by IPv4
+// address. Host uses client-id (DUID) as identifier.
+TEST_F(CqlHostDataSourceTest, basic4ClientId) {
+    testBasic4(Host::IDENT_DUID);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(CqlHostDataSourceTest, getByIPv4HWaddr) {
+    testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+// Test verifies that multiple hosts can be added and later retrieved by their
+// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(CqlHostDataSourceTest, getByIPv4ClientId) {
+    testGetByIPv4(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(CqlHostDataSourceTest, get4ByHWaddr) {
+    testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// DUID.
+TEST_F(CqlHostDataSourceTest, get4ByDUID) {
+    testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// circuit id.
+TEST_F(CqlHostDataSourceTest, get4ByCircuitId) {
+    testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(CqlHostDataSourceTest, hwaddrNotClientId1) {
+    testHWAddrNotClientId();
+}
+
+// Test verifies if hardware address and client identifier are not confused.
+TEST_F(CqlHostDataSourceTest, hwaddrNotClientId2) {
+    testClientIdNotHWAddr();
+}
+
+// Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(CqlHostDataSourceTest, hostnameFQDN) {
+    testHostname("foo.example.org", 1);
+}
+
+// Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+// retrieved.
+TEST_F(CqlHostDataSourceTest, hostnameFQDN100) {
+    testHostname("foo.example.org", 100);
+}
+
+// Test verifies if a host without any hostname specified can be stored and
+// later retrieved.
+TEST_F(CqlHostDataSourceTest, noHostname) {
+    testHostname("", 1);
+}
+
+// Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with hardware address X, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies if the hardware or client-id query can match client-id.
+TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+    /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+    /// be discussed.
+    ///
+    /// @todo: Add host reservation with client identifier Y, try to retrieve
+    /// host for hardware address X or client identifier Y, verify that the
+    /// reservation is returned.
+}
+
+// Test verifies that host with IPv6 address and DUID can be added and
+// later retrieved by IPv6 address.
+TEST_F(CqlHostDataSourceTest, get6AddrWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+// Test verifies that host with IPv6 address and HWAddr can be added and
+// later retrieved by IPv6 address.
+TEST_F(CqlHostDataSourceTest, get6AddrWithHWAddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+// Test verifies that host with IPv6 prefix and DUID can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(CqlHostDataSourceTest, get6PrefixWithDuid) {
+    testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+// Test verifies that host with IPv6 prefix and HWAddr can be added and
+// later retrieved by IPv6 prefix.
+TEST_F(CqlHostDataSourceTest, get6PrefixWithHWaddr) {
+    testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+// Test verifies that host with IPv6 prefix reservation can be retrieved
+// by subnet id and prefix value.
+TEST_F(CqlHostDataSourceTest, get6SubnetPrefix) {
+    testGetBySubnetIPv6();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// hardware address.
+TEST_F(CqlHostDataSourceTest, get6ByHWaddr) {
+    testGet6ByHWAddr();
+}
+
+// Test verifies if a host reservation can be added and later retrieved by
+// client identifier.
+TEST_F(CqlHostDataSourceTest, get6ByClientId) {
+    testGet6ByClientId();
+}
+
+// Test verifies if a host reservation can be stored with both IPv6 address and
+// prefix.
+TEST_F(CqlHostDataSourceTest, addr6AndPrefix) {
+    testAddr6AndPrefix();
+}
+
+// Tests if host with multiple IPv6 reservations can be added and then
+// retrieved correctly. Test checks reservations comparing.
+TEST_F(CqlHostDataSourceTest, multipleReservations) {
+    testMultipleReservations();
+}
+
+// Tests if compareIPv6Reservations() method treats same pool of reservations
+// but added in different order as equal.
+TEST_F(CqlHostDataSourceTest, multipleReservationsDifferentOrder) {
+    testMultipleReservationsDifferentOrder();
+}
+
+// Test verifies if multiple client classes for IPv4 can be stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClasses4) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for IPv6 can be stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClasses6) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v6 client-classes, retrieve it and
+    /// make sure that all client classes are retrieved properly.
+}
+
+// Test verifies if multiple client classes for both IPv4 and IPv6 can be
+// stored.
+TEST_F(CqlHostDataSourceTest, DISABLED_multipleClientClassesBoth) {
+    /// @todo: Implement this test as part of #4213.
+
+    /// Add host reservation with a multiple v4 and v6 client-classes, retrieve
+    /// it and make sure that all client classes are retrieved properly. Also,
+    /// check that the classes are not confused.
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same hardware address). The test logic is as follows:
+// Insert 10 host reservations for a given physical host (the same
+// hardware address), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them all correctly.
+TEST_F(CqlHostDataSourceTest, multipleSubnetsHWAddr) {
+    testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+// Test if the same host can have reservations in different subnets (with the
+// same client identifier). The test logic is as follows:
+//
+// Insert 10 host reservations for a given physical host (the same
+// client-identifier), but for different subnets (different subnet-ids).
+// Make sure that getAll() returns them correctly.
+TEST_F(CqlHostDataSourceTest, multipleSubnetsClientId) {
+    testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+// Test if host reservations made for different IPv6 subnets are handled
+// correctly. The test logic is as follows:
+//
+// Insert 10 host reservations for different subnets. Make sure that
+// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(CqlHostDataSourceTest, subnetId6) {
+    testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same DUID.
+TEST_F(CqlHostDataSourceTest, addDuplicate6WithDUID) {
+    testAddDuplicate6WithSameDUID();
+}
+
+// Test if the duplicate host instances can't be inserted. The test logic is as
+// follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+// Hosts with same HWAddr.
+TEST_F(CqlHostDataSourceTest, addDuplicate6WithHWAddr) {
+    testAddDuplicate6WithSameHWAddr();
+}
+
+// Test if the duplicate IPv4 host instances can't be inserted. The test logic
+// is as follows: try to add multiple instances of the same host reservation and
+// verify that the second and following attempts will throw exceptions.
+TEST_F(CqlHostDataSourceTest, addDuplicate4) {
+    testAddDuplicate4();
+}
+
+// This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, optionsReservations4) {
+    testOptionsReservations4(false);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, optionsReservations6) {
+    testOptionsReservations6(false);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(CqlHostDataSourceTest, optionsReservations46) {
+    testOptionsReservations46(false);
+}
+
+// This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations4) {
+    testOptionsReservations4(true);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the CQL host database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations6) {
+    testOptionsReservations6(true);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+// textual format and retrieved with a single query to the database.
+TEST_F(CqlHostDataSourceTest, formattedOptionsReservations46) {
+    testOptionsReservations46(true);
+}
+
+// This test checks transactional insertion of the host information
+// into the database. The failure to insert host information at
+// any stage should cause the whole transaction to be rolled back.
+TEST_F(CqlHostDataSourceTest, testAddRollback) {
+    // Make sure we have the pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // To test the transaction rollback mechanism we need to cause the
+    // insertion of host information to fail at some stage. The 'hosts'
+    // table should be updated correctly but the failure should occur
+    // when inserting reservations or options. The simplest way to
+    // achieve that is to simply drop one of the tables. To do so, we
+    // connect to the database and issue a DROP query.
+    CqlConnection::ParameterMap params;
+    params["name"] = "keatest";
+    params["user"] = "keatest";
+    params["password"] = "keatest";
+    CqlConnection connection(params);
+    ASSERT_NO_THROW(connection.openDatabase());
+
+    // Drop every table so we make sure host_ipv6_reservation_options doesn't
+    // exist anymore
+    destroyCqlSchema(false, true);
+
+    // Create a host with a reservation.
+    HostPtr host = initializeHost6("2001:db8:1::1", Host::IDENT_HWADDR, false);
+    // Let's assign some DHCPv4 subnet to the host, because we will use the
+    // DHCPv4 subnet to try to retrieve the host after failed insertion.
+    host->setIPv4SubnetID(SubnetID(4));
+
+    // There is no ipv6_reservations table, so the insertion should fail.
+    ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+    // Even though we have created a DHCPv6 host, we can't use get6()
+    // method to retrieve the host from the database, because the
+    // query would expect that the ipv6_reservations table is present.
+    // Therefore, the query would fail. Instead, we use the get4 method
+    // which uses the same client identifier, but doesn't attempt to
+    // retrieve the data from ipv6_reservations table. The query should
+    // pass but return no host because the (insert) transaction is expected
+    // to be rolled back.
+    ASSERT_THROW(
+        hdsptr_->get4(host->getIPv4SubnetID(), host->getIdentifierType(),
+                      &host->getIdentifier()[0], host->getIdentifier().size()),
+        DbOperationError);
+}
+
+};  // anonymous namespace
index 43a87fb860f2e210ff961787c9df6484a1950ebb..db7c494c3847681d26126a1abaabb577157c8030 100644 (file)
@@ -4,6 +4,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+#include <config.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
@@ -261,6 +262,24 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
     compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
 }
 
+bool GenericHostDataSourceTest::compareHostsForSort4(
+    const ConstHostPtr& host1,
+    const ConstHostPtr& host2) {
+    if (host1->getIPv4SubnetID() < host2->getIPv4SubnetID()) {
+        return true;
+    }
+    return false;
+}
+
+bool GenericHostDataSourceTest::compareHostsForSort6(
+    const ConstHostPtr& host1,
+    const ConstHostPtr& host2) {
+    if (host1->getIPv6SubnetID() < host2->getIPv6SubnetID()) {
+        return true;
+    }
+    return false;
+}
+
 DuidPtr
 GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
     if (!hwaddr) {
@@ -817,6 +836,12 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
 
     // Verify that the values returned are proper.
     int i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are eventually implemented as ordered in the Cassandra host data
+        // source.
+        std::sort(all_by_addr.begin(), all_by_addr.end(), compareHostsForSort4);
+    }
     for (ConstHostCollection::const_iterator it = all_by_addr.begin();
          it != all_by_addr.end(); ++it) {
         EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
@@ -831,6 +856,12 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
 
     // Check that the returned values are as expected.
     i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are eventually implemented as ordered in the Cassandra host data
+        // source.
+        std::sort(all_by_id.begin(), all_by_id.end(), compareHostsForSort4);
+    }
     for (ConstHostCollection::const_iterator it = all_by_id.begin();
          it != all_by_id.end(); ++it) {
         EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
@@ -952,6 +983,11 @@ GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
 
     // Check that the returned values are as expected.
     int i = 0;
+    if (hdsptr_->getType() == "cql") {
+        // There is no ORDER BY in Cassandra. Order here. Remove this if entries
+        // are implemented as ordered in the Cassandra host data source.
+        std::sort(all_by_id.begin(), all_by_id.end(), compareHostsForSort6);
+    }
     for (ConstHostCollection::const_iterator it = all_by_id.begin();
          it != all_by_id.end(); ++it) {
         EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
index f84638bb83f9f1a59479907aeed9a3dd5fb50db7..7ada26279c6011f4fc87d80072e3c1f675f4d85b 100644 (file)
@@ -122,6 +122,14 @@ public:
     /// @param host2 second host to compare
     void compareHosts(const ConstHostPtr& host1, const ConstHostPtr& host2);
 
+    /// @ brief Used to sort a host collection by IPv4 subnet id.
+    static bool compareHostsForSort4(const ConstHostPtr& host1,
+                                     const ConstHostPtr& host2);
+
+    /// @ brief Used to sort a host collection by IPv6 subnet id.
+    static bool compareHostsForSort6(const ConstHostPtr& host1,
+                                     const ConstHostPtr& host2);
+
     /// @brief Compares two IPv6 reservation lists.
     ///
     /// This method uses gtest macros to signal errors.
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d184e0fe314e46b5a85e5caf3a7761a0f4baa24a 100644 (file)
@@ -0,0 +1,2 @@
+upgrade_1.0_to_2.0.sh
+
index 82f47fc8dd50098c2fd72c799caca6b29d552f32..3d46022eac1178246655c18ecbdc84d8126f955d 100644 (file)
@@ -185,3 +185,45 @@ CREATE TABLE schema_version (
 INSERT INTO schema_version (version, minor) VALUES (1, 0);
 
 -- This line concludes database initalization to version 1.0.
+
+-- This line starts database upgrade to version 2.0
+
+-- -----------------------------------------------------
+-- Table `host_reservations`
+-- -----------------------------------------------------
+CREATE TABLE host_reservations (
+    id bigint,
+    host_identifier blob,
+    host_identifier_type int,
+    host_ipv4_subnet_id int,
+    host_ipv6_subnet_id int,
+    host_ipv4_address int,
+    hostname text,
+    host_ipv4_client_classes text,
+    host_ipv6_client_classes text,
+    reserved_ipv6_prefix_address text,
+    reserved_ipv6_prefix_length int,
+    reserved_ipv6_prefix_address_type int,
+    iaid int,
+    option_universe int,
+    option_code int,
+    option_value blob,
+    option_formatted_value text,
+    option_space text,
+    option_is_persistent boolean,
+    option_client_class text,
+    option_subnet_id int,
+    PRIMARY KEY (id)
+);
+CREATE INDEX host_reservationsindex1 ON host_reservations (host_identifier);
+CREATE INDEX host_reservationsindex2 ON host_reservations (host_identifier_type);
+CREATE INDEX host_reservationsindex3 ON host_reservations (host_ipv4_subnet_id);
+CREATE INDEX host_reservationsindex4 ON host_reservations (host_ipv6_subnet_id);
+CREATE INDEX host_reservationsindex5 ON host_reservations (host_ipv4_address);
+CREATE INDEX host_reservationsindex6 ON host_reservations (reserved_ipv6_prefix_address);
+CREATE INDEX host_reservationsindex7 ON host_reservations (reserved_ipv6_prefix_length);
+
+DELETE FROM schema_version WHERE version=1;
+INSERT INTO schema_version (version, minor) VALUES(2, 0);
+
+-- This line concludes database upgrade to version 2.0
index aa0631937a1d926eb6b6ad049d2fd7a161ffa3b4..4f2fbee46a4f880d5da1acad8d4ad171000bd9cd 100644 (file)
@@ -21,10 +21,9 @@ DROP TABLE IF EXISTS lease_hwaddr_source;
 DROP TABLE IF EXISTS schema_version;
 DROP TABLE IF EXISTS ipv6_reservations;
 DROP TABLE IF EXISTS hosts;
-DROP TABLE IF EXISTS dhcp4_options;
-DROP TABLE IF EXISTS dhcp6_options;
 DROP TABLE IF EXISTS host_identifier_type;
 DROP TABLE IF EXISTS lease_state;
+DROP TABLE IF EXISTS host_reservations;
 
 DROP INDEX IF EXISTS lease4index1;
 DROP INDEX IF EXISTS lease4index2;
@@ -38,3 +37,11 @@ DROP INDEX IF EXISTS lease6index3;
 DROP INDEX IF EXISTS lease6index4;
 DROP INDEX IF EXISTS lease6index5;
 DROP INDEX IF EXISTS lease6index6;
+
+DROP INDEX IF EXISTS host_reservationsindex1;
+DROP INDEX IF EXISTS host_reservationsindex2;
+DROP INDEX IF EXISTS host_reservationsindex3;
+DROP INDEX IF EXISTS host_reservationsindex4;
+DROP INDEX IF EXISTS host_reservationsindex5;
+DROP INDEX IF EXISTS host_reservationsindex6;
+DROP INDEX IF EXISTS host_reservationsindex7;
diff --git a/src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in b/src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh.in
new file mode 100644 (file)
index 0000000..b08d4f1
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+prefix=@prefix@
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+    . @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh
+else
+    . @abs_top_builddir@/src/bin/admin/admin-utils.sh
+fi
+
+VERSION=`cql_version "$@"`
+
+if [ "$VERSION" != "1.0" ]; then
+    printf "This script upgrades 1.0 to 2.0. Reported version is $VERSION. Skipping upgrade.\n"
+    exit 0
+fi
+
+cqlsh "$@" <<EOF
+-- This line starts database upgrade to version 2.0
+
+-- -----------------------------------------------------
+-- Table `host_reservations`
+-- -----------------------------------------------------
+CREATE TABLE host_reservations (
+    id bigint,
+    host_identifier blob,
+    host_identifier_type int,
+    host_ipv4_subnet_id int,
+    host_ipv6_subnet_id int,
+    host_ipv4_address int,
+    hostname text,
+    host_ipv4_client_classes text,
+    host_ipv6_client_classes text,
+    reserved_ipv6_prefix_address text,
+    reserved_ipv6_prefix_length int,
+    reserved_ipv6_prefix_address_type int,
+    iaid int,
+    option_universe int,
+    option_code int,
+    option_value blob,
+    option_formatted_value text,
+    option_space text,
+    option_is_persistent boolean,
+    option_client_class text,
+    option_subnet_id int,
+    PRIMARY KEY (id)
+);
+CREATE INDEX host_reservationsindex1 ON host_reservations (host_identifier);
+CREATE INDEX host_reservationsindex2 ON host_reservations (host_identifier_type);
+CREATE INDEX host_reservationsindex3 ON host_reservations (host_ipv4_subnet_id);
+CREATE INDEX host_reservationsindex4 ON host_reservations (host_ipv6_subnet_id);
+CREATE INDEX host_reservationsindex5 ON host_reservations (host_ipv4_address);
+CREATE INDEX host_reservationsindex6 ON host_reservations (reserved_ipv6_prefix_address);
+CREATE INDEX host_reservationsindex7 ON host_reservations (reserved_ipv6_prefix_length);
+
+DELETE FROM schema_version WHERE version=1;
+INSERT INTO schema_version (version, minor) VALUES(2, 0);
+
+-- This line concludes database upgrade to version 2.0
+EOF
+
+exit $?