]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#93,!35] Implemented subnet fetching in MySQL Config Backend.
authorMarcin Siodelski <marcin@isc.org>
Tue, 25 Sep 2018 08:50:13 +0000 (10:50 +0200)
committerMarcin Siodelski <marcin@isc.org>
Mon, 8 Oct 2018 14:39:22 +0000 (16:39 +0200)
19 files changed:
src/hooks/dhcp/mysql_cb/Makefile.am
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/tests/Makefile.am
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/lib/cc/Makefile.am
src/lib/cc/stamped_element.cc [new file with mode: 0644]
src/lib/cc/stamped_element.h [new file with mode: 0644]
src/lib/cc/tests/Makefile.am
src/lib/cc/tests/stamped_element_unittest.cc [new file with mode: 0644]
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/subnet.h
src/lib/dhcpsrv/tests/subnet_unittest.cc
src/lib/mysql/mysql_binding.cc
src/lib/mysql/mysql_binding.h
src/lib/mysql/mysql_connection.h
src/lib/mysql/tests/Makefile.am
src/lib/mysql/tests/mysql_binding_unittest.cc [new file with mode: 0644]
src/lib/mysql/tests/mysql_connection_unittest.cc

index ae7aef442f2400d5d2fe7d5ce10d594d4a224b56..28da17d4f1cd791b379a67696353366b7f85d513 100644 (file)
@@ -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
index da0acce5cf381184486eb26d9cc13ef22680a2af..86a205374543072b4150c2a0f87fa089b8d3b1e8 100644 (file)
@@ -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
index 5546b734ddb3421c315821fa9a261629f2a63ccb..c8663e2d0030888d03ef1ef26a5a565cb2931cc6 100644 (file)
@@ -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
index feb191bfabad0da4dcd5d2694d57002a517e961b..4d7a532e92ad0cbab23b11720ee11a34c9971ee9 100644 (file)
@@ -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());
+}
 
 }
index 18362f650b7edd621b366cd5e144c17862c143cb..f191e6c819e0a1691e31257c053b5a335a8da94e 100644 (file)
@@ -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 (file)
index 0000000..0761978
--- /dev/null
@@ -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 (file)
index 0000000..9d15bb9
--- /dev/null
@@ -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
index 452926e36c7105f96d7ca74996209d1fe6a01775..e33c049b74dd5f9fe23b5ae9b3bc1cca010dfef2 100644 (file)
@@ -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 (file)
index 0000000..6b832eb
--- /dev/null
@@ -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);
+}
+
+}
index 433e80102c175bf7c9a8d40e2a404c497c8e6b51..4f1d0fa7362378df6e1358c16539799d9894861c 100644 (file)
@@ -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.
     ///
index 993d5495c80547c0365d3d7a6f16eda36869736d..eb41681dae59ec4dcdf2fdf9a4b83e4689ab6ec7 100644 (file)
@@ -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
index e995283e35ed707b0dc08e60a35186c249608460..25af5ddeeddc0088fbb6ae807ee695c401003dfc 100644 (file)
@@ -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
index 9d444be237c8377e2addc438ed3b7122f7429019..e69c6fc961842159ae56987f778486c614b5a862 100644 (file)
@@ -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);
index ad07c4a97af2e8b4f5fdf7f66a62acd39babc116..974bb3fa82fd2a754daefcb979584453d8f764e6 100644 (file)
@@ -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,
@@ -56,6 +91,11 @@ MySqlBinding::createString(const std::string& value) {
     return (binding);
 }
 
+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,
index 450d63ddd3716bcd9c848dc88362aa49f9e113ea..05cb8bd035ce5f8631611170d70b0c2ed93ea83f 100644 (file)
@@ -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
index 16f0458123c6035dbbe81b341f29ce6d24fde1ec..28b6facc083d321f00be43f81c6ab9397909d958 100644 (file)
@@ -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());
index 14d5f16f5c8c677c040c609defbc14bbff06e68e..f2d6ed2142a5ffed2e727ef4908f90feba03556e 100644 (file)
@@ -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 (file)
index 0000000..48d9667
--- /dev/null
@@ -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));
+}
+
+}
+
index f8455923a5df9413197846faddc4cf2e2c96231b..6d305df6137d6a4adda88e04482cdecbc7d0e48a 100644 (file)
@@ -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;