#include <config.h>
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
TaggedStatement tagged_statements[] = {
{MySqlHostDataSource::INSERT_HOST,
"INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
- "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
- "dhcp4_client_classes, dhcp6_client_classes) "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
{MySqlHostDataSource::INSERT_V6_RESRV,
- "INSERT INTO ipv6_reservations(host_id, address, prefix_len, type, dhcp6_iaid) "
- "VALUES (?,?,?,?,?)"},
+ "INSERT INTO ipv6_reservations(reservation_id, address, prefix_len, type, "
+ "dhcp6_iaid, host_id) "
+ "VALUES (?,?,?,?,?,?)"},
{MySqlHostDataSource::GET_V6_RESRV,
- "SELECT address, prefix_len, type, dhcp6_iaid FROM ipv6_reservations "
+ "SELECT reservation_id, address, prefix_len, type, dhcp6_iaid, host_id "
+ "FROM ipv6_reservations "
"WHERE host_id = ?"},
{MySqlHostDataSource::GET_HOST_HWADDR_DUID,
"SELECT host_id, dhcp_identifier, dhcp_identifier_type, "
"dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
"dhcp4_client_classes, dhcp6_client_classes "
"FROM hosts h, ipv6_reservations r "
- "WHERE h.host_id = r.host_id AND r.prefix_len = ? "
- " AND r.address = ?"},
+ "WHERE h.host_id = r.host_id "
+ "AND r.address = ? AND r.prefix_len = ?"},
{MySqlHostDataSource::GET_VERSION,
"SELECT version, minor FROM schema_version"},
{MySqlHostDataSource::NUM_STATEMENTS, NULL}
class MySqlIPv6ReservationExchange {
/// @brief Set number of database columns for this reservation structure
- static const size_t RESRV_COLUMNS = 5;
+ static const size_t RESRV_COLUMNS = 6;
public:
return (result);
}
- /// @brief Create MYSQL_BIND objects for Host Pointer
+ /// @brief Create MYSQL_BIND objects for IPv6 Reservation
///
- /// Fills in the MYSQL_BIND array for sending data in the Host object to
- /// the database.
+ /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
+ /// object to the database.
///
- /// @param host Host object to be added to the database.
- /// None of the fields in the host reservation are modified -
- /// the host data is only read.
+ /// @param resv IPv6 reservation object to be added to the database.
+ /// None of the fields in the reservation are modified -
+ /// the reservation data is only read.
+ /// @param id ID of a host owning this reservation
///
/// @return Vector of MySQL BIND objects representing the data to be added.
std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv, const HostID& id) {
// Set up the structures for the various components of the host structure.
try {
- // host_id : INT UNSIGNED NOT NULL
- host_id_ = static_cast<uint32_t>(NULL);
+ // reservation_id INT UNSIGNED NOT NULL
+ // The host_id is auto_incremented by MySQL database,
+ // so we need to pass the NULL value
+ reservation_id_ = static_cast<uint32_t>(NULL);
bind_[0].buffer_type = MYSQL_TYPE_LONG;
- bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].buffer = reinterpret_cast<char*>(&reservation_id_);
bind_[0].is_unsigned = MLM_TRUE;
// address VARCHAR(39)
bind_[4].buffer = reinterpret_cast<char*>(&iaid_);
bind_[4].is_unsigned = MLM_TRUE;
+ // host_id INT UNSIGNED NOT NULL
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
+
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
- "Could not create bind array from Host: "
- << host_->getHostname() << ", reason: " << ex.what());
+ "Could not create bind array from IPv6 Reservation: "
+ << resv_.toText() << ", reason: " << ex.what());
}
// Add the data to the vector. Note the end element is one after the
// code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
- /// @todo: set bind_[X] fields here.
+ // reservation_id INT UNSIGNED NOT NULL
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&reservation_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // address VARCHAR(39)
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>
+ (const_cast<char*>(&address_[0]));
+ bind_[1].buffer_length = address_len_;
+ bind_[1].length = &address_len_;
+
+ // prefix_len tinyint
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&prefix_len_);
+ bind_[2].is_unsigned = MLM_TRUE;
+
+ // type tinyint
+ bind_[3].buffer_type = MYSQL_TYPE_TINY;
+ bind_[3].buffer = reinterpret_cast<char*>(&type_);
+ bind_[3].is_unsigned = MLM_TRUE;
+
+ // dhcp6_iaid INT UNSIGNED
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&iaid_);
+ bind_[4].is_unsigned = MLM_TRUE;
+
+ // host_id INT UNSIGNED NOT NULL
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
// Add the error flags
setErrorIndicators(bind_, error_, RESRV_COLUMNS);
// .. and check that we have the numbers correct at compile time.
- BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
+ BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
// Add the data to the vector. Note the end element is one after the
// end of the array.
///
/// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
IPv6Resrv getIPv6ReservData(){
- /// @todo: Implement actual extraction.
- IPv6Resrv r(IPv6Resrv::TYPE_NA, IOAddress("::"), 128);
+ // Set the IPv6 Reservation type (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
+ IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
+
+ switch (type_) {
+ case 0:
+ type = IPv6Resrv::TYPE_NA;
+ break;
+
+ case 1:
+ type = IPv6Resrv::TYPE_TA;
+ break;
+
+ case 2:
+ type = IPv6Resrv::TYPE_PD;
+ break;
+
+ default:
+ isc_throw(BadValue,
+ "invalid IPv6 reservation type returned: "
+ << static_cast<int>(type_)
+ << ". Only 0, 1 or 2 are allowed.");
+ }
+
+ IPv6Resrv r(type, IOAddress(address_), prefix_len_);
return (r);
}
private:
uint64_t host_id_; /// Host unique identifier
+ uint64_t reservation_id_; /// Host unique identifier
size_t host_id_length_; /// Length of the host unique ID
std::string address_; ///< Address (or prefix)
size_t address_len_; ///< Length of the textual address representation
IPv6Resrv resv_;
MYSQL_BIND bind_[RESRV_COLUMNS];
- std::string columns_[RESRV_COLUMNS]; /// Column names
+ std::string columns_[RESRV_COLUMNS]; /// Column names
my_bool error_[RESRV_COLUMNS]; /// Error array
- HostPtr host_; // Pointer to Host object
+ HostPtr host_; /// Pointer to Host object
};
// MySqlHostDataSource Constructor and Destructor
return;
}
- // Ok, there are v6 reservations. Let's insert them. But first, we need
- // to learn what's the host_id of the host we just added.
-
- /// @todo: See how get6() is done in hostMgr - calls with duid only first,
- /// and if can't find it, then calls with hwaddr only.
- ConstHostPtr from_db = get6(host->getIPv6SubnetID(), host->getDuid(),
- host->getHWAddress());
- if (!from_db) {
- // Oops, we have a problem. We can't find the host we just added.
- isc_throw(DbOperationError, "Unable to retrieve the host that just "
- "had been added");
- }
-
+ // Gets the last inserted hosts id
+ uint64_t host_id = mysql_insert_id(conn_.mysql_);
for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second; ++resv) {
- addResv(resv->second, from_db->getHostId());
+ addResv(resv->second, host_id);
}
}
}
}
}
+void
+MySqlHostDataSource::getIPv6ReservationCollection(StatementIndex stindex,
+ MYSQL_BIND* bind, boost::shared_ptr<MySqlIPv6ReservationExchange> exchange,
+ IPv6ResrvCollection& result) const {
+
+ // Bind the selection parameters to the statement
+ int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
+ checkError(status, stindex, "unable to bind WHERE clause parameter");
+
+ // Set up the MYSQL_BIND array for the data being returned and bind it to
+ // the statement.
+ std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+ status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
+ checkError(status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(conn_.statements_[stindex]);
+ checkError(status, stindex, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(conn_.statements_[stindex]);
+ checkError(status, stindex, "unable to set up for storing all results");
+
+ // Set up the fetch "release" object to release resources associated
+ // with the call to mysql_stmt_fetch when this method exits, then
+ // retrieve the data.
+ MySqlFreeResult fetch_release(conn_.statements_[stindex]);
+ while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) {
+ try {
+ result.insert(IPv6ResrvTuple(exchange->getIPv6ReservData().getType(),
+ exchange->getIPv6ReservData()));
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ conn_.text_statements_[stindex] << ">");
+ }
+ }
+
+ // How did the fetch end?
+ if (status == 1) {
+ // Error - unable to fetch results
+ checkError(status, stindex, "unable to fetch results");
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, conn_.text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+IPv6ResrvCollection
+MySqlHostDataSource::getAllReservations(HostID host_id) const{
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t id = static_cast<uint32_t>(host_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ IPv6ResrvCollection result;
+ getIPv6ReservationCollection(GET_V6_RESRV, inbind, resvExchange_, result);
+
+ return (result);
+}
+
+void
+MySqlHostDataSource::assignReservations(HostPtr& host) const {
+
+ IPv6ResrvCollection reservations;
+ reservations = getAllReservations(host->getHostId());
+
+ for (IPv6ResrvIterator resv = reservations.begin(); resv != reservations.end(); ++resv){
+ host->addReservation(resv->second);
+
+ }
+}
+
void
MySqlHostDataSource::getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
boost::shared_ptr<MySqlHostReservationExchange> exchange,
// retrieve the data.
MySqlFreeResult fetch_release(conn_.statements_[stindex]);
int count = 0;
+ HostPtr host;
while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) == 0) {
try {
- result.push_back(exchange->getHostData());
+ host = exchange->getHostData();
+ assignReservations(host);
+ result.push_back(host);
} catch (const isc::BadValue& ex) {
// Rethrow the exception with a bit more data.
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
- inbind[0].buffer_type = MYSQL_TYPE_LONG;
- inbind[0].buffer = reinterpret_cast<char*>(&prefix.toBytes()[0]);
- inbind[0].is_unsigned = MLM_TRUE;
+ std::string addr6 = prefix.toText();
+ unsigned long addr6_length = addr6.size();
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>
+ (const_cast<char*>(addr6.c_str()));
+ inbind[0].length = &addr6_length;
+ inbind[0].buffer_length = addr6_length;
+
uint8_t tmp = prefix_len;
- inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
inbind[1].buffer = reinterpret_cast<char*>(&tmp);
inbind[1].is_unsigned = MLM_TRUE;
+
ConstHostCollection collection;
getHostCollection(GET_HOST_PREFIX, inbind, hostExchange_,
collection, true);
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++, resrv2.first++){
+ EXPECT_EQ(resrv1.first->second.getType(), resrv2.first->second.getType());
+ EXPECT_EQ(resrv1.first->second.getPrefixLen(), resrv2.first->second.getPrefixLen());
+ EXPECT_EQ(resrv1.first->second.getPrefix(), resrv2.first->second.getPrefix());
+ }
}
}
}
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());
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());
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_DUID, true);
// Add this reservation once.
ASSERT_NO_THROW(hdsptr_->add(host));
}
+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(), true);
+}
+
+void GenericHostDataSourceTest::testMultipletReservations(){
+ // 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);
+
+ host->addReservation(resv1);
+ host->addReservation(resv2);
+ host->addReservation(resv3);
+
+ 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);
+
+}
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc