Commit b64567ca authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

Merge branch 'cassandra-host-data-source' of https://github.com/andreipavelQ/kea into github37

parents 7272a2f2 a58638ae
......@@ -1313,6 +1313,7 @@ AC_CONFIG_FILES([Makefile
src/share/database/Makefile
src/share/database/scripts/Makefile
src/share/database/scripts/cql/Makefile
src/share/database/scripts/cql/upgrade_1.0_to_2.0.sh
src/share/database/scripts/mysql/Makefile
src/share/database/scripts/mysql/upgrade_1.0_to_2.0.sh
src/share/database/scripts/mysql/upgrade_2.0_to_3.0.sh
......
......@@ -160,19 +160,19 @@
<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>Memfile</entry>
<entry>MySQL</entry>
<entry>PostgreSQL</entry>
<entry>CQL(Cassandra)</entry>
<entry>CQL (Cassandra)</entry>
</row>
</thead>
<tbody>
......@@ -206,7 +206,7 @@
<entry>no</entry>
<entry>yes</entry>
<entry>yes</entry>
<entry>no</entry>
<entry>yes</entry>
</row>
<row>
......@@ -214,7 +214,7 @@
<entry>no</entry>
<entry>yes</entry>
<entry>yes</entry>
<entry>no</entry>
<entry>yes</entry>
</row>
</tbody>
......@@ -409,6 +409,8 @@ $ <userinput>kea-admin lease-upgrade mysql -u <replaceable>database-user</replac
<title>PostgreSQL</title>
<para>
PostgreSQL is able to store leases, host reservations and options
defined on a per host basis.
A PostgreSQL database must be set up if you want Kea to store
lease and other information in PostgreSQL. This step can be
safely ignored if you are using other database backends.
......@@ -590,10 +592,9 @@ $ <userinput>kea-admin lease-upgrade pgsql -u <replaceable>database-user</replac
<para>
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.
testing as other backends, it is considered experimental. Please use
with caution. The Casandra backend is currently able to store leases,
host reservations and options defined on a per host basis.
</para>
<para>
......
......@@ -517,8 +517,7 @@ If a timeout is given though, it should be an integer greater than zero.
purpose, be it lease or hosts information. This arrangement gives the most
flexibility. Kea can be used to keep leases and host reservations
separately, but can also point to the same database. Currently the
supported hosts database types are MySQL and PostgreSQL. The Cassandra
backend does not support host reservations yet.</para>
supported hosts database types are MySQL, PostgreSQL and Cassandra.</para>
<para>Please note that usage of hosts storage is optional. A user can define
all host reservations in the configuration file. That is the recommended way
......@@ -3387,13 +3386,13 @@ 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 Cassandra</title>
<para>
It is possible to store host reservations in MySQL or PostgreSQL. See <xref
linkend="hosts6-storage" /> for information on how to configure Kea to use
reservations stored in MySQL or PostgreSQL. Kea provides dedicated hook for
It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
<xref linkend="hosts6-storage" /> for information on how to configure Kea to use
reservations stored in MySQL, PostgreSQL or Cassandra. Kea provides dedicated hook for
managing reservations in a database, section <xref linkend="host-cmds" /> provide
detailed information.
</para>
......@@ -3402,12 +3401,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>
......
......@@ -2905,13 +2905,13 @@ should include options from the isc option space:
with classification using expressions.</para>
</section>
<section id="reservations6-mysql-pgsql">
<title>Storing Host Reservations in MySQL or PostgreSQL</title>
<section id="reservations6-mysql-pgsql-cql">
<title>Storing Host Reservations in MySQL, PostgreSQL or Cassandra</title>
<para>
It is possible to store host reservations in MySQL or PostgreSQL. See <xref
linkend="hosts6-storage" /> for information on how to configure Kea to use
reservations stored in MySQL or PostgreSQL. Kea provides dedicated hook for
It is possible to store host reservations in MySQL, PostgreSQL or Cassandra. See
<xref linkend="hosts6-storage" /> for information on how to configure Kea to use
reservations stored in MySQL, PostgreSQL or Cassandra. Kea provides dedicated hook for
managing reservations in a database, section <xref linkend="host-cmds" /> provide
detailed information.
</para>
......@@ -2920,12 +2920,6 @@ should include options from the isc option space:
arbitrarily set to 4096 bytes.</simpara></note>
</section>
<section id="reservations6-cql">
<title>Storing Host Reservations in CQL (Cassandra)</title>
<para>Kea currently does not support storing reservations in
Cassandra (CQL).</para>
</section>
<section id="reservations6-tuning">
<title>Fine Tuning DHCPv6 Host Reservation</title>
......
......@@ -82,7 +82,7 @@ cql_lease_version_test() {
# Verify that kea-admin lease-version returns the correct version.
version=$($keaadmin lease-version cql -u $db_user -p $db_password -n $db_name)
assert_str_eq "1.0" $version "Expected kea-admin to return %s, returned value was %s"
assert_str_eq "2.0" $version "Expected kea-admin to return %s, returned value was %s"
# Wipe the database.
cql_execute_script $db_scripts_dir/cql/dhcpdb_drop.cql
......
......@@ -153,6 +153,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
......
......@@ -48,9 +48,9 @@ constexpr uint32_t CQL_DRIVER_VERSION_MAJOR = CASS_VERSION_MAJOR;
constexpr uint32_t CQL_DRIVER_VERSION_MINOR = CASS_VERSION_MINOR;
/// @}
/// Define CQL schema version: 1.0
/// Define CQL schema version: 2.0
/// @{
constexpr uint32_t CQL_SCHEMA_VERSION_MAJOR = 1u;
constexpr uint32_t CQL_SCHEMA_VERSION_MAJOR = 2u;
constexpr uint32_t CQL_SCHEMA_VERSION_MINOR = 0u;
/// @}
......
This diff is collapsed.
// Copyright (C) 2016-2017 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 <string>
#include <vector>
namespace isc {
namespace dhcp {
/// @brief 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 host to the collection.
///
/// 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) override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @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 override;
/// @brief Attempts to delete a host by (subnet-id, address)
///
/// This method supports both v4 and v6.
///
/// @param subnet_id subnet identfier.
/// @param addr specified address.
/// @return true if deletion was successful, false if the host was not
/// there.
/// @throw various exceptions in case of errors
virtual bool del(const SubnetID& subnet_id,
const asiolink::IOAddress& addr);
/// @brief Attempts to delete a host by (subnet-id4, identifier,
/// identifier-type)
///
/// This method supports both v4 hosts only.
///
/// @param subnet_id IPv4 Subnet identifier.
/// @param identifier_type Identifier type.
/// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
/// @return true if deletion was successful, false if the host was not
/// there.
/// @throw various exceptions in case of errors
virtual bool del4(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len);
/// @brief Attempts to delete a host by (subnet-id6, identifier,
/// identifier-type)
///
/// This method supports both v6 hosts only.
///
/// @param subnet_id IPv6 Subnet identifier.
/// @param identifier_type Identifier type.
/// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
/// @return true if deletion was successful, false if the host was not
/// there.
/// @throw various exceptions in case of errors
virtual bool del6(const SubnetID& subnet_id,
const Host::IdentifierType& identifier_type,
const uint8_t* identifier_begin,
const size_t identifier_len);
/// @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 override;
/// @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 VersionPair getVersion() const;
/// @brief Commit Transactions
///
/// Commits all pending database operations.
virtual void commit() override;
/// @brief Rollback Transactions
///
/// Rolls back all pending database operations.
virtual void rollback() override;
private:
/// @brief Pointer to the implementation of the @ref CqlHostDataSource.
CqlHostDataSourceImpl* impl_;
}; // class CqlHostDataSource
} // namespace dhcp
} // namespace isc
#endif // CQL_HOST_DATA_SOURCE_H
......@@ -23,7 +23,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.
......@@ -533,13 +533,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_);
......@@ -596,7 +596,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.
......
......@@ -4,7 +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 <config.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/host_data_source_factory.h>
......@@ -18,6 +18,10 @@
#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>
......@@ -34,7 +38,6 @@ using namespace std;
namespace isc {
namespace dhcp {
HostDataSourcePtr&
HostDataSourceFactory::getHostDataSourcePtr() {
static HostDataSourcePtr hostDataSourcePtr;
......@@ -76,8 +79,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
......@@ -109,5 +114,5 @@ HostDataSourceFactory::instance() {
}
#endif
}; // namespace dhcp
}; // namespace isc
} // namespace dhcp
} // namespace isc
......@@ -2694,7 +2694,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];
......
......@@ -122,6 +122,7 @@ endif
if HAVE_CQL
libdhcpsrv_unittests_SOURCES += cql_connection_unittest.cc
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 += shared_network_parser_unittest.cc
......