summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcin Siodelski <marcin@isc.org>2018-09-25 10:50:13 +0200
committerMarcin Siodelski <marcin@isc.org>2018-10-08 16:39:22 +0200
commit3ef5938626712225e3302b8e663211462953b7f1 (patch)
treee13ae39fe277838997dd9e54ae32a5190b7fd9c3
parent[#93,!35] Extended MySqlConnection with generic query functions. (diff)
downloadkea-3ef5938626712225e3302b8e663211462953b7f1.tar.xz
kea-3ef5938626712225e3302b8e663211462953b7f1.zip
[#93,!35] Implemented subnet fetching in MySQL Config Backend.
-rw-r--r--src/hooks/dhcp/mysql_cb/Makefile.am1
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc508
-rw-r--r--src/hooks/dhcp/mysql_cb/tests/Makefile.am1
-rw-r--r--src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc172
-rw-r--r--src/lib/cc/Makefile.am2
-rw-r--r--src/lib/cc/stamped_element.cc22
-rw-r--r--src/lib/cc/stamped_element.h57
-rw-r--r--src/lib/cc/tests/Makefile.am1
-rw-r--r--src/lib/cc/tests/stamped_element_unittest.cc60
-rw-r--r--src/lib/dhcpsrv/network.h5
-rw-r--r--src/lib/dhcpsrv/subnet.cc41
-rw-r--r--src/lib/dhcpsrv/subnet.h27
-rw-r--r--src/lib/dhcpsrv/tests/subnet_unittest.cc60
-rw-r--r--src/lib/mysql/mysql_binding.cc40
-rw-r--r--src/lib/mysql/mysql_binding.h91
-rw-r--r--src/lib/mysql/mysql_connection.h10
-rw-r--r--src/lib/mysql/tests/Makefile.am3
-rw-r--r--src/lib/mysql/tests/mysql_binding_unittest.cc97
-rw-r--r--src/lib/mysql/tests/mysql_connection_unittest.cc26
19 files changed, 1178 insertions, 46 deletions
diff --git a/src/hooks/dhcp/mysql_cb/Makefile.am b/src/hooks/dhcp/mysql_cb/Makefile.am
index ae7aef442f..28da17d4f1 100644
--- a/src/hooks/dhcp/mysql_cb/Makefile.am
+++ b/src/hooks/dhcp/mysql_cb/Makefile.am
@@ -43,6 +43,7 @@ libdhcp_mysql_cb_la_LDFLAGS = $(AM_LDFLAGS) $(MYSQL_LIBS)o
libdhcp_mysql_cb_la_LDFLAGS += -avoid-version -export-dynamic -module
libdhcp_mysql_cb_la_LIBADD = libmysqlcb.la
+libdhcp_mysql_cb_la_LIBADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
libdhcp_mysql_cb_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
libdhcp_mysql_cb_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
libdhcp_mysql_cb_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
index da0acce5cf..86a2053745 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
@@ -4,20 +4,24 @@
// 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 <cc/data.h>
#include <database/db_exceptions.h>
+#include <dhcp/classify.h>
+#include <dhcp/dhcp6.h>
#include <mysql_cb_dhcp4.h>
#include <mysql/mysql_connection.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
#include <mysql.h>
#include <mysqld_error.h>
#include <array>
+#include <sstream>
#include <utility>
#include <vector>
using namespace isc::db;
-
-namespace {
-
-}
+using namespace isc::data;
+using namespace isc::asiolink;
namespace isc {
namespace dhcp {
@@ -34,6 +38,11 @@ public:
/// database.
enum StatementIndex {
GET_SUBNET4_ID,
+ GET_SUBNET4_PREFIX,
+ GET_ALL_SUBNETS4,
+ GET_MODIFIED_SUBNETS4,
+ INSERT_SUBNET4,
+ UPDATE_SUBNET4,
NUM_STATEMENTS
};
@@ -46,6 +55,292 @@ public:
/// @brief Destructor.
~MySqlConfigBackendDHCPv4Impl();
+ /// @brief Sends query to the database to retrieve multiple subnets.
+ ///
+ /// Query should order subnets by subnet_id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param [out] subnets Reference to the container where fetched subnets
+ /// will be inserted.
+ void getSubnets4(const StatementIndex& index,
+ const MySqlBindingCollection& in_bindings,
+ Subnet4Collection& subnets) {
+ // Create output bindings. The order must match that in the prepared
+ // statement.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint32_t>(), // subnet_id
+ MySqlBinding::createString(32), // subnet_prefix
+ MySqlBinding::createString(128), // 4o6_interface
+ MySqlBinding::createString(128), // 4o6_interface_id
+ MySqlBinding::createString(64), // 4o6_subnet
+ MySqlBinding::createString(512), // boot_file_name
+ MySqlBinding::createString(128), // client_class
+ MySqlBinding::createString(128), // interface
+ MySqlBinding::createInteger<uint8_t>(), // match_client_id
+ MySqlBinding::createTimestamp(), // modification_ts
+ MySqlBinding::createInteger<uint32_t>(), // next_server
+ MySqlBinding::createInteger<uint32_t>(), // rebind_timer
+ MySqlBinding::createString(65536), // relay
+ MySqlBinding::createInteger<uint32_t>(), // renew_timer
+ MySqlBinding::createString(65536), // require_client_classes
+ MySqlBinding::createInteger<uint8_t>(), // reservation_mode
+ MySqlBinding::createString(512), // server_hostname
+ MySqlBinding::createString(128), // shared_network_name
+ MySqlBinding::createString(65536), // user_context
+ MySqlBinding::createInteger<uint32_t>() // valid_lifetime
+ };
+
+ // Execute actual query.
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&subnets](MySqlBindingCollection& out_bindings) {
+ // Get pointer to the last subnet in the collection.
+ Subnet4Ptr last_subnet;
+ if (!subnets.empty()) {
+ last_subnet = *subnets.rbegin();
+ }
+
+ // Subnet has been returned. Assuming that subnets are ordered by
+ // subnet identifier, if the subnet identifier of the current row
+ // is different than the subnet identifier of the previously returned
+ // row, it means that we have to construct new subnet object.
+ if (!last_subnet || (last_subnet->getID() != out_bindings[0]->getInteger<uint32_t>())) {
+ // subnet_id
+ SubnetID subnet_id(out_bindings[0]->getInteger<uint32_t>());
+ // subnet_prefix
+ std::string subnet_prefix = out_bindings[1]->getString();
+ auto prefix_pair = Subnet4::parsePrefix(subnet_prefix);
+ // renew_timer
+ uint32_t renew_timer = out_bindings[13]->getIntegerOrDefault<uint32_t>(0);
+ // rebind_timer
+ uint32_t rebind_timer = out_bindings[11]->getIntegerOrDefault<uint32_t>(0);
+ // valid_lifetime
+ uint32_t valid_lifetime = out_bindings[19]->getIntegerOrDefault<uint32_t>(0);
+
+ // Create subnet with basic settings.
+ last_subnet.reset(new Subnet4(prefix_pair.first, prefix_pair.second,
+ renew_timer, rebind_timer,
+ valid_lifetime, subnet_id));
+
+ // 4o6_interface
+ if (!out_bindings[2]->amNull()) {
+ last_subnet->get4o6().setIface4o6(out_bindings[2]->getString());
+ }
+ // 4o6_interface_id
+ if (!out_bindings[3]->amNull()) {
+ std::string dhcp4o6_interface_id = out_bindings[3]->getString();
+ OptionBuffer dhcp4o6_interface_id_buf(dhcp4o6_interface_id.begin(),
+ dhcp4o6_interface_id.end());
+ OptionPtr option_dhcp4o6_interface_id(new Option(Option::V6, D6O_INTERFACE_ID,
+ dhcp4o6_interface_id_buf));
+ last_subnet->get4o6().setInterfaceId(option_dhcp4o6_interface_id);
+ }
+ // 4o6_subnet
+ if (!out_bindings[4]->amNull()) {
+ std::pair<IOAddress, uint8_t> dhcp4o6_subnet_prefix_pair =
+ Subnet6::parsePrefix(out_bindings[4]->getString());
+ last_subnet->get4o6().setSubnet4o6(dhcp4o6_subnet_prefix_pair.first,
+ dhcp4o6_subnet_prefix_pair.second);
+ }
+ // boot_file_name
+ last_subnet->setFilename(out_bindings[5]->getStringOrDefault(""));
+ // client_class
+ if (!out_bindings[6]->amNull()) {
+ last_subnet->allowClientClass(out_bindings[6]->getString());
+ }
+ // interface
+ last_subnet->setIface(out_bindings[7]->getStringOrDefault(""));
+ // match_client_id
+ last_subnet->setMatchClientId(static_cast<bool>
+ (out_bindings[8]->getIntegerOrDefault<uint8_t>(1)));
+ // next_server
+ last_subnet->setSiaddr(IOAddress(out_bindings[10]->getIntegerOrDefault<uint32_t>(0)));
+ // relay
+ ElementPtr relay_element = out_bindings[12]->getJSON();
+ if (relay_element) {
+ if (relay_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid relay value "
+ << out_bindings[12]->getString());
+ }
+ for (auto i = 0; i < relay_element->size(); ++i) {
+ auto relay_address_element = relay_element->get(i);
+ if (relay_address_element->getType() != Element::string) {
+ isc_throw(BadValue, "relay address must be a string");
+ }
+ last_subnet->addRelayAddress(IOAddress(relay_element->get(i)->stringValue()));
+ }
+ }
+ // require_client_classes
+ ElementPtr require_element = out_bindings[14]->getJSON();
+ if (require_element) {
+ if (require_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid require_client_classes value "
+ << out_bindings[14]->getString());
+ }
+ for (auto i = 0; i < require_element->size(); ++i) {
+ auto require_item = require_element->get(i);
+ if (require_item->getType() != Element::string) {
+ isc_throw(BadValue, "elements of require_client_classes list must"
+ "be valid strings");
+ }
+ last_subnet->requireClientClass(require_item->stringValue());
+ }
+ }
+ // reservation_mode
+ last_subnet->setHostReservationMode(static_cast<Subnet4::HRMode>
+ (out_bindings[15]->getIntegerOrDefault<uint8_t>(Subnet4::HR_ALL)));
+ // server_hostname
+ last_subnet->setSname(out_bindings[16]->getStringOrDefault(""));
+ // user_context
+ ElementPtr user_context = out_bindings[18]->getJSON();
+ if (user_context) {
+ last_subnet->setContext(user_context);
+ }
+
+ // Subnet ready. Add it to the list.
+ subnets.push_back(last_subnet);
+ }
+ });
+ }
+
+ /// @brief Sends query to retrieve single subnet by id.
+ ///
+ /// @param selector Server selector.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the returned subnet or NULL if such subnet
+ /// doesn't exist.
+ Subnet4Ptr getSubnet4(const ServerSelector& selector,
+ const SubnetID& subnet_id) {
+ MySqlBindingCollection in_bindings;
+ in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(subnet_id));
+
+ Subnet4Collection subnets;
+ getSubnets4(GET_SUBNET4_ID, in_bindings, subnets);
+
+ return (subnets.empty() ? Subnet4Ptr() : *subnets.begin());
+ }
+
+ /// @brief Sends query to retrieve single subnet by prefix.
+ ///
+ /// The prefix should be in the following format: "192.0.2.0/24".
+ ///
+ /// @param selector Server selector.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the returned subnet or NULL if such subnet
+ /// doesn't exist.
+ Subnet4Ptr getSubnet4(const ServerSelector& selector,
+ const std::string& subnet_prefix) {
+ MySqlBindingCollection in_bindings;
+ in_bindings.push_back(MySqlBinding::createString(subnet_prefix));
+
+ Subnet4Collection subnets;
+ getSubnets4(GET_SUBNET4_PREFIX, in_bindings, subnets);
+
+ return (subnets.empty() ? Subnet4Ptr() : *subnets.begin());
+ }
+
+ /// @brief Sends query to insert or update subnet.
+ ///
+ /// @param selector Server selector.
+ /// @param subnet Pointer to the subnet to be inserted or updated.
+ void createUpdateSubnet4(const ServerSelector& selector,
+ const Subnet4Ptr& subnet) {
+ // Convert DHCPv4o6 interface id to text.
+ OptionPtr dhcp4o6_interface_id = subnet->get4o6().getInterfaceId();
+ std::string dhcp4o6_interface_id_text;
+ if (dhcp4o6_interface_id) {
+ dhcp4o6_interface_id_text.assign(dhcp4o6_interface_id->getData().begin(),
+ dhcp4o6_interface_id->getData().end());
+ }
+
+ // Convert DHCPv4o6 subnet to text.
+ std::string dhcp4o6_subnet;
+ if (!subnet->get4o6().getSubnet4o6().first.isV6Zero() ||
+ (subnet->get4o6().getSubnet4o6().second != 128u)) {
+ std::ostringstream s;
+ s << subnet->get4o6().getSubnet4o6().first << "/"
+ << static_cast<int>(subnet->get4o6().getSubnet4o6().second);
+ dhcp4o6_subnet = s.str();
+ }
+
+ // Create JSON list of relay addresses.
+ ElementPtr relay_element = Element::createList();
+ const auto& addresses = subnet->getRelayAddresses();
+ if (!addresses.empty()) {
+ for (const auto& address : addresses) {
+ relay_element->add(Element::create(address.toText()));
+ }
+ }
+
+ // Create JSON list of required classes.
+ ElementPtr required_classes_element = Element::createList();
+ const auto& required_classes = subnet->getRequiredClasses();
+ for (auto required_class = required_classes.cbegin();
+ required_class != required_classes.cend();
+ ++required_class) {
+ required_classes_element->add(Element::create(*required_class));
+ }
+
+ // Create binding with shared network name if the subnet belongs to a
+ // shared network.
+ SharedNetwork4Ptr shared_network;
+ subnet->getSharedNetwork(shared_network);
+ MySqlBindingPtr shared_network_binding =
+ (shared_network ? MySqlBinding::createString(shared_network->getName()) :
+ MySqlBinding::createNull());
+
+ // Create user context binding if user context exists.
+ auto context_element = subnet->getContext();
+ MySqlBindingPtr context_binding =
+ (context_element ? MySqlBinding::createString(context_element->str()) :
+ MySqlBinding::createNull());
+
+ // Create input bindings.
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint32_t>(subnet->getID()),
+ MySqlBinding::createString(subnet->toText()),
+ MySqlBinding::condCreateString(subnet->get4o6().getIface4o6()),
+ MySqlBinding::condCreateString(dhcp4o6_interface_id_text),
+ MySqlBinding::condCreateString(dhcp4o6_subnet),
+ MySqlBinding::condCreateString(subnet->getFilename()),
+ MySqlBinding::condCreateString(subnet->getClientClass()),
+ MySqlBinding::condCreateString(subnet->getIface()),
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(subnet->getMatchClientId())),
+ MySqlBinding::createTimestamp(subnet->getModificationTime()),
+ MySqlBinding::condCreateInteger<uint32_t>(subnet->getSiaddr().toUint32()),
+ MySqlBinding::createInteger<uint32_t>(subnet->getT2()),
+ MySqlBinding::condCreateString(relay_element->str()),
+ MySqlBinding::createInteger<uint32_t>(subnet->getT1()),
+ MySqlBinding::condCreateString(required_classes_element->str()),
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(subnet->getHostReservationMode())),
+ MySqlBinding::condCreateString(subnet->getSname()),
+ shared_network_binding,
+ context_binding,
+ MySqlBinding::createInteger<uint32_t>(subnet->getValid())
+ };
+
+ // Check if the subnet already exists.
+ Subnet4Ptr existing_subnet = getSubnet4(selector, subnet->getID());
+
+ // If the subnet exists we are going to update this subnet.
+ if (existing_subnet) {
+ // Need to add one more binding for WHERE clause.
+ in_bindings.push_back(MySqlBinding::createInteger<uint32_t>(existing_subnet->getID()));
+ conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_SUBNET4,
+ in_bindings);
+
+ } else {
+ // If the subnet doesn't exist, let's insert it.
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_SUBNET4,
+ in_bindings);
+ }
+ }
+
/// @brief Represents connection to the MySQL database.
MySqlConnection conn_;
};
@@ -57,8 +352,161 @@ TaggedStatementArray;
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve data from the database.
TaggedStatementArray tagged_statements = { {
+ // Select subnet by id.
{ MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID,
- "SELECT hostname FROM hosts WHERE dhcp4_subnet_id = ?" }
+ "SELECT "
+ " subnet_id,"
+ " subnet_prefix,"
+ " 4o6_interface,"
+ " 4o6_interface_id,"
+ " 4o6_subnet,"
+ " boot_file_name,"
+ " client_class,"
+ " interface,"
+ " match_client_id,"
+ " modification_ts,"
+ " next_server,"
+ " rebind_timer,"
+ " relay,"
+ " renew_timer,"
+ " require_client_classes,"
+ " reservation_mode,"
+ " server_hostname,"
+ " shared_network_name,"
+ " user_context,"
+ " valid_lifetime "
+ "FROM dhcp4_subnet WHERE subnet_id = ? "
+ "ORDER BY subnet_id" },
+
+ // Select subnet by prefix.
+ { MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_PREFIX,
+ "SELECT "
+ " subnet_id,"
+ " subnet_prefix,"
+ " 4o6_interface,"
+ " 4o6_interface_id,"
+ " 4o6_subnet,"
+ " boot_file_name,"
+ " client_class,"
+ " interface,"
+ " match_client_id,"
+ " modification_ts,"
+ " next_server,"
+ " rebind_timer,"
+ " relay,"
+ " renew_timer,"
+ " require_client_classes,"
+ " reservation_mode,"
+ " server_hostname,"
+ " shared_network_name,"
+ " user_context,"
+ " valid_lifetime "
+ "FROM dhcp4_subnet WHERE subnet_prefix = ? "
+ "ORDER BY subnet_id" },
+
+ // Select all subnets.
+ { MySqlConfigBackendDHCPv4Impl::GET_ALL_SUBNETS4,
+ "SELECT "
+ " subnet_id,"
+ " subnet_prefix,"
+ " 4o6_interface,"
+ " 4o6_interface_id,"
+ " 4o6_subnet,"
+ " boot_file_name,"
+ " client_class,"
+ " interface,"
+ " match_client_id,"
+ " modification_ts,"
+ " next_server,"
+ " rebind_timer,"
+ " relay,"
+ " renew_timer,"
+ " require_client_classes,"
+ " reservation_mode,"
+ " server_hostname,"
+ " shared_network_name,"
+ " user_context,"
+ " valid_lifetime "
+ "FROM dhcp4_subnet "
+ "ORDER BY subnet_id" },
+
+ // Select subnets having modification time later than X.
+ { MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_SUBNETS4,
+ "SELECT "
+ " subnet_id,"
+ " subnet_prefix,"
+ " 4o6_interface,"
+ " 4o6_interface_id,"
+ " 4o6_subnet,"
+ " boot_file_name,"
+ " client_class,"
+ " interface,"
+ " match_client_id,"
+ " modification_ts,"
+ " next_server,"
+ " rebind_timer,"
+ " relay,"
+ " renew_timer,"
+ " require_client_classes,"
+ " reservation_mode,"
+ " server_hostname,"
+ " shared_network_name,"
+ " user_context,"
+ " valid_lifetime "
+ "FROM dhcp4_subnet "
+ "WHERE modification_ts > ? "
+ "ORDER BY subnet_id" },
+
+ // Insert a subnet.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_SUBNET4,
+ "INSERT INTO dhcp4_subnet("
+ " subnet_id,"
+ " subnet_prefix,"
+ " 4o6_interface,"
+ " 4o6_interface_id,"
+ " 4o6_subnet,"
+ " boot_file_name,"
+ " client_class,"
+ " interface,"
+ " match_client_id,"
+ " modification_ts,"
+ " next_server,"
+ " rebind_timer,"
+ " relay,"
+ " renew_timer,"
+ " require_client_classes,"
+ " reservation_mode,"
+ " server_hostname,"
+ " shared_network_name,"
+ " user_context,"
+ " valid_lifetime"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,"
+ "?, ?, ?, ?, ?, ?, ?, ?)" },
+
+ // Update existing subnet.
+ { MySqlConfigBackendDHCPv4Impl::UPDATE_SUBNET4,
+ "UPDATE dhcp4_subnet SET "
+ " subnet_id = ?,"
+ " subnet_prefix = ?,"
+ " 4o6_interface = ?,"
+ " 4o6_interface_id = ?,"
+ " 4o6_subnet = ?,"
+ " boot_file_name = ?,"
+ " client_class = ?,"
+ " interface = ?,"
+ " match_client_id = ?,"
+ " modification_ts = ?,"
+ " next_server = ?,"
+ " rebind_timer = ?,"
+ " relay = ?,"
+ " renew_timer = ?,"
+ " require_client_classes = ?,"
+ " reservation_mode = ?,"
+ " server_hostname = ?,"
+ " shared_network_name = ?,"
+ " user_context = ?,"
+ " valid_lifetime = ? "
+ "WHERE subnet_id = ?" }
}
};
@@ -119,80 +567,94 @@ MySqlConfigBackendDHCPv4(const DatabaseConnection::ParameterMap& parameters)
Subnet4Ptr
MySqlConfigBackendDHCPv4::getSubnet4(const ServerSelector& selector,
const std::string& subnet_prefix) const {
+ return (impl_->getSubnet4(selector, subnet_prefix));
}
Subnet4Ptr
MySqlConfigBackendDHCPv4::getSubnet4(const ServerSelector& selector,
const SubnetID& subnet_id) const {
- BindingCollection in_bindings;
- in_bindings.push_back(Binding::createString("1024"));
-
- BindingCollection out_bindings;
- out_bindings.push_back(Binding::createString());
-
- impl_->conn_.selectQuery(MySqlConfigBackendDHCPv4Impl::GET_SUBNET4_ID,
- in_bindings, out_bindings,
- [&out_bindings]() {
- uint32_t hostname = out_bindings[0]->getValue<uint32_t>();
- });
-
- return (Subnet4Ptr());
+ return (impl_->getSubnet4(selector, subnet_id));
}
Subnet4Collection
MySqlConfigBackendDHCPv4::getAllSubnets4(const ServerSelector& selector) const {
+ Subnet4Collection subnets;
+ MySqlBindingCollection in_bindings;
+ impl_->getSubnets4(MySqlConfigBackendDHCPv4Impl::GET_ALL_SUBNETS4,
+ in_bindings, subnets);
+ return (subnets);
}
Subnet4Collection
MySqlConfigBackendDHCPv4::getModifiedSubnets4(const ServerSelector& selector,
const boost::posix_time::ptime& modification_time) const {
+ Subnet4Collection subnets;
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createTimestamp(modification_time)
+ };
+ impl_->getSubnets4(MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_SUBNETS4,
+ in_bindings, subnets);
+ return (subnets);
}
SharedNetwork4Ptr
MySqlConfigBackendDHCPv4::getSharedNetwork4(const ServerSelector& selector,
const std::string& name) const {
+ isc_throw(NotImplemented, "not implemented");
}
SharedNetwork4Collection
MySqlConfigBackendDHCPv4::getAllSharedNetworks4(const ServerSelector& selector) const {
+ isc_throw(NotImplemented, "not implemented");
}
SharedNetwork4Collection
-MySqlConfigBackendDHCPv4::getModifiedSharedNetworks4(const ServerSelector& selector,
- const boost::posix_time::ptime& modification_time) const {
+MySqlConfigBackendDHCPv4::
+getModifiedSharedNetworks4(const ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const {
+ isc_throw(NotImplemented, "not implemented");
}
OptionDefinitionPtr
MySqlConfigBackendDHCPv4::getOptionDef4(const ServerSelector& selector,
const uint16_t code,
const std::string& space) const {
+ isc_throw(NotImplemented, "not implemented");
}
OptionDefContainer
MySqlConfigBackendDHCPv4::getAllOptionDefs4(const ServerSelector& selector) const {
+ isc_throw(NotImplemented, "not implemented");
}
OptionDefContainer
-MySqlConfigBackendDHCPv4::getModifiedOptionDefs4(const ServerSelector& selector,
- const boost::posix_time::ptime& modification_time) const {
+MySqlConfigBackendDHCPv4::
+getModifiedOptionDefs4(const ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const {
+ isc_throw(NotImplemented, "not implemented");
}
util::OptionalValue<std::string>
MySqlConfigBackendDHCPv4::getGlobalStringParameter4(const ServerSelector& selector,
const std::string& name) const {
+ isc_throw(NotImplemented, "not implemented");
}
+
util::OptionalValue<int64_t>
MySqlConfigBackendDHCPv4::getGlobalNumberParameter4(const ServerSelector& selector,
const std::string& name) const {
+ isc_throw(NotImplemented, "not implemented");
}
std::map<std::string, std::string>
MySqlConfigBackendDHCPv4::getAllGlobalParameters4(const ServerSelector& selector) const {
+ isc_throw(NotImplemented, "not implemented");
}
void
MySqlConfigBackendDHCPv4::createUpdateSubnet4(const ServerSelector& selector,
const Subnet4Ptr& subnet) {
+ impl_->createUpdateSubnet4(selector, subnet);
}
void
@@ -305,10 +767,12 @@ MySqlConfigBackendDHCPv4::getType() const {
std::string
MySqlConfigBackendDHCPv4::getHost() const {
+ return ("");
}
uint16_t
MySqlConfigBackendDHCPv4::getPort() const {
+ return (0);
}
} // end of namespace isc::dhcp
diff --git a/src/hooks/dhcp/mysql_cb/tests/Makefile.am b/src/hooks/dhcp/mysql_cb/tests/Makefile.am
index 5546b734dd..c8663e2d00 100644
--- a/src/hooks/dhcp/mysql_cb/tests/Makefile.am
+++ b/src/hooks/dhcp/mysql_cb/tests/Makefile.am
@@ -33,6 +33,7 @@ mysql_cb_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
mysql_cb_unittests_CXXFLAGS = $(AM_CXXFLAGS)
mysql_cb_unittests_LDADD = $(top_builddir)/src/hooks/dhcp/mysql_cb/libmysqlcb.la
+mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
mysql_cb_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
index feb191bfab..4d7a532e92 100644
--- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
+++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
@@ -5,24 +5,34 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
+#include <dhcp/dhcp6.h>
#include <mysql_cb_dhcp4.h>
#include <mysql/testutils/mysql_schema.h>
+#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
+#include <map>
+using namespace isc::asiolink;
using namespace isc::db;
using namespace isc::db::test;
+using namespace isc::data;
using namespace isc::dhcp;
namespace {
+/// @brief Test fixture class for @c MySqlConfigBackendDHCPv4.
class MySqlConfigBackendDHCPv4Test : public ::testing::Test {
public:
- MySqlConfigBackendDHCPv4Test() {
+ /// @brief Constructor.
+ MySqlConfigBackendDHCPv4Test()
+ : test_subnets_(), timestamps_() {
+ // Recreate database schema.
destroyMySQLSchema();
createMySQLSchema();
try {
+ // Create MySQL connection and use it to start the backend.
DatabaseConnection::ParameterMap params =
DatabaseConnection::parse(validMySQLConnectionString());
cbptr_.reset(new MySqlConfigBackendDHCPv4(params));
@@ -35,19 +45,175 @@ public:
"*** accompanying exception output.\n";
throw;
}
+
+ // Create test data.
+ initTestSubnets();
+ initTimestamps();
}
+ /// @brief Destructor.
virtual ~MySqlConfigBackendDHCPv4Test() {
cbptr_.reset();
destroyMySQLSchema();
}
- MySqlConfigBackendDHCPv4Ptr cbptr_;
+ /// @brief Creates several subnets used in tests.
+ void initTestSubnets() {
+ // First subnet includes all parameters.
+ std::string interface_id_text = "whale";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40, 60, 1024));
+ subnet->get4o6().setIface4o6("eth0");
+ subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID,
+ interface_id)));
+ subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64);
+ subnet->setFilename("/tmp/filename");
+ subnet->allowClientClass("home");
+ subnet->setIface("eth1");
+ subnet->setMatchClientId(false);
+ subnet->setSiaddr(IOAddress("10.1.2.3"));
+ subnet->setT2(323212);
+ subnet->addRelayAddress(IOAddress("10.2.3.4"));
+ subnet->addRelayAddress(IOAddress("10.5.6.7"));
+ subnet->setT1(1234);
+ subnet->requireClientClass("required-class1");
+ subnet->requireClientClass("required-class2");
+ subnet->setHostReservationMode(Subnet4::HR_DISABLED);
+ subnet->setSname("server-hostname");
+ subnet->setContext(user_context);
+ // shared-network?
+ subnet->setValid(555555);
+
+ test_subnets_.push_back(subnet);
+
+ // Other subnets include mostly null values except for mandatory parameters.
+ subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 20, 30, 40, 1024));
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048));
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24, 30, 40, 60, 4096));
+ test_subnets_.push_back(subnet);
+ }
+
+ /// @brief Initialize posix time values used in tests.
+ void initTimestamps() {
+ // Current time minus 1 hour to make sure it is in the past.
+ timestamps_["today"] = boost::posix_time::second_clock::universal_time()
+ - boost::posix_time::hours(1);
+ // Yesterday.
+ timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+ // Tomorrow.
+ timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+ }
+
+ /// @brief Holds pointers to subnets used in tests.
+ std::vector<Subnet4Ptr> test_subnets_;
+
+ /// @brief Holds timestamp values used in tests.
+ std::map<std::string, boost::posix_time::ptime> timestamps_;
+
+ /// @brief Holds pointer to the backend.
+ boost::shared_ptr<ConfigBackendDHCPv4> cbptr_;
};
+// Test that subnet can be inserted, fetched, updated and then fetched again.
TEST_F(MySqlConfigBackendDHCPv4Test, getSubnet4) {
- cbptr_->getSubnet4(ServerSelector::UNASSIGNED(), SubnetID(1));
+ // Insert new subnet.
+ Subnet4Ptr subnet = test_subnets_[0];
+ cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet);
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the subnet in the database (both use the same ID).
+ Subnet4Ptr subnet2 = test_subnets_[1];
+ cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet2);
+
+ // Fetch updated subnet and see if it matches.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ SubnetID(1024));
+ EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str());
}
+// Test that subnet can be fetched by prefix.
+TEST_F(MySqlConfigBackendDHCPv4Test, getSubnet4ByPrefix) {
+ // Insert subnet to the database.
+ Subnet4Ptr subnet = test_subnets_[0];
+ cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet);
+
+ // Fetch the subnet by prefix.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ "192.0.2.0/24");
+ ASSERT_TRUE(returned_subnet);
+
+ // Verify subnet contents.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+// Test that all subnets can be fetched.
+TEST_F(MySqlConfigBackendDHCPv4Test, getAllSubnets4) {
+ // Insert test subnets into the database. Note that the second subnet will
+ // overwrite the first subnet as they use the same ID.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet);
+ }
+
+ // Fetch all subnets.
+ Subnet4Collection subnets = cbptr_->getAllSubnets4(ServerSelector::UNASSIGNED());
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // See if the subnets are returned ok.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(),
+ subnets[i]->toElement()->str());
+ }
+}
+
+// Test that subnets modified after given time can be fetched.
+TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedSubnets4) {
+ // Explicitly set timestamps of subnets. First subnet has a timestamp
+ // pointing to the future. Second subnet has timestamp pointing to the
+ // past (yesterday). Third subnet has a timestamp pointing to the
+ // past (an hour ago).
+ test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_subnets_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_subnets_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert subnets into the database.
+ for (int i = 1; i < test_subnets_.size(); ++i) {
+ cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(),
+ test_subnets_[i]);
+ }
+
+ // Fetch subnets with timestamp later than today. Only one subnet
+ // should be returned.
+ Subnet4Collection
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // Fetch subnets with timestamp later than yesterday. We should get
+ // two subnets.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]);
+ ASSERT_EQ(2, subnets.size());
+
+ // Fetch subnets with timestamp later than tomorrow. Nothing should
+ // be returned.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["tomorrow"]);
+ ASSERT_TRUE(subnets.empty());
+}
}
diff --git a/src/lib/cc/Makefile.am b/src/lib/cc/Makefile.am
index 18362f650b..f191e6c819 100644
--- a/src/lib/cc/Makefile.am
+++ b/src/lib/cc/Makefile.am
@@ -10,6 +10,7 @@ libkea_cc_la_SOURCES += cfg_to_element.h dhcp_config_error.h
libkea_cc_la_SOURCES += command_interpreter.cc command_interpreter.h
libkea_cc_la_SOURCES += json_feed.cc json_feed.h
libkea_cc_la_SOURCES += simple_parser.cc simple_parser.h
+libkea_cc_la_SOURCES += stamped_element.cc stamped_element.h
libkea_cc_la_SOURCES += user_context.cc user_context.h
libkea_cc_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
@@ -28,6 +29,7 @@ libkea_cc_include_HEADERS = \
dhcp_config_error.h \
json_feed.h \
simple_parser.h \
+ stamped_element.h \
user_context.h
EXTRA_DIST = cc.dox
diff --git a/src/lib/cc/stamped_element.cc b/src/lib/cc/stamped_element.cc
new file mode 100644
index 0000000000..0761978844
--- /dev/null
+++ b/src/lib/cc/stamped_element.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2018 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 <cc/stamped_element.h>
+
+namespace isc {
+namespace data {
+
+StampedElement::StampedElement()
+ : timestamp_(boost::posix_time::second_clock::universal_time()) {
+}
+
+void
+StampedElement::updateModificationTime() {
+ setModificationTime(boost::posix_time::second_clock::universal_time());
+}
+
+} // end of namespace isc::data
+} // end of namespace isc
diff --git a/src/lib/cc/stamped_element.h b/src/lib/cc/stamped_element.h
new file mode 100644
index 0000000000..9d15bb952c
--- /dev/null
+++ b/src/lib/cc/stamped_element.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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/.
+
+#ifndef STAMPED_ELEMENT_H
+#define STAMPED_ELEMENT_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace data {
+
+/// @brief This class represents configuration element which is
+/// associated with the modification timestamp.
+///
+/// Classes storing Kea configuration should derive from this object
+/// to track modification times of the configuration objects. This
+/// is specifically required by the Kea Configuration Backend feature
+/// which stores configuration in the database and must be able
+/// to recognize recently modified objects to fetch incremental
+/// changes.
+class StampedElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets timestamp to the current time.
+ StampedElement();
+
+ /// @brief Sets timestamp to the explicitly provided value.
+ ///
+ /// @param timestamp New timestamp value.
+ void setModificationTime(const boost::posix_time::ptime& timestamp) {
+ timestamp_ = timestamp;
+ }
+
+ /// @brief Sets timestmp to the current time.
+ void updateModificationTime();
+
+ /// @brief Returns timestamp.
+ boost::posix_time::ptime getModificationTime() const {
+ return (timestamp_);
+ }
+
+private:
+
+ /// @brief Holds timestamp value.
+ boost::posix_time::ptime timestamp_;
+
+};
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 452926e36c..e33c049b74 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -19,6 +19,7 @@ run_unittests_SOURCES += data_unittests.cc
run_unittests_SOURCES += data_file_unittests.cc
run_unittests_SOURCES += json_feed_unittests.cc
run_unittests_SOURCES += simple_parser_unittest.cc
+run_unittests_SOURCES += stamped_element_unittest.cc
run_unittests_SOURCES += user_context_unittests.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/cc/tests/stamped_element_unittest.cc b/src/lib/cc/tests/stamped_element_unittest.cc
new file mode 100644
index 0000000000..6b832ebd31
--- /dev/null
+++ b/src/lib/cc/tests/stamped_element_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2018 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 <cc/stamped_element.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+
+namespace {
+
+// Tests that the modification timestamp is by default set to current
+// time.
+TEST(StampedElementTest, create) {
+ StampedElement element;
+
+ // Checking that the delta between now and the timestamp is within
+ // 5s range should be sufficient.
+ boost::posix_time::time_duration delta =
+ boost::posix_time::second_clock::universal_time() -
+ element.getModificationTime();
+ EXPECT_LT(delta.seconds(), 5);
+}
+
+// Tests that the modification timestamp can be set to an arbitrary
+// value.
+TEST(StampedElementTest, setModificationTime) {
+ boost::posix_time::ptime
+ modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10),
+ boost::posix_time::time_duration(1,2,3));
+ StampedElement element;
+ element.setModificationTime(modification_time);
+ EXPECT_TRUE(element.getModificationTime() == modification_time);
+}
+
+// Tests that updating modification timestamp sets it to the current
+// time.
+TEST(StampedElementTest, update) {
+ boost::posix_time::ptime
+ modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10),
+ boost::posix_time::time_duration(1,2,3));
+ StampedElement element;
+ element.setModificationTime(modification_time);
+ element.updateModificationTime();
+
+ // Checking that the delta between now and the timestamp is within
+ // 5s range should be sufficient.
+ boost::posix_time::time_duration delta =
+ boost::posix_time::second_clock::universal_time() -
+ element.getModificationTime();
+ EXPECT_LT(delta.seconds(), 5);
+}
+
+}
diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h
index 433e80102c..4f1d0fa736 100644
--- a/src/lib/dhcpsrv/network.h
+++ b/src/lib/dhcpsrv/network.h
@@ -10,6 +10,7 @@
#include <asiolink/io_address.h>
#include <cc/cfg_to_element.h>
#include <cc/data.h>
+#include <cc/stamped_element.h>
#include <cc/user_context.h>
#include <dhcp/classify.h>
#include <dhcp/option.h>
@@ -45,7 +46,9 @@ typedef std::vector<isc::asiolink::IOAddress> IOAddressList;
/// class provides an abstract interface that must be implemented by derived
/// classes and, where appropriate, implements common methods used by the
/// derived classes.
-class Network : public virtual isc::data::UserContext, public isc::data::CfgToElement {
+class Network : public virtual isc::data::StampedElement,
+ public virtual isc::data::UserContext,
+ public isc::data::CfgToElement {
public:
/// @brief Holds optional information about relay.
///
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 993d5495c8..eb41681dae 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -11,6 +11,7 @@
#include <dhcp/option_space.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
+#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <sstream>
@@ -214,6 +215,26 @@ Subnet::sumPoolCapacity(const PoolCollection& pools,
return (sum);
}
+std::pair<IOAddress, uint8_t>
+Subnet::parsePrefixCommon(const std::string& prefix) {
+ auto pos = prefix.find('/');
+ if ((pos == std::string::npos) ||
+ (pos == prefix.size() - 1) ||
+ (pos == 0)) {
+ isc_throw(BadValue, "unable to parse invalid prefix " << prefix);
+ }
+
+ try {
+ IOAddress address(prefix.substr(0, pos));
+ int length = boost::lexical_cast<int>(prefix.substr(pos + 1));
+ return (std::make_pair(address, static_cast<int>(length)));
+
+ } catch (...) {
+ isc_throw(BadValue, "unable to parse invalid prefix " << prefix);
+ }
+}
+
+
void Subnet4::checkType(Lease::Type type) const {
if (type != Lease::TYPE_V4) {
isc_throw(BadValue, "Only TYPE_V4 is allowed for Subnet4");
@@ -717,6 +738,16 @@ Subnet4::toElement() const {
return (map);
}
+std::pair<IOAddress, uint8_t>
+Subnet4::parsePrefix(const std::string& prefix) {
+ std::pair<IOAddress, uint8_t> parsed = Subnet::parsePrefixCommon(prefix);
+ if (!parsed.first.isV4() || parsed.first.isV4Zero() ||
+ (parsed.second > 32) || (parsed.second == 0)) {
+ isc_throw(BadValue, "unable to parse invalid IPv4 prefix " << prefix);
+ }
+ return (parsed);
+}
+
data::ElementPtr
Subnet6::toElement() const {
// Prepare the map
@@ -748,6 +779,16 @@ Subnet6::toElement() const {
return (map);
}
+std::pair<IOAddress, uint8_t>
+Subnet6::parsePrefix(const std::string& prefix) {
+ std::pair<IOAddress, uint8_t> parsed = Subnet::parsePrefixCommon(prefix);
+ if (!parsed.first.isV6() || parsed.first.isV6Zero() ||
+ (parsed.second > 128) || (parsed.second == 0)) {
+ isc_throw(BadValue, "unable to parse invalid IPv6 prefix " << prefix);
+ }
+ return (parsed);
+}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index e995283e35..25af5ddeed 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -25,7 +25,9 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/pointer_cast.hpp>
#include <boost/shared_ptr.hpp>
+#include <cstdint>
#include <map>
+#include <utility>
namespace isc {
namespace dhcp {
@@ -359,6 +361,17 @@ protected:
/// @return A pointer to unparsed subnet configuration.
virtual data::ElementPtr toElement() const;
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// IPv4 and IPv6 specific conversion functions should apply extra checks
+ /// on the returned values, i.e. whether length is in range and the IP
+ /// address has a valid type.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided prefix is not valid.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefixCommon(const std::string& prefix);
+
/// @brief subnet-id
///
/// Subnet-id is a unique value that can be used to find or identify
@@ -545,6 +558,13 @@ public:
/// @return A pointer to unparsed subnet configuration.
virtual data::ElementPtr toElement() const;
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided invalid IPv4 prefix.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefix(const std::string& prefix);
+
private:
/// @brief Returns default address for pool selection
@@ -657,6 +677,13 @@ public:
/// @return A pointer to unparsed subnet configuration.
virtual data::ElementPtr toElement() const;
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided invalid IPv4 prefix.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefix(const std::string& prefix);
+
private:
/// @brief Returns default address for pool selection
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9d444be237..e69c6fc961 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -548,6 +548,36 @@ TEST(Subnet4Test, toText) {
EXPECT_EQ("192.0.2.0/24", subnet->toText());
}
+// This test verifies that the IPv4 prefix can be parsed into prefix/length pair.
+TEST(Subnet4Test, parsePrefix) {
+ std::pair<IOAddress, uint8_t> parsed =
+ std::make_pair(IOAddress::IPV4_ZERO_ADDRESS(), 0);
+
+ // Valid prefix.
+ EXPECT_NO_THROW(parsed = Subnet4::parsePrefix("192.0.5.0/24"));
+ EXPECT_EQ("192.0.5.0", parsed.first.toText());
+ EXPECT_EQ(24, static_cast<int>(parsed.second));
+
+ // Invalid IPv4 address.
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.322/24"), BadValue);
+
+ // Invalid prefix length.
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.0/64"), BadValue);
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.0/0"), BadValue);
+
+ // No IP address.
+ EXPECT_THROW(Subnet4::parsePrefix(" /24"), BadValue);
+
+ // No prefix length but slash present.
+ EXPECT_THROW(Subnet4::parsePrefix("10.0.0.0/ "), BadValue);
+
+ // No slash sign.
+ EXPECT_THROW(Subnet4::parsePrefix("10.0.0.1"), BadValue);
+
+ // IPv6 is not allowed here.
+ EXPECT_THROW(Subnet4::parsePrefix("3000::/24"), BadValue);
+}
+
// This test checks if the get() method returns proper parameters
TEST(Subnet4Test, get) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 28, 1, 2, 3));
@@ -1405,6 +1435,36 @@ TEST(Subnet6Test, toText) {
EXPECT_EQ("2001:db8::/32", subnet.toText());
}
+// This test verifies that the IPv6 prefix can be parsed into prefix/length pair.
+TEST(Subnet6Test, parsePrefix) {
+ std::pair<IOAddress, uint8_t> parsed =
+ std::make_pair(IOAddress::IPV6_ZERO_ADDRESS(), 0);
+
+ // Valid prefix.
+ EXPECT_NO_THROW(parsed = Subnet6::parsePrefix("2001:db8:1::/64"));
+ EXPECT_EQ("2001:db8:1::", parsed.first.toText());
+ EXPECT_EQ(64, static_cast<int>(parsed.second));
+
+ // Invalid IPv6 address.
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8::1::/64"), BadValue);
+
+ // Invalid prefix length.
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8:1::/164"), BadValue);
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8:1::/0"), BadValue);
+
+ // No IP address.
+ EXPECT_THROW(Subnet6::parsePrefix(" /64"), BadValue);
+
+ // No prefix length but slash present.
+ EXPECT_THROW(Subnet6::parsePrefix("3000::/ "), BadValue);
+
+ // No slash sign.
+ EXPECT_THROW(Subnet6::parsePrefix("3000::"), BadValue);
+
+ // IPv4 is not allowed here.
+ EXPECT_THROW(Subnet6::parsePrefix("192.0.2.0/24"), BadValue);
+}
+
// This test checks if the get() method returns proper parameters
TEST(Subnet6Test, get) {
Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
diff --git a/src/lib/mysql/mysql_binding.cc b/src/lib/mysql/mysql_binding.cc
index ad07c4a97a..974bb3fa82 100644
--- a/src/lib/mysql/mysql_binding.cc
+++ b/src/lib/mysql/mysql_binding.cc
@@ -8,6 +8,8 @@
#include <mysql/mysql_binding.h>
+using namespace isc::data;
+
namespace isc {
namespace db {
@@ -21,6 +23,23 @@ MySqlBinding::getString() const {
return (std::string(buffer_.begin(), buffer_.begin() + length_));
}
+std::string
+MySqlBinding::getStringOrDefault(const std::string& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getString());
+}
+
+ElementPtr
+MySqlBinding::getJSON() const {
+ if (amNull()) {
+ return (ElementPtr());
+ }
+ std::string s = getString();
+ return (Element::fromJSON(s));
+}
+
std::vector<uint8_t>
MySqlBinding::getBlob() const {
// Make sure the binding type is blob.
@@ -31,6 +50,14 @@ MySqlBinding::getBlob() const {
return (std::vector<uint8_t>(buffer_.begin(), buffer_.begin() + length_));
}
+std::vector<uint8_t>
+MySqlBinding::getBlobOrDefault(const std::vector<uint8_t>& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getBlob());
+}
+
boost::posix_time::ptime
MySqlBinding::getTimestamp() const {
// Make sure the binding type is timestamp.
@@ -41,6 +68,14 @@ MySqlBinding::getTimestamp() const {
return (convertFromDatabaseTime(*database_time));
}
+boost::posix_time::ptime
+MySqlBinding::getTimestampOrDefault(const boost::posix_time::ptime& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getTimestamp());
+}
+
MySqlBindingPtr
MySqlBinding::createString(const unsigned long length) {
MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
@@ -57,6 +92,11 @@ MySqlBinding::createString(const std::string& value) {
}
MySqlBindingPtr
+MySqlBinding::condCreateString(const std::string& value) {
+ return (value.empty() ? MySqlBinding::createNull() : createString(value));
+}
+
+MySqlBindingPtr
MySqlBinding::createBlob(const unsigned long length) {
MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::vector<uint8_t> >::column_type,
length));
diff --git a/src/lib/mysql/mysql_binding.h b/src/lib/mysql/mysql_binding.h
index 450d63ddd3..05cb8bd035 100644
--- a/src/lib/mysql/mysql_binding.h
+++ b/src/lib/mysql/mysql_binding.h
@@ -7,6 +7,7 @@
#ifndef MYSQL_BINDING_H
#define MYSQL_BINDING_H
+#include <cc/data.h>
#include <database/database_connection.h>
#include <exceptions/exceptions.h>
#include <boost/date_time/posix_time/conversion.hpp>
@@ -182,6 +183,28 @@ public:
/// @return String value.
std::string getString() const;
+ /// @brief Returns value held in the binding as string.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_STRING.
+ ///
+ /// @return String value.
+ std::string getStringOrDefault(const std::string& default_value) const;
+
+ /// @brief Returns value held in the binding as JSON.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the binding is not @c MYSQL_TYPE_STRING.
+ /// @throw data::JSONError if the string value is not a valid JSON.
+ ///
+ /// @return JSON structure or NULL if the string is null.
+ data::ElementPtr getJSON() const;
+
/// @brief Returns value held in the binding as blob.
///
/// Call @c MySqlBinding::amNull to verify that the value is not
@@ -193,6 +216,18 @@ public:
/// @return Blob in a vactor.
std::vector<uint8_t> getBlob() const;
+ /// @brief Returns value held in the binding as blob.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_BLOB.
+ ///
+ /// @return Blob in a vactor.
+ std::vector<uint8_t>
+ getBlobOrDefault(const std::vector<uint8_t>& default_value) const;
+
/// @brief Returns numeric value held in the binding.
///
/// Call @c MySqlBinding::amNull to verify that the value is not
@@ -215,6 +250,26 @@ public:
return (*value);
}
+ /// @brief Returns numeric value held in the binding.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type does not match the
+ /// template parameter.
+ ///
+ /// @return Numeric value of a specified type.
+ template<typename T>
+ T getIntegerOrDefault(T default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getInteger<T>());
+ }
+
/// @brief Returns timestamp value held in the binding.
///
/// Call @c MySqlBinding::amNull to verify that the value is not
@@ -226,6 +281,18 @@ public:
/// @return Timestamp converted to posix time.
boost::posix_time::ptime getTimestamp() const;
+ /// @brief Returns timestamp value held in the binding.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_TIMESTAMP.
+ ///
+ /// @return Timestamp converted to posix time.
+ boost::posix_time::ptime
+ getTimestampOrDefault(const boost::posix_time::ptime& default_value) const;
+
/// @brief Checks if the bound value is NULL.
///
/// @return true if the value in the binding is NULL, false otherwise.
@@ -248,6 +315,14 @@ public:
/// @return Pointer to the created binding.
static MySqlBindingPtr createString(const std::string& value);
+ /// @brief Conditionally creates binding of text type for sending
+ /// data if provided value is not empty.
+ ///
+ /// @param value String value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr condCreateString(const std::string& value);
+
/// @brief Creates binding of blob type for receiving data.
///
/// @param length Length of the buffer into which received data will
@@ -304,6 +379,20 @@ public:
return (binding);
}
+ /// @brief Conditionally creates binding of numeric type for sending
+ /// data if provided value is not 0.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @param value Numeric value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr condCreateInteger(T value) {
+ return (value == 0 ? createNull() : createInteger(value));
+ }
+
/// @brief Creates binding of timestamp type for receiving data.
///
/// @return Pointer to the created binding.
@@ -470,7 +559,7 @@ private:
};
/// @brief Collection of bindings.
-typedef std::vector<MySqlBindingPtr> BindingCollection;
+typedef std::vector<MySqlBindingPtr> MySqlBindingCollection;
} // end of namespace isc::db
diff --git a/src/lib/mysql/mysql_connection.h b/src/lib/mysql/mysql_connection.h
index 16f0458123..28b6facc08 100644
--- a/src/lib/mysql/mysql_connection.h
+++ b/src/lib/mysql/mysql_connection.h
@@ -192,7 +192,7 @@ class MySqlConnection : public db::DatabaseConnection {
public:
/// @brief Function invoked to process fetched row.
- typedef std::function<void(BindingCollection&)> ConsumeResultFun;
+ typedef std::function<void(MySqlBindingCollection&)> ConsumeResultFun;
/// @brief Constructor
///
@@ -341,8 +341,8 @@ public:
/// output bindings.
template<typename StatementIndex>
void selectQuery(const StatementIndex& index,
- const BindingCollection& in_bindings,
- BindingCollection& out_bindings,
+ const MySqlBindingCollection& in_bindings,
+ MySqlBindingCollection& out_bindings,
ConsumeResultFun process_result) {
// Extract native input bindings.
std::vector<MYSQL_BIND> in_bind_vec;
@@ -417,7 +417,7 @@ public:
/// in the query.
template<typename StatementIndex>
void insertQuery(const StatementIndex& index,
- const BindingCollection& in_bindings) {
+ const MySqlBindingCollection& in_bindings) {
std::vector<MYSQL_BIND> in_bind_vec;
for (MySqlBindingPtr in_binding : in_bindings) {
in_bind_vec.push_back(in_binding->getMySqlBinding());
@@ -455,7 +455,7 @@ public:
/// @return Number of affected rows.
template<typename StatementIndex>
uint64_t updateDeleteQuery(const StatementIndex& index,
- const BindingCollection& in_bindings) {
+ const MySqlBindingCollection& in_bindings) {
std::vector<MYSQL_BIND> in_bind_vec;
for (MySqlBindingPtr in_binding : in_bindings) {
in_bind_vec.push_back(in_binding->getMySqlBinding());
diff --git a/src/lib/mysql/tests/Makefile.am b/src/lib/mysql/tests/Makefile.am
index 14d5f16f5c..f2d6ed2142 100644
--- a/src/lib/mysql/tests/Makefile.am
+++ b/src/lib/mysql/tests/Makefile.am
@@ -18,7 +18,8 @@ TESTS =
if HAVE_GTEST
TESTS += libmysql_unittests
-libmysql_unittests_SOURCES = mysql_connection_unittest.cc
+libmysql_unittests_SOURCES = mysql_binding_unittest.cc
+libmysql_unittests_SOURCES += mysql_connection_unittest.cc
libmysql_unittests_SOURCES += run_unittests.cc
libmysql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/mysql/tests/mysql_binding_unittest.cc b/src/lib/mysql/tests/mysql_binding_unittest.cc
new file mode 100644
index 0000000000..48d96671ff
--- /dev/null
+++ b/src/lib/mysql/tests/mysql_binding_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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 <mysql/mysql_binding.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace {
+
+// This test verifies that default string is returned if binding is null.
+TEST(MySqlBindingTest, defaultString) {
+ auto binding = MySqlBinding::createNull();
+ EXPECT_EQ("foo", binding->getStringOrDefault("foo"));
+
+ binding = MySqlBinding::createString("bar");
+ EXPECT_EQ("bar", binding->getStringOrDefault("foo"));
+}
+
+// This test verifies that null binding is created for empty string.
+TEST(MySqlBindingTest, conditionalString) {
+ auto binding = MySqlBinding::condCreateString("");
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateString("foo");
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ("foo", binding->getString());
+}
+
+// This test verifies that null JSON is returned if the string binding
+// is null, JSON value is returned when string value is valid JSON and
+// that exception is thrown if the string is not a valid JSON.
+TEST(MySqlBindingTest, getJSON) {
+ auto binding = MySqlBinding::createNull();
+ EXPECT_FALSE(binding->getJSON());
+
+ binding = MySqlBinding::createString("{ \"foo\": \"bar\" }");
+ auto json = binding->getJSON();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ auto foo = json->get("foo");
+ ASSERT_TRUE(foo);
+ ASSERT_EQ(Element::string, foo->getType());
+ EXPECT_EQ("bar", foo->stringValue());
+}
+
+// This test verifies that default blob is returned if binding is null.
+TEST(MySqlBindingTest, defaultBlob) {
+ std::vector<uint8_t> blob(10, 1);
+ std::vector<uint8_t> default_blob(10, 5);
+ auto binding = MySqlBinding::createNull();
+ EXPECT_EQ(default_blob, binding->getBlobOrDefault(default_blob));
+
+ binding = MySqlBinding::createBlob(blob.begin(), blob.end());
+ EXPECT_EQ(blob, binding->getBlobOrDefault(default_blob));
+}
+
+// This test verifies that default number is returned if binding is null.
+TEST(MySqlBindingTest, defaultInteger) {
+ auto binding = MySqlBinding::createNull();
+ EXPECT_EQ(123, binding->getIntegerOrDefault<uint32_t>(123));
+
+ binding = MySqlBinding::createInteger<uint32_t>(1024);
+ EXPECT_EQ(1024, binding->getIntegerOrDefault<uint32_t>(123));
+}
+
+// This test verifies that null binding is created for 0 number.
+TEST(MySqlBindingTest, conditionalInteger) {
+ auto binding = MySqlBinding::condCreateInteger<uint16_t>(0);
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateInteger<uint16_t>(1);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ(1, binding->getInteger<uint16_t>());
+}
+
+// This test verifies that default timestamp is returned if binding is null.
+TEST(MySqlBindingTest, defaultTimestamp) {
+ boost::posix_time::ptime current_time = boost::posix_time::second_clock::universal_time();
+ boost::posix_time::ptime past_time = current_time - boost::posix_time::hours(1);
+
+ auto binding = MySqlBinding::createNull();
+ EXPECT_TRUE(past_time == binding->getTimestampOrDefault(past_time));
+
+ binding = MySqlBinding::createTimestamp(current_time);
+ EXPECT_TRUE(current_time == binding->getTimestampOrDefault(past_time));
+}
+
+}
+
diff --git a/src/lib/mysql/tests/mysql_connection_unittest.cc b/src/lib/mysql/tests/mysql_connection_unittest.cc
index f8455923a5..6d305df613 100644
--- a/src/lib/mysql/tests/mysql_connection_unittest.cc
+++ b/src/lib/mysql/tests/mysql_connection_unittest.cc
@@ -149,7 +149,7 @@ public:
///
/// @param in_bindings Collection of bindings encapsulating the data to
/// be inserted into the database and then retrieved.
- void testInsertSelect(const BindingCollection& in_bindings) {
+ void testInsertSelect(const MySqlBindingCollection& in_bindings) {
// Expecting 6 bindings because we have 6 columns in our table.
ASSERT_EQ(6, in_bindings.size());
// We are going to select by int_value so this value must not be null.
@@ -160,11 +160,11 @@ public:
in_bindings));
// Create input binding for select query.
- BindingCollection bindings =
+ MySqlBindingCollection bindings =
{ MySqlBinding::createInteger<uint32_t>(in_bindings[1]->getInteger<uint32_t>()) };
// Also, create output (placeholder) bindings for receiving data.
- BindingCollection out_bindings = {
+ MySqlBindingCollection out_bindings = {
MySqlBinding::createInteger<uint8_t>(),
MySqlBinding::createInteger<uint32_t>(),
MySqlBinding::createInteger<int64_t>(),
@@ -177,7 +177,7 @@ public:
// returned row the lambda provided as 4th argument should be executed.
ASSERT_NO_THROW(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
bindings, out_bindings,
- [&](BindingCollection& out_bindings) {
+ [&](MySqlBindingCollection& out_bindings) {
// Compare received data with input data assuming they are both non-null.
@@ -230,7 +230,7 @@ public:
// from the dataabse.
TEST_F(MySqlConnectionTest, select) {
std::string blob = "myblob";
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -246,7 +246,7 @@ TEST_F(MySqlConnectionTest, select) {
// retrieved.
TEST_F(MySqlConnectionTest, selectNullInteger) {
std::string blob = "myblob";
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createNull(),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -263,7 +263,7 @@ TEST_F(MySqlConnectionTest, selectNullInteger) {
TEST_F(MySqlConnectionTest, selectNullString) {
std::string blob = "myblob";
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -278,7 +278,7 @@ TEST_F(MySqlConnectionTest, selectNullString) {
// Test that null value can be inserted to a column having blob type and
// retrieved.
TEST_F(MySqlConnectionTest, selectNullBlob) {
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -294,7 +294,7 @@ TEST_F(MySqlConnectionTest, selectNullBlob) {
// retrieved.
TEST_F(MySqlConnectionTest, selectNullTimestamp) {
std::string blob = "myblob";
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -309,7 +309,7 @@ TEST_F(MySqlConnectionTest, selectNullTimestamp) {
// Test that empty string and empty blob can be inserted to a database.
TEST_F(MySqlConnectionTest, selectEmptyStringBlob) {
std::string blob = "";
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -324,7 +324,7 @@ TEST_F(MySqlConnectionTest, selectEmptyStringBlob) {
// Test that a row can be deleted from the database.
TEST_F(MySqlConnectionTest, deleteByValue) {
// Insert a row with numeric values.
- BindingCollection in_bindings = {
+ MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint8_t>(123),
MySqlBinding::createInteger<uint32_t>(1024),
MySqlBinding::createInteger<int64_t>(-4096),
@@ -354,7 +354,7 @@ TEST_F(MySqlConnectionTest, deleteByValue) {
ASSERT_TRUE(deleted);
// Let's confirm that it has been deleted by issuing a select query.
- BindingCollection out_bindings = {
+ MySqlBindingCollection out_bindings = {
MySqlBinding::createInteger<uint8_t>(),
MySqlBinding::createInteger<uint32_t>(),
MySqlBinding::createInteger<int64_t>(),
@@ -365,7 +365,7 @@ TEST_F(MySqlConnectionTest, deleteByValue) {
ASSERT_NO_THROW(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
in_bindings, out_bindings,
- [&deleted](BindingCollection& out_bindings) {
+ [&deleted](MySqlBindingCollection& out_bindings) {
// This will be executed if the row is returned as a result of
// select query. We expect that this is not executed.
deleted = false;