Commit 79481043 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac4212'

parents c54beb5f 34d21432
......@@ -84,7 +84,8 @@ We have received the following contributions:
2015-01: Extract MAC address from remote-id
2015-05: MySQL schema extended to cover host reservation
2015-10: Common MySQL Connector Pool
2015-12: MySQL host data source implemented.
2015-12: MySQL host data source implemented
2016-02: IPv6 reservations implemented
- Jinmei Tatuya
2015-10: Pkt4o6 class improvements
......
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -10,6 +10,7 @@
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <sstream>
#include <vector>
namespace isc {
......@@ -28,6 +29,18 @@ ClientClasses::ClientClasses(const std::string& class_names)
}
}
}
std::string
ClientClasses::toText(const std::string& separator) const {
std::stringstream s;
for (const_iterator class_it = begin(); class_it != end(); ++class_it) {
if (class_it != begin()) {
s << separator;
}
s << *class_it;
}
return (s.str());
}
} // end of namespace isc::dhcp
} // end of namespace isc
......
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -65,6 +65,13 @@ namespace dhcp {
contains(const ClientClass& x) const {
return (find(x) != end());
}
/// @brief Returns all class names as text
///
/// @param separator Separator to be used between class names. The
/// default separator comprises comma sign followed by space
/// character.
std::string toText(const std::string& separator = ", ") const;
};
};
......
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-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
......@@ -107,3 +107,27 @@ TEST(ClassifyTest, ClientClassesIterator) {
EXPECT_TRUE(seengamma);
EXPECT_FALSE(seendelta);
}
// Check that the ClientClasses::toText function returns
// correct values.
TEST(ClassifyTest, ClientClassesToText) {
// No classes.
ClientClasses classes;
EXPECT_TRUE(classes.toText().empty());
// Insert single class name and see if it doesn't include spurious
// comma after it.
classes.insert("alpha");
EXPECT_EQ("alpha", classes.toText());
// Insert next class name and see that both classes are present.
classes.insert("gamma");
EXPECT_EQ("alpha, gamma", classes.toText());
// Insert third class and make sure they get ordered alphabetically.
classes.insert("beta");
EXPECT_EQ("alpha, beta, gamma", classes.toText());
// Check non-standard separator.
EXPECT_EQ("alpha.beta.gamma", classes.toText("."));
}
......@@ -78,7 +78,7 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
dhcp6_client_classes_(dhcp6_client_classes),
dhcp6_client_classes_(dhcp6_client_classes), host_id_(0),
cfg_option4_(), cfg_option6_() {
// Initialize HWAddr or DUID
......@@ -100,7 +100,7 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
dhcp6_client_classes_(dhcp6_client_classes),
dhcp6_client_classes_(dhcp6_client_classes), host_id_(0),
cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) {
// Initialize HWAddr or DUID
......
......@@ -22,6 +22,9 @@
namespace isc {
namespace dhcp {
/// @brief HostID (used only when storing in MySQL or Postgres)
typedef uint64_t HostID;
/// @brief IPv6 reservation for a host.
///
/// This class represents a reservation for a host of a single IPv6
......@@ -447,6 +450,18 @@ 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)
/// @param id HostId value
void setHostId(HostID id) {
host_id_ = id;
}
/// @brief Returns Host ID (primary key in MySQL and Postgres backends)
/// @return id HostId value (or 0 if not set)
HostID getHostId() const {
return (host_id_);
}
private:
/// @brief Adds new client class for DHCPv4 or DHCPv6.
......@@ -482,6 +497,11 @@ private:
ClientClasses dhcp4_client_classes_;
/// @brief Collection of classes associated with a DHCPv6 client.
ClientClasses dhcp6_client_classes_;
/// @brief HostID (a unique identifier assigned when the host is stored in
/// MySQL or Pgsql)
uint64_t host_id_;
/// @brief Pointer to the DHCPv4 option data configuration for this host.
CfgOptionPtr cfg_option4_;
/// @brief Pointer to the DHCPv6 option data configuration for this host.
......
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -22,21 +22,10 @@ using namespace std;
namespace isc {
namespace dhcp {
/// @brief Maximum size of database fields
///
/// The following constants define buffer sizes for variable length database
/// fields. The values should be greater than or equal to the length set in
/// the schema definition.
///
/// The exception is the length of any VARCHAR fields: buffers for these should
/// be set greater than or equal to the length of the field plus 1: this allows
/// for the insertion of a trailing null whatever data is returned.
const my_bool MLM_FALSE = 0; ///< False value
const my_bool MLM_TRUE = 1; ///< True value
///@}
const my_bool MLM_FALSE = 0;
const my_bool MLM_TRUE = 1;
const int MLM_MYSQL_FETCH_SUCCESS = 0;
const int MLM_MYSQL_FETCH_FAILURE = 1;
// Open the database using the parameters passed to the constructor.
......
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -16,20 +16,31 @@
namespace isc {
namespace dhcp {
/// @brief MySQL True/False constants
/// @name MySQL constants.
///
/// Declare typed values so as to avoid problems of data conversion. These
/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
/// avoid any likely conflicts with variables in header files named TRUE or
/// FALSE.
//@{
/// @brief MySQL false value.
extern const my_bool MLM_FALSE;
/// @brief MySQL true value.
extern const my_bool MLM_TRUE;
// Define the current database schema values
/// @brief MySQL fetch success code.
extern const int MLM_MYSQL_FETCH_SUCCESS;
/// @brief MySQL fetch failure code.
extern const int MLM_MYSQL_FETCH_FAILURE;
//@}
/// @name Current database schema version values.
//@{
const uint32_t CURRENT_VERSION_VERSION = 3;
const uint32_t CURRENT_VERSION_MINOR = 0;
//@}
/// @brief Fetch and Release MySQL Results
///
/// When a MySQL statement is expected, to fetch the results the function
......
This diff is collapsed.
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -7,26 +7,20 @@
#ifndef MYSQL_HOST_DATA_SOURCE_H
#define MYSQL_HOST_DATA_SOURCE_H
#include <dhcp/hwaddr.h>
#include <dhcpsrv/base_host_data_source.h>
#include <dhcpsrv/mysql_connection.h>
#include <boost/scoped_ptr.hpp>
#include <boost/utility.hpp>
#include <mysql.h>
namespace isc {
namespace dhcp {
// Forward declaration of the Host exchange objects. These classes are defined
// in the .cc file.
class MySqlHostReservationExchange;
/// Forward declaration to the implementation of the @ref MySqlHostDataSource.
class MySqlHostDataSourceImpl;
/// @brief MySQL Host Data Source
///
/// This class provides the @ref isc::dhcp::BaseHostDataSource interface to the MySQL
/// database. Use of this backend presupposes that a MySQL database is
/// available and that the Kea schema has been created within it.
/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
/// the MySQL database. Use of this backend presupposes that a MySQL database
/// is available and that the Kea schema has been created within it.
class MySqlHostDataSource: public BaseHostDataSource {
public:
......@@ -54,7 +48,9 @@ public:
/// failed.
MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
/// @brief Destructor (closes database)
/// @brief Virtual destructor.
///
/// Releases prepared MySQL statements used by the backend.
virtual ~MySqlHostDataSource();
/// @brief Return all hosts for the specified HW address or DUID.
......@@ -108,7 +104,7 @@ public:
/// @return Const @c Host object using a specified HW address or DUID.
virtual ConstHostPtr
get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
const DuidPtr& duid = DuidPtr()) const;
const DuidPtr& duid = DuidPtr()) const;
/// @brief Returns a host connected to the IPv4 subnet and having
/// a reservation for a specified IPv4 address.
......@@ -179,9 +175,9 @@ public:
/// @brief Returns backend name.
///
/// Each backend have specific name, e.g. "mysql" or "sqlite".
/// Each backend have specific name.
///
/// @return Name of the backend.
/// @return "mysql".
virtual std::string getName() const;
/// @brief Returns description of the backend.
......@@ -203,25 +199,20 @@ public:
/// @brief Commit Transactions
///
/// Commits all pending database operations. On databases that don't
/// support transactions, this is a no-op.
/// Commits all pending database operations.
virtual void commit();
/// @brief Rollback Transactions
///
/// Rolls back all pending database operations. On databases that don't
/// support transactions, this is a no-op.
/// Rolls back all pending database operations.
virtual void rollback();
MySqlConnection* getDatabaseConnection() {
return &conn_;
}
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements
enum StatementIndex {
INSERT_HOST, // Insert new host to collection
INSERT_V6_RESRV, // Insert v6 reservation
GET_HOST_HWADDR_DUID, // Gets hosts by DUID and/or HW address
GET_HOST_ADDR, // Gets hosts by IPv4 address
GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
......@@ -233,81 +224,14 @@ public:
};
private:
/// @brief Add Host Code
///
/// This method performs adding a host operation.
/// It binds the contents of the host object to
/// the prepared statement and adds it to the database.
///
/// @param stindex Index of statemnent being executed
/// @param bind MYSQL_BIND array that has been created for the host
///
/// @htrow isc::dhcp::DuplicateEntry Database throws duplicate entry error
void addHost(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
/// @brief Get Host Collection Code
///
/// This method obtains multiple hosts from the database.
///
/// @param stindex Index of statement being executed
/// @param bind MYSQL_BIND array for input parameters
/// @param exchange Exchange object to use
/// @param result ConstHostCollection object returned. Note that any hosts
/// in the collection when this method is called are not erased: the
/// new data is appended to the end.
/// @param single If true, only a single data item is to be retrieved.
/// If more than one is present, a MultipleRecords exception will
/// be thrown.
///
/// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
/// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
/// from the database where only one was expected.
void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
boost::shared_ptr<MySqlHostReservationExchange> exchange,
ConstHostCollection& result, bool single = false) const;
/// @brief Check Error and Throw Exception
///
/// Virtually all MySQL functions return a status which, if non-zero,
/// indicates an error. This inline function conceals a lot of error
/// checking/exception-throwing code.
///
/// @param status Status code: non-zero implies an error
/// @param index Index of statement that caused the error
/// @param what High-level description of the error
///
/// @throw isc::dhcp::DbOperationError An operation on the open database
/// has failed.
inline void checkError(int status, StatementIndex index,
const char* what) const {
if (status != 0) {
isc_throw(DbOperationError, what << " for <"
<< conn_.text_statements_[index] << ">, reason: "
<< mysql_error(conn_.mysql_) << " (error code "
<< mysql_errno(conn_.mysql_) << ")");
}
}
/// @brief Checks if Host with same parameters already been added.
/// @brief Checks if the specified host already exists in the database.
///
/// @param host Pointer to the new @c Host object being added.
bool checkIfExists(const HostPtr& host);
// Members
/// The exchange objects are used for transfer of data to/from the database.
/// They are pointed-to objects as the contents may change in "const" calls,
/// while the rest of this object does not. (At alternative would be to
/// declare them as "mutable".)
/// @brief MySQL Host Reservation Exchange object
boost::shared_ptr<MySqlHostReservationExchange> hostExchange_;
/// @brief MySQL connection
MySqlConnection conn_;
/// @brief Pointer to the implementation of the @ref MySqlHostDataSource.
MySqlHostDataSourceImpl* impl_;
};
}
......
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -242,8 +242,7 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
// Compare IPv6 reservations
compareReservations6(host1->getIPv6Reservations(),
host2->getIPv6Reservations(),
true);
host2->getIPv6Reservations());
// And compare client classification details
compareClientClasses(host1->getClientClasses4(),
......@@ -274,25 +273,50 @@ GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
void
GenericHostDataSourceTest::compareReservations6(IPv6ResrvRange resrv1,
IPv6ResrvRange resrv2,
bool expect_match) {
IPv6ResrvRange resrv2) {
// Compare number of reservations for both hosts
if (std::distance(resrv1.first, resrv1.second) !=
std::distance(resrv2.first, resrv2.second)){
// Number of reservations is not equal.
// Let's see if it's a problem.
if (expect_match) {
ADD_FAILURE()<< "Reservation comparison failed, "
"hosts got different number of reservations.";
}
ADD_FAILURE()<< "Reservation comparison failed, "
"hosts got different number of reservations.";
return;
}
// Iterate over the range of reservations to find a match in the
// reference range.
for (IPv6ResrvIterator r1 = resrv1.first; r1 != resrv1.second; ++r1) {
IPv6ResrvIterator r2 = resrv2.first;
for (; r2 != resrv2.second; ++r2) {
// IPv6Resrv object implements equality operator.
if (r1->second == r2->second) {
break;
}
}
// If r2 iterator reached the end of the range it means that there
// is no match.
if (r2 == resrv2.second) {
ADD_FAILURE() << "No match found for reservation: "
<< resrv1.first->second.getPrefix().toText();
}
}
if (std::distance(resrv1.first, resrv1.second) > 0) {
if (expect_match){
/// @todo Compare every reservation from both hosts
/// This is part of the work for #4212.
for (; resrv1.first != resrv1.second; resrv1.first++) {
IPv6ResrvIterator iter = resrv2.first;
while (iter != resrv2.second) {
if((resrv1.first->second.getType() == iter->second.getType()) &&
(resrv1.first->second.getPrefixLen() == iter->second.getPrefixLen()) &&
(resrv1.first->second.getPrefix() == iter->second.getPrefix())) {
break;
}
iter++;
if (iter == resrv2.second) {
ADD_FAILURE()<< "Reservation comparison failed, "
"no match for reservation: "
<< resrv1.first->second.getPrefix().toText();
}
}
}
}
}
......@@ -600,8 +624,8 @@ void GenericHostDataSourceTest::testGet6ByHWAddr() {
ASSERT_TRUE(hdsptr_);
// Create a host reservations.
HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_HWADDR, true);
HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
HostPtr host1 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
HostPtr host2 = initializeHost6("2001:db8::2", BaseHostDataSource::ID_HWADDR, true);
// Sanity check: make sure the hosts have different HW addresses.
ASSERT_TRUE(host1->getHWAddress());
......@@ -631,8 +655,8 @@ void GenericHostDataSourceTest::testGet6ByClientId() {
ASSERT_TRUE(hdsptr_);
// Create a host reservations.
HostPtr host1 = initializeHost6("2001:db8::0", BaseHostDataSource::ID_DUID, true);
HostPtr host2 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
HostPtr host1 = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
HostPtr host2 = initializeHost6("2001:db8::2", BaseHostDataSource::ID_DUID, true);
// Sanity check: make sure the hosts have different HW addresses.
ASSERT_TRUE(host1->getDuid());
......@@ -741,19 +765,128 @@ void GenericHostDataSourceTest::testGetByIPv6(BaseHostDataSource::IdType id,
EXPECT_FALSE(hdsptr_->get6(IOAddress("2001:db8::5"), len));
}
void GenericHostDataSourceTest::testAddDuplicate() {
void GenericHostDataSourceTest::testAddDuplicate6WithSameDUID() {
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Create a host reservations.
HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
// Add this reservation once.
ASSERT_NO_THROW(hdsptr_->add(host));
// Then try to add it again, it should throw an exception.
ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
}
void GenericHostDataSourceTest::testAddDuplicate6WithSameHWAddr() {
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Create a host reservations.
HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID,
true);
HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_HWADDR, true);
// Add this reservation once.
ASSERT_NO_THROW(hdsptr_->add(host));
// Then try to add it again, it should throw an exception.
ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
}
void GenericHostDataSourceTest::testAddDuplicate4() {
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Create a host reservations.
HostPtr host = initializeHost4("192.0.2.1", false);
// Add this reservation once.
ASSERT_NO_THROW(hdsptr_->add(host));
// Then try to add it again, it should throw an exception.
ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
}
void GenericHostDataSourceTest::testAddr6AndPrefix(){
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Create a host reservations with prefix reservation (prefix = true)
HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, true);
// Create IPv6 reservation (for an address) and add it to the host
IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2"), 128);
host->addReservation(resv);
// Add this reservation
ASSERT_NO_THROW(hdsptr_->add(host));
// Get this host by DUID
ConstHostPtr from_hds = hdsptr_->get6(host->getIPv6SubnetID(), host->getDuid(), HWAddrPtr());
// Make sure we got something back
ASSERT_TRUE(from_hds);
// Check if reservations are the same
compareReservations6(host->getIPv6Reservations(), from_hds->getIPv6Reservations());
}
void GenericHostDataSourceTest::testMultipleReservations(){
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
uint8_t len = 128;
HostPtr host = initializeHost6("2001:db8::1", BaseHostDataSource::ID_DUID, false);
// Add some reservations
IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::7"), len);
IPv6Resrv resv3(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::8"), len);
IPv6Resrv resv4(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::9"), len);
host->addReservation(resv1);
host->addReservation(resv2);
host->addReservation(resv3);
host->addReservation(resv4);
ASSERT_NO_THROW(hdsptr_->add(host));
ConstHostPtr from_hds = hdsptr_->get6(IOAddress("2001:db8::1"), len);
// Make sure we got something back
ASSERT_TRUE(from_hds);
// Check if hosts are the same
compareHosts(host, from_hds);
}