From: Marcin Siodelski Date: Mon, 2 May 2016 08:24:30 +0000 (+0200) Subject: [4281] Extended MySQL host data source with DHCPv4 and DHCPv6 options. X-Git-Tag: trac4106_update_base~7^2~3^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=718643a83ad44abb8b07d406be00ee2646fa5ac6;p=thirdparty%2Fkea.git [4281] Extended MySQL host data source with DHCPv4 and DHCPv6 options. Missing things: - proper commentary - formatted values for options - rollback host insertion when failing to add reservation or option --- diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 700b734bc5..fdbc1437c1 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -21,9 +21,11 @@ #include #include +#include #include #include +#include #include using namespace std; @@ -844,6 +846,45 @@ LibDHCP::initVendorOptsIsc6() { initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE); } +uint32_t +LibDHCP::optionSpaceToVendorId(const std::string& option_space) { + if (option_space.size() < 8) { + // 8 is a minimal length of "vendor-X" format + return (0); + } + if (option_space.substr(0,7) != "vendor-") { + return (0); + } + + // text after "vendor-", supposedly numbers only + std::string x = option_space.substr(7); + + int64_t check; + try { + check = boost::lexical_cast(x); + } catch (const boost::bad_lexical_cast &) { + /// @todo: Should we throw here? + // isc_throw(BadValue, "Failed to parse vendor-X value (" << x + // << ") as unsigned 32-bit integer."); + return (0); + } + if (check > std::numeric_limits::max()) { + /// @todo: Should we throw here? + //isc_throw(BadValue, "Value " << x << "is too large" + // << " for unsigned 32-bit integer."); + return (0); + } + if (check < 0) { + /// @todo: Should we throw here? + // isc_throw(BadValue, "Value " << x << "is negative." + // << " Only 0 or larger are allowed for unsigned 32-bit integer."); + return (0); + } + + // value is small enough to fit + return (static_cast(check)); +} + void initOptionSpace(OptionDefContainer& defs, const OptionDefParams* params, size_t params_size) { diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index 51b68e4432..b310b1a8b4 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -14,6 +14,7 @@ #include #include +#include #include namespace isc { @@ -321,6 +322,21 @@ public: /// @brief Commits runtime option definitions. static void commitRuntimeOptionDefs(); + /// @brief Converts option space name to vendor id. + /// + /// If the option space name is specified in the following format: + /// "vendor-X" where X is an uint32_t number, it is assumed to be + /// a vendor space and the uint32_t number is returned by this function. + /// If the option space name is invalid this method will return 0, which + /// is not a valid vendor-id, to signal an error. + /// + /// @todo remove this function once when the conversion is dealt by the + /// appropriate functions returning options by option space names. + /// + /// @param option_space Option space name. + /// @return vendor id. + static uint32_t optionSpaceToVendorId(const std::string& option_space); + private: /// Initialize standard DHCPv4 option definitions. diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index aac7702f49..5b13ebae41 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -1,14 +1,13 @@ -// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#include #include #include -#include #include -#include #include namespace isc { @@ -37,7 +36,12 @@ CfgOption::equals(const CfgOption& other) const { void CfgOption::add(const OptionPtr& option, const bool persistent, const std::string& option_space) { - if (!option) { + add(OptionDescriptor(option, persistent), option_space); +} + +void +CfgOption::add(const OptionDescriptor& desc, const std::string& option_space) { + if (!desc.option_) { isc_throw(isc::BadValue, "option being configured must not be NULL"); } else if (!OptionSpace::validateName(option_space)) { @@ -45,13 +49,25 @@ CfgOption::add(const OptionPtr& option, const bool persistent, << option_space << "'"); } - const uint32_t vendor_id = optionSpaceToVendorId(option_space); + const uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space); if (vendor_id) { - vendor_options_.addItem(OptionDescriptor(option, persistent), - vendor_id); + vendor_options_.addItem(desc, vendor_id); } else { - options_.addItem(OptionDescriptor(option, persistent), option_space); + options_.addItem(desc, option_space); + } +} + +std::list +CfgOption::getVendorIdsSpaceNames() const { + std::list ids = getVendorIds(); + std::list names; + for (std::list::const_iterator id = ids.begin(); + id != ids.end(); ++id) { + std::ostringstream s; + s << "vendor-" << *id; + names.push_back(s.str()); } + return (names); } void @@ -152,44 +168,5 @@ CfgOption::getAll(const uint32_t vendor_id) const { return (vendor_options_.getItems(vendor_id)); } -uint32_t -CfgOption::optionSpaceToVendorId(const std::string& option_space) { - if (option_space.size() < 8) { - // 8 is a minimal length of "vendor-X" format - return (0); - } - if (option_space.substr(0,7) != "vendor-") { - return (0); - } - - // text after "vendor-", supposedly numbers only - std::string x = option_space.substr(7); - - int64_t check; - try { - check = boost::lexical_cast(x); - } catch (const boost::bad_lexical_cast &) { - /// @todo: Should we throw here? - // isc_throw(BadValue, "Failed to parse vendor-X value (" << x - // << ") as unsigned 32-bit integer."); - return (0); - } - if (check > std::numeric_limits::max()) { - /// @todo: Should we throw here? - //isc_throw(BadValue, "Value " << x << "is too large" - // << " for unsigned 32-bit integer."); - return (0); - } - if (check < 0) { - /// @todo: Should we throw here? - // isc_throw(BadValue, "Value " << x << "is negative." - // << " Only 0 or larger are allowed for unsigned 32-bit integer."); - return (0); - } - - // value is small enough to fit - return (static_cast(check)); -} - } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index a5830506fe..a8d4400a7a 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -254,6 +254,19 @@ public: void add(const OptionPtr& option, const bool persistent, const std::string& option_space); + /// @brief A variant of the method which takes option descriptor as an + /// argument. + /// + /// This method works exactly the same as the other variant, but it takes + /// option descriptor as an argument. + /// + /// @param desc Option descriptor holding option instance and other + /// parameters pertaining to this option. + /// @param option_space Option space name. + /// + /// @throw isc::BadValue if the option space is invalid. + void add(const OptionDescriptor& desc, const std::string& option_space); + /// @brief Merges this configuration to another configuration. /// /// This method iterates over the configuration items held in this @@ -337,20 +350,21 @@ public: return (*od_itr); } - /// @brief Converts option space name to vendor id. - /// - /// If the option space name is specified in the following format: - /// "vendor-X" where X is an uint32_t number, it is assumed to be - /// a vendor space and the uint32_t number is returned by this function. - /// If the option space name is invalid this method will return 0, which - /// is not a valid vendor-id, to signal an error. - /// - /// @todo remove this function once when the conversion is dealt by the - /// appropriate functions returning options by option space names. + /// @brief Returns a list of all configured option space names. + std::list getOptionSpaceNames() const { + return (options_.getOptionSpaceNames()); + } + + /// @brief Returns a list of all configured vendor identifiers. + std::list getVendorIds() const { + return (vendor_options_.getOptionSpaceNames()); + } + + /// @brief Returns a list of option space names for configured vendor ids. /// - /// @param option_space Option space name. - /// @return vendor id. - static uint32_t optionSpaceToVendorId(const std::string& option_space); + /// For each vendor-id the option space name returned is constructed + /// as "vendor-". + std::list getVendorIdsSpaceNames() const; private: diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc index c387d057ff..ac5b9f7660 100644 --- a/src/lib/dhcpsrv/host.cc +++ b/src/lib/dhcpsrv/host.cc @@ -81,7 +81,7 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len, ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()), hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes), dhcp6_client_classes_(dhcp6_client_classes), host_id_(0), - cfg_option4_(), cfg_option6_() { + cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) { // Initialize host identifier. setIdentifier(identifier, identifier_len, identifier_type); diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index dfef3373c0..48d5eb09f6 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -6,9 +6,16 @@ #include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include #include @@ -20,8 +27,9 @@ #include using namespace isc; -using namespace isc::dhcp; using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; using namespace std; namespace { @@ -42,6 +50,15 @@ const size_t CLIENT_CLASSES_MAX_LEN = 255; /// in the Client FQDN %Option (see RFC4702 and RFC4704). const size_t HOSTNAME_MAX_LEN = 255; +/// @brief Maximum length of option value. +const size_t OPTION_VALUE_MAX_LEN = 4096; + +/// @brief Maximum length of option value specified in textual format. +const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192; + +/// @brief Maximum length of option space name. +const size_t OPTION_SPACE_MAX_LEN = 128; + /// @brief Numeric value representing last supported identifier. /// /// This value is used to validate whether the identifier type stored in @@ -64,40 +81,72 @@ TaggedStatement tagged_statements[] = { "dhcp6_iaid, host_id) " "VALUES (?,?,?,?,?)"}, + // Inserts a single DHCPv4 option into 'dhcp4_options' table. + {MySqlHostDataSource::INSERT_V4_OPTION, + "INSERT INTO dhcp4_options(option_id, code, value, space, persistent, " + "dhcp_client_class, dhcp4_subnet_id, host_id) " + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"}, + + // Inserts a single DHCPv6 option into 'dhcp6_options' table. + {MySqlHostDataSource::INSERT_V6_OPTION, + "INSERT INTO dhcp6_options(option_id, code, value, space, persistent, " + "dhcp_client_class, dhcp6_subnet_id, host_id) " + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"}, + // Retrieves host information along with IPv6 reservations associated // with this host. If the host exists in multiple subnets, all hosts // having a specified identifier will be returned from those subnets. // Because LEFT JOIN clause is used, the number of rows returned for // a single host depends on the number of reservations. {MySqlHostDataSource::GET_HOST_DHCPID, - "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, " "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, " - "r.address, r.prefix_len, r.type, r.dhcp6_iaid " + "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, " + "o4.persistent, " + "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, " + "o6.persistent, " + "r.reservation_id, r.address, r.prefix_len, r.type, " + "r.dhcp6_iaid " "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o4 " + "ON h.host_id = o4.host_id " + "LEFT JOIN dhcp6_options AS o6 " + "ON h.host_id = o6.host_id " "LEFT JOIN ipv6_reservations AS r " "ON h.host_id = r.host_id " - "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ?"}, + "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? " + "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"}, // Retrieves host information by IPv4 address. This should typically // return a single host, but if we ever allow for defining subnets // with overlapping address pools, multiple hosts may be returned. {MySqlHostDataSource::GET_HOST_ADDR, - "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " - "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " - "dhcp4_client_classes, dhcp6_client_classes " - "FROM hosts " - "WHERE ipv4_address = ?"}, + "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + "h.dhcp4_client_classes, h.dhcp6_client_classes, " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent " + "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o " + "ON h.host_id = o.host_id " + "WHERE ipv4_address = ? " + "ORDER BY h.host_id, o.option_id"}, // Retrieves host information by subnet identifier and unique // identifier of a client. This is expected to return a single host. {MySqlHostDataSource::GET_HOST_SUBID4_DHCPID, - "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " - "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " - "dhcp4_client_classes, dhcp6_client_classes " - "FROM hosts " - "WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type = ? " - " AND dhcp_identifier = ?"}, + "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + "h.dhcp4_client_classes, h.dhcp6_client_classes, " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent " + "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o " + "ON h.host_id = o.host_id " + "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? " + " AND h.dhcp_identifier = ? " + "ORDER BY h.host_id, o.option_id"}, // Retrieves host information by subnet identifier and unique // identifier of a client. This query should return information @@ -110,23 +159,33 @@ TaggedStatement tagged_statements[] = { "h.dhcp_identifier_type, h.dhcp4_subnet_id, " "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " "h.dhcp4_client_classes, h.dhcp6_client_classes, " - "r.address, r.prefix_len, r.type, r.dhcp6_iaid " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent, " + "r.reservation_id, r.address, r.prefix_len, r.type, " + "r.dhcp6_iaid " "FROM hosts AS h " + "LEFT JOIN dhcp6_options AS o " + "ON h.host_id = o.host_id " "LEFT JOIN ipv6_reservations AS r " "ON h.host_id = r.host_id " - "WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type = ? " - "AND dhcp_identifier = ? " - "ORDER BY h.host_id, r.prefix_len, r.address"}, + "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? " + "AND h.dhcp_identifier = ? " + "ORDER BY h.host_id, o.option_id, r.reservation_id"}, // Retrieves host information using subnet identifier and the // IPv4 address reservation. This should return inforamation for // a single host. {MySqlHostDataSource::GET_HOST_SUBID_ADDR, - "SELECT host_id, dhcp_identifier, dhcp_identifier_type, " - "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, " - "dhcp4_client_classes, dhcp6_client_classes " - "FROM hosts " - "WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"}, + "SELECT DISTINCT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, " + "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " + "h.dhcp4_client_classes, h.dhcp6_client_classes, " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent " + "FROM hosts AS h " + "LEFT JOIN dhcp4_options AS o " + "ON h.host_id = o.host_id " + "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? " + "ORDER BY h.host_id, o.option_id"}, // Retrieves host information using IPv6 prefix and prefix length // or IPv6 address. This query returns host information for a @@ -139,14 +198,19 @@ TaggedStatement tagged_statements[] = { "h.dhcp_identifier_type, h.dhcp4_subnet_id, " "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " "h.dhcp4_client_classes, h.dhcp6_client_classes, " - "r.address, r.prefix_len, r.type, r.dhcp6_iaid " + "o.option_id, o.code, o.value, o.formatted_value, o.space, " + "o.persistent, " + "r.reservation_id, r.address, r.prefix_len, r.type, " + "r.dhcp6_iaid " "FROM hosts AS h " + "LEFT JOIN dhcp6_options AS o " + "ON h.host_id = o.host_id " "LEFT JOIN ipv6_reservations AS r " "ON h.host_id = r.host_id " "WHERE h.host_id = " "(SELECT host_id FROM ipv6_reservations " "WHERE address = ? AND prefix_len = ?) " - "ORDER BY h.host_id, r.prefix_len, r.address"}, + "ORDER BY h.host_id, o.option_id, r.reservation_id"}, // Retrieves MySQL schema version. {MySqlHostDataSource::GET_VERSION, @@ -178,15 +242,22 @@ public: /// /// The initialization of the variables here is only to satisfy cppcheck - /// all variables are initialized/set in the methods before they are used. - MySqlHostExchange() - : bind_(HOST_COLUMNS), columns_(HOST_COLUMNS), - error_(HOST_COLUMNS, MLM_FALSE), host_id_(0), + /// + /// @param additional_columns_num Additional number of columns to standard + /// set of columns used by this class, for which resources should be + /// allocated. + MySqlHostExchange(const size_t additional_columns_num = 0) + : columns_num_(HOST_COLUMNS + additional_columns_num), + bind_(columns_num_), columns_(columns_num_), + error_(columns_num_, MLM_FALSE), host_id_(0), dhcp_identifier_length_(0), dhcp_identifier_type_(0), dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0), hostname_length_(0), dhcp4_client_classes_length_(0), - dhcp6_client_classes_length_(0), dhcp4_subnet_id_null_(MLM_FALSE), - dhcp6_subnet_id_null_(MLM_FALSE), ipv4_address_null_(MLM_FALSE), - hostname_null_(MLM_FALSE), dhcp4_client_classes_null_(MLM_FALSE), + dhcp6_client_classes_length_(0), + dhcp4_subnet_id_null_(MLM_FALSE), + dhcp6_subnet_id_null_(MLM_FALSE), + ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE), + dhcp4_client_classes_null_(MLM_FALSE), dhcp6_client_classes_null_(MLM_FALSE) { // Fill arrays with 0 so as they don't include any garbage. @@ -213,6 +284,12 @@ public: virtual ~MySqlHostExchange() { } + size_t findAvailColumn() const { + std::vector::const_iterator empty_column = + std::find(columns_.begin(), columns_.end(), std::string()); + return (std::distance(columns_.begin(), empty_column)); + } + /// @brief Returns value of host id. /// /// This method is used by derived classes. @@ -378,7 +455,7 @@ public: // Add the data to the vector. Note the end element is one after the // end of the array. - return (std::vector(&bind_[0], &bind_[HOST_COLUMNS])); + return (std::vector(&bind_[0], &bind_[columns_num_])); }; /// @brief Create BIND array to receive Host data. @@ -484,29 +561,34 @@ public: isc_throw(BadValue, "invalid dhcp identifier type returned: " << static_cast(dhcp_identifier_type_)); } - // Set the dhcp identifier type in a variable of the appropriate data type. + // Set the dhcp identifier type in a variable of the appropriate + // data type. Host::IdentifierType type = static_cast(dhcp_identifier_type_); - // Set DHCPv4 subnet ID to the value returned. If NULL returned, set to 0. + // Set DHCPv4 subnet ID to the value returned. If NULL returned, + // set to 0. SubnetID ipv4_subnet_id(0); if (dhcp4_subnet_id_null_ == MLM_FALSE) { ipv4_subnet_id = static_cast(dhcp4_subnet_id_); } - // Set DHCPv6 subnet ID to the value returned. If NULL returned, set to 0. + // Set DHCPv6 subnet ID to the value returned. If NULL returned, + // set to 0. SubnetID ipv6_subnet_id(0); if (dhcp6_subnet_id_null_ == MLM_FALSE) { ipv6_subnet_id = static_cast(dhcp6_subnet_id_); } - // Set IPv4 address reservation if it was given, if not, set IPv4 zero address + // Set IPv4 address reservation if it was given, if not, set IPv4 zero + // address asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS(); if (ipv4_address_null_ == MLM_FALSE) { ipv4_reservation = asiolink::IOAddress(ipv4_address_); } - // Set hostname if non NULL value returned. Otherwise, leave an empty string. + // Set hostname if non NULL value returned. Otherwise, leave an + // empty string. std::string hostname; if (hostname_null_ == MLM_FALSE) { hostname = std::string(hostname_, hostname_length_); @@ -577,6 +659,9 @@ public: protected: + /// Number of columns returned in queries. + size_t columns_num_; + /// Vector of MySQL bindings. std::vector bind_; @@ -662,7 +747,319 @@ private: }; -/// @brief This class provides mechanisms for sending and retrieving +class MySqlHostExchangeOpts : public MySqlHostExchange { +private: + + /// @brief Number of columns holding option information. + static const size_t OPTION_COLUMNS = 6; + + class OptionProcessor { + public: + OptionProcessor(const Option::Universe& universe, + const size_t start_column) + : universe_(universe), start_column_(start_column), option_id_(0), + code_(0), value_length_(0), formatted_value_length_(0), + space_length_(0), persistent_(false), option_id_null_(MLM_FALSE), + code_null_(MLM_FALSE), value_null_(MLM_FALSE), + formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE), + option_id_index_(start_column), code_index_(start_column_ + 1), + value_index_(start_column_ + 2), + formatted_value_index_(start_column_ + 3), + space_index_(start_column_ + 4), + persistent_index_(start_column_ + 5), + most_recent_option_id_(0) { + + memset(value_, 0, sizeof(value_)); + memset(formatted_value_, 0, sizeof(formatted_value_)); + memset(space_, 0, sizeof(space_)); + } + + uint64_t getOptionId() const { + if (option_id_null_ == MLM_FALSE) { + return (option_id_); + } + return (0); + } + + void retrieveOption(const CfgOptionPtr& cfg) { + if (option_id_ == 0) { + return; + } + + if (most_recent_option_id_ < option_id_) { + most_recent_option_id_ = option_id_; + + space_[space_length_] = '\0'; + std::string space(space_); + + OptionDefinitionPtr def; + if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) { + def = LibDHCP::getOptionDef(universe_, code_); + } + + if (!def && (space != DHCP4_OPTION_SPACE) && + (space != DHCP6_OPTION_SPACE)) { + uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space); + if (vendor_id > 0) { + def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_); + } + } + + if (!def) { + def = LibDHCP::getRuntimeOptionDef(space, code_); + } + + OptionPtr option; + OptionBuffer buf(value_, value_ + value_length_); + if (def) { + option = def->optionFactory(universe_, code_, buf.begin(), + buf.end()); + } else { + option.reset(new Option(universe_, code_, buf.begin(), + buf.end())); + } + + OptionDescriptor desc(option, persistent_); + cfg->add(desc, space); + } + } + + void setColumnNames(std::vector& columns) { + columns[option_id_index_] = "option_id"; + columns[code_index_] = "code"; + columns[value_index_] = "value"; + columns[formatted_value_index_] = "formatted_value"; + columns[space_index_] = "space"; + columns[persistent_index_] = "persistent"; + } + + void setBindFields(std::vector& bind) { + most_recent_option_id_ = 0; + + // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT, + bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG; + bind[option_id_index_].buffer = reinterpret_cast(&option_id_); + bind[option_id_index_].is_unsigned = MLM_TRUE; + + // code : TINYINT OR SHORT UNSIGNED NOT NULL + bind[code_index_].buffer_type = MYSQL_TYPE_SHORT; + bind[code_index_].buffer = reinterpret_cast(&code_); + bind[code_index_].is_unsigned = MLM_TRUE; + bind[code_index_].is_null = &code_null_; + + // value : BLOB NULL + value_length_ = sizeof(value_); + bind[value_index_].buffer_type = MYSQL_TYPE_BLOB; + bind[value_index_].buffer = reinterpret_cast(value_); + bind[value_index_].buffer_length = value_length_; + bind[value_index_].length = &value_length_; + bind[value_index_].is_null = &value_null_; + + // formatted_value : TEXT NULL + formatted_value_length_ = sizeof(formatted_value_); + bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING; + bind[formatted_value_index_].buffer = reinterpret_cast(formatted_value_); + bind[formatted_value_index_].buffer_length = formatted_value_length_; + bind[formatted_value_index_].length = &formatted_value_length_; + bind[formatted_value_index_].is_null = &formatted_value_null_; + + // space : VARCHAR(128) NULL + space_length_ = sizeof(space_); + bind[space_index_].buffer_type = MYSQL_TYPE_STRING; + bind[space_index_].buffer = reinterpret_cast(space_); + bind[space_index_].buffer_length = space_length_; + bind[space_index_].length = &space_length_; + bind[space_index_].is_null = &space_null_; + + // persistent : TINYINT(1) NOT NULL DEFAULT 0 + bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY; + bind[persistent_index_].buffer = reinterpret_cast(&persistent_); + bind[persistent_index_].is_unsigned = MLM_TRUE; + } + + private: + + Option::Universe universe_; + + size_t start_column_; + + /// Option id. + uint64_t option_id_; + + /// Option code. + uint16_t code_; + + /// Buffer holding binary value of an option. + uint8_t value_[OPTION_VALUE_MAX_LEN]; + + /// Option value length. + unsigned long value_length_; + + /// Buffer holding textual value of an option. + char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN]; + + /// Formatted option value length. + unsigned long formatted_value_length_; + + /// Buffer holding option space name. + char space_[OPTION_SPACE_MAX_LEN]; + + /// Option space length. + unsigned long space_length_; + + /// Flag indicating if option is always sent or only if requested. + bool persistent_; + + /// @name Boolean values indicating if values of specific columns in + /// the database are NULL. + //@{ + /// Boolean flag indicating if the DHCPv4 option id is NULL. + my_bool option_id_null_; + + /// Boolean flag indicating if the DHCPv4 option code is NULL. + my_bool code_null_; + + /// Boolean flag indicating if the DHCPv4 option value is NULL. + my_bool value_null_; + + /// Boolean flag indicating if the DHCPv4 formatted option value + /// is NULL. + my_bool formatted_value_null_; + + /// Boolean flag indicating if the DHCPv4 option space is NULL. + my_bool space_null_; + + //@} + + /// @name Indexes of the specific columns + //@{ + /// Option id + size_t option_id_index_; + + /// Code + size_t code_index_; + + /// Value + size_t value_index_; + + /// Formatted value + size_t formatted_value_index_; + + /// Space + size_t space_index_; + + /// Persistent + size_t persistent_index_; + //@} + + /// @brief Option id for last processed row. + uint64_t most_recent_option_id_; + }; + + typedef boost::shared_ptr OptionProcessorPtr; + +public: + + enum FetchedOptions { + DHCP4_ONLY, + DHCP6_ONLY, + DHCP4_AND_DHCP6 + }; + + MySqlHostExchangeOpts(const FetchedOptions& fetched_options, + const size_t additional_columns_num = 0) + : MySqlHostExchange(getRequiredColumnsNum(fetched_options) + + additional_columns_num), + opt_proc4_(), opt_proc6_() { + + if ((fetched_options == DHCP4_ONLY) || + (fetched_options == DHCP4_AND_DHCP6)) { + opt_proc4_.reset(new OptionProcessor(Option::V4, + findAvailColumn())); + opt_proc4_->setColumnNames(columns_); + } + + if ((fetched_options == DHCP6_ONLY) || + (fetched_options == DHCP4_AND_DHCP6)) { + opt_proc6_.reset(new OptionProcessor(Option::V6, + findAvailColumn())); + opt_proc6_->setColumnNames(columns_); + } + } + + virtual void processFetchedData(ConstHostCollection& hosts) { + HostPtr host; + HostPtr most_recent_host; + + if (!hosts.empty()) { + // Const cast is not very elegant way to deal with it, but + // there is a good reason to use it here. This method is called + // to build a collection of const hosts to be returned to the + // caller. If we wanted to use non-const collection we'd need + // to copy the whole collection before returning it, which has + // performance implications. Alternatively, we could store the + // most recently added host in a class member but this would + // make the code less readable. + most_recent_host = boost::const_pointer_cast(hosts.back()); + + } + + if (!most_recent_host || (most_recent_host->getHostId() != getHostId())) { + host = retrieveHost(); + hosts.push_back(host); + most_recent_host = host; + } + + if (opt_proc4_) { + CfgOptionPtr cfg = most_recent_host->getCfgOption4(); + opt_proc4_->retrieveOption(cfg); + } + + if (opt_proc6_) { + CfgOptionPtr cfg = most_recent_host->getCfgOption6(); + opt_proc6_->retrieveOption(cfg); + } + } + + virtual std::vector createBindForReceive() { + // The following call sets bind_ values between 0 and 8. + static_cast(MySqlHostExchange::createBindForReceive()); + + if (opt_proc4_) { + opt_proc4_->setBindFields(bind_); + } + + if (opt_proc6_) { + opt_proc6_->setBindFields(bind_); + } + + // Add the error flags + setErrorIndicators(bind_, error_); + + // Add the data to the vector. Note the end element is one after the + // end of the array. + return (bind_); + }; + +private: + + static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) { + return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS : + OPTION_COLUMNS); + } + + /// @brief Pointer to DHCPv4 options processor. + /// + /// If this object is NULL, the DHCPv4 options are not fetched. + OptionProcessorPtr opt_proc4_; + + /// @brief Pointer to DHCPv6 options processor. + /// + /// If this object is NULL, the DHCPv6 options are not fetched. + OptionProcessorPtr opt_proc6_; +}; + +/// @Brief This class provides mechanisms for sending and retrieving /// host information and associated IPv6 reservations. /// /// This class extends the @ref MySqlHostExchange class with the @@ -674,12 +1071,11 @@ private: /// (for each IPv6 reservation). This class is responsible for /// converting those multiple host instances into a single Host /// object with multiple IPv6 reservations. -class MySqlHostIPv6Exchange : public MySqlHostExchange { +class MySqlHostIPv6Exchange : public MySqlHostExchangeOpts { private: - /// @brief Number of columns returned in the queries used by - /// @ref MySqlHostIPv6Exchange. - static const size_t RESERVATION_COLUMNS = 13; + /// @brief Number of columns holding IPv6 reservation information. + static const size_t RESERVATION_COLUMNS = 5; public: @@ -687,34 +1083,36 @@ public: /// /// Apart from initializing the base class data structures it also /// initializes values representing IPv6 reservation information. - MySqlHostIPv6Exchange() - : MySqlHostExchange(), reserv_type_(0), reserv_type_null_(MLM_FALSE), - ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0) { + MySqlHostIPv6Exchange(const FetchedOptions& fetched_options) + : MySqlHostExchangeOpts(fetched_options, RESERVATION_COLUMNS), + reservation_id_(0), + reserv_type_(0), reserv_type_null_(MLM_FALSE), + ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0), + reservation_id_index_(findAvailColumn()), + address_index_(reservation_id_index_ + 1), + prefix_len_index_(reservation_id_index_ + 2), + type_index_(reservation_id_index_ + 3), + iaid_index_(reservation_id_index_ + 4), + most_recent_reservation_id_(0) { memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_)); // Append additional columns returned by the queries. - columns_.push_back("address"); - columns_.push_back("prefix_len"); - columns_.push_back("type"); - columns_.push_back("dhcp6_iaid"); - - // Resize binding table initialized in the base class. Do not - // run memset on this table, because it is when createBindForReceive - // is called. - bind_.resize(RESERVATION_COLUMNS); - - // Resize error table. - error_.resize(RESERVATION_COLUMNS); - std::fill(&error_[0], &error_[RESERVATION_COLUMNS], MLM_FALSE); + columns_[reservation_id_index_] = "reservation_id"; + columns_[address_index_] = "address"; + columns_[prefix_len_index_] = "prefix_len"; + columns_[type_index_] = "type"; + columns_[iaid_index_] = "dhcp6_iaid"; } - /// @brief Checks if a currently processed row contains IPv6 reservation. + /// @brief Returns last fetched reservation id. /// - /// @return true if IPv6 reservation data is non-null for the processed - /// row, false otherwise. - bool hasReservation() const { - return (reserv_type_null_ == MLM_FALSE); + /// @return Reservation id or 0 if no reservation data is fetched. + uint64_t getReservationId() const { + if (reserv_type_null_ == MLM_FALSE) { + return (reservation_id_); + } + return (0); }; /// @brief Create IPv6 reservation from the data contained in the @@ -767,42 +1165,28 @@ public: /// @param [out] hosts Collection of hosts to which a new host created /// from the processed data should be inserted. virtual void processFetchedData(ConstHostCollection& hosts) { - HostPtr host; - HostPtr most_recent_host; - // If there are any hosts already created, let's obtain an instance - // to the most recently added host. We will have to check if the - // currently processed row contains some data for this host or a - // different host. In the former case, we'll need to update the - // host information. - if (!hosts.empty()) { - // Const cast is not very elegant way to deal with it, but - // there is a good reason to use it here. This method is called - // to build a collection of const hosts to be returned to the - // caller. If we wanted to use non-const collection we'd need - // to copy the whole collection before returning it, which has - // performance implications. Alternatively, we could store the - // most recently added host in a class member but this would - // make the code less readable. - most_recent_host = boost::const_pointer_cast(hosts.back()); + // Call parent class to fetch host information and options. + MySqlHostExchangeOpts::processFetchedData(hosts); + + if (getReservationId() == 0) { + return; } - // If there is no existing host or the new host id doesn't match - // we need to create a new host. - if (!most_recent_host || (most_recent_host->getHostId() != getHostId())) { - host = retrieveHost(); - // If the row also contains IPv6 reservation we should add it - // to the host. - if (hasReservation()) { + if (hosts.empty()) { + isc_throw(Unexpected, "no host information while retrieving" + " IPv6 reservation"); + } + HostPtr host = boost::const_pointer_cast(hosts.back()); + + // If we're dealing with a new reservation, let's add it to the + // host. + if (getReservationId() > most_recent_reservation_id_) { + most_recent_reservation_id_ = getReservationId(); + + if (most_recent_reservation_id_ > 0) { host->addReservation(retrieveReservation()); } - // In any case let's put the new host in the results. - hosts.push_back(host); - - // If the returned row pertains to an existing host, let's just - // add a reservation. - } else if (hasReservation() && most_recent_host) { - most_recent_host->addReservation(retrieveReservation()); } } @@ -815,32 +1199,41 @@ public: /// /// @return Vector of MYSQL_BIND objects representing data to be retrieved. virtual std::vector createBindForReceive() { + // Reset most recent reservation id value because we're now making + // a new SELECT query. + most_recent_reservation_id_ = 0; + // The following call sets bind_ values between 0 and 8. - static_cast(MySqlHostExchange::createBindForReceive()); + static_cast(MySqlHostExchangeOpts::createBindForReceive()); + + // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT + bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG; + bind_[reservation_id_index_].buffer = reinterpret_cast(&reservation_id_); + bind_[reservation_id_index_].is_unsigned = MLM_TRUE; // IPv6 address/prefix VARCHAR(39) ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1; - bind_[9].buffer_type = MYSQL_TYPE_STRING; - bind_[9].buffer = ipv6_address_buffer_; - bind_[9].buffer_length = ipv6_address_buffer_len_; - bind_[9].length = &ipv6_address_buffer_len_; + bind_[address_index_].buffer_type = MYSQL_TYPE_STRING; + bind_[address_index_].buffer = ipv6_address_buffer_; + bind_[address_index_].buffer_length = ipv6_address_buffer_len_; + bind_[address_index_].length = &ipv6_address_buffer_len_; // prefix_len : TINYINT - bind_[10].buffer_type = MYSQL_TYPE_TINY; - bind_[10].buffer = reinterpret_cast(&prefix_len_); - bind_[10].is_unsigned = MLM_TRUE; + bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY; + bind_[prefix_len_index_].buffer = reinterpret_cast(&prefix_len_); + bind_[prefix_len_index_].is_unsigned = MLM_TRUE; // (reservation) type : TINYINT reserv_type_null_ = MLM_FALSE; - bind_[11].buffer_type = MYSQL_TYPE_TINY; - bind_[11].buffer = reinterpret_cast(&reserv_type_); - bind_[11].is_unsigned = MLM_TRUE; - bind_[11].is_null = &reserv_type_null_; + bind_[type_index_].buffer_type = MYSQL_TYPE_TINY; + bind_[type_index_].buffer = reinterpret_cast(&reserv_type_); + bind_[type_index_].is_unsigned = MLM_TRUE; + bind_[type_index_].is_null = &reserv_type_null_; // dhcp6_iaid INT UNSIGNED - bind_[12].buffer_type = MYSQL_TYPE_LONG; - bind_[12].buffer = reinterpret_cast(&iaid_); - bind_[12].is_unsigned = MLM_TRUE; + bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG; + bind_[iaid_index_].buffer = reinterpret_cast(&iaid_); + bind_[iaid_index_].is_unsigned = MLM_TRUE; // Add the error flags setErrorIndicators(bind_, error_); @@ -852,6 +1245,9 @@ public: private: + /// @brief IPv6 reservation id. + uint64_t reservation_id_; + /// @brief IPv6 reservation type. uint8_t reserv_type_; @@ -873,6 +1269,28 @@ private: /// @brief IAID. uint8_t iaid_; + /// @name Indexes of columns holding information about IPv6 reservations. + //@{ + /// @brief Index of reservation_id column. + size_t reservation_id_index_; + + /// @brief Index of address column. + size_t address_index_; + + /// @brief Index of prefix_len column. + size_t prefix_len_index_; + + /// @brief Index of type column. + size_t type_index_; + + /// @brief Index of IAID column. + size_t iaid_index_; + + //@} + + /// @brief Reservation id for last processed row. + uint64_t most_recent_reservation_id_; + }; /// @brief This class is used for storing IPv6 reservations in a MySQL database. @@ -1021,6 +1439,141 @@ private: my_bool error_[RESRV_COLUMNS]; }; +class MySqlOptionExchange { +private: + + static const size_t OPTION_COLUMNS = 8; + +public: + + MySqlOptionExchange() + : type_(0), value_len_(0), space_(), space_len_(0), + persistent_(false), client_class_(), client_class_len_(0), + subnet_id_(0), host_id_(0), option_() { + + BOOST_STATIC_ASSERT(7 < OPTION_COLUMNS); + } + + + std::vector + createBindForSend(const OptionDescriptor& opt_desc, + const std::string& opt_space, + const OptionalValue& subnet_id, + const HostID& host_id) { + + // Hold pointer to the option to make sure it remains valid until + // we make a query. + option_ = opt_desc.option_; + + memset(bind_, 0, sizeof(bind_)); + + try { + // option_id: INT UNSIGNED NOT NULL + // The option_id is auto_incremented, so we need to pass the NULL + // value. + bind_[0].buffer_type = MYSQL_TYPE_NULL; + + // code: SMALLINT UNSIGNED NOT NULL + type_ = option_->getType(); + bind_[1].buffer_type = MYSQL_TYPE_SHORT; + bind_[1].buffer = reinterpret_cast(&type_); + bind_[1].is_unsigned = MLM_TRUE; + + // value: BLOB NULL + OutputBuffer buf(opt_desc.option_->len()); + opt_desc.option_->pack(buf); + if (buf.getLength() > opt_desc.option_->getHeaderLen()) { + const char* buf_ptr = static_cast(buf.getData()); + value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(), + buf_ptr + buf.getLength()); + value_len_ = value_.size(); + bind_[2].buffer_type = MYSQL_TYPE_BLOB; + bind_[2].buffer = &value_[0]; + bind_[2].buffer_length = value_len_; + bind_[2].length = &value_len_; + + } else { + value_.clear(); + bind_[2].buffer_type = MYSQL_TYPE_NULL; + } + + // space: VARCHAR(128) NULL + space_ = opt_space; + space_len_ = space_.size(); + bind_[3].buffer_type = MYSQL_TYPE_STRING; + bind_[3].buffer = const_cast(space_.c_str()); + bind_[3].buffer_length = space_len_; + bind_[3].length = &space_len_; + + // persistent: TINYINT(1) NOT NULL DEFAULT 0 + persistent_ = opt_desc.persistent_; + bind_[4].buffer_type = MYSQL_TYPE_TINY; + bind_[4].buffer = reinterpret_cast(&persistent_); + bind_[4].is_unsigned = MLM_TRUE; + + // dhcp_client_class: VARCHAR(128) NULL + /// @todo Assign actual value to client class string. + client_class_len_ = client_class_.size(); + bind_[5].buffer_type = MYSQL_TYPE_STRING; + bind_[5].buffer = const_cast(client_class_.c_str()); + bind_[5].buffer_length = client_class_len_; + bind_[5].length = &client_class_len_; + + // dhcp4_subnet_id: INT UNSIGNED NULL + if (subnet_id.isSpecified()) { + subnet_id_ = subnet_id; + bind_[6].buffer_type = MYSQL_TYPE_LONG; + bind_[6].buffer = reinterpret_cast(subnet_id_); + bind_[6].is_unsigned = MLM_TRUE; + + } else { + bind_[6].buffer_type = MYSQL_TYPE_NULL; + } + + // host_id: INT UNSIGNED NOT NULL + host_id_ = host_id; + bind_[7].buffer_type = MYSQL_TYPE_LONG; + bind_[7].buffer = reinterpret_cast(&host_id_); + bind_[7].is_unsigned = MLM_TRUE; + + } catch (const std::exception& ex) { + isc_throw(DbOperationError, + "Could not create bind array for inserting DHCP " + "option: " << option_->toText() << ", reason: " + << ex.what()); + } + + return (std::vector(&bind_[0], &bind_[OPTION_COLUMNS])); + } + +private: + + uint16_t type_; + + std::vector value_; + + size_t value_len_; + + std::string space_; + + size_t space_len_; + + bool persistent_; + + std::string client_class_; + + size_t client_class_len_; + + uint32_t subnet_id_; + + uint32_t host_id_; + + OptionPtr option_; + + MYSQL_BIND bind_[OPTION_COLUMNS]; + +}; + } // end of anonymous namespace namespace isc { @@ -1055,6 +1608,16 @@ public: /// @param id ID of a host owning this reservation void addResv(const IPv6Resrv& resv, const HostID& id); + void addOption(const MySqlHostDataSource::StatementIndex& stindex, + const OptionDescriptor& opt_desc, + const std::string& opt_space, + const OptionalValue& subnet_id, + const HostID& id); + + void addOptions(const MySqlHostDataSource::StatementIndex& stindex, + const ConstCfgOptionPtr& options_cfg, + uint64_t& host_id); + /// @brief Check Error and Throw Exception /// /// Virtually all MySQL functions return a status which, if non-zero, @@ -1117,16 +1680,25 @@ public: /// @brief Pointer to the object representing an exchange which /// can be used to retrieve DHCPv4 reservation. - boost::shared_ptr host_exchange_; + boost::shared_ptr host_exchange_; /// @brief Pointer to an object representing an exchange which can /// be used to retrieve DHCPv6 reservations. boost::shared_ptr host_ipv6_exchange_; + /// @brief Pointer to an object representing an exchange which can + /// be used to retrieve DHCPv4 and DHCPv6 reservations using a + /// single query. + boost::shared_ptr host_ipv46_exchange_; + /// @brief Pointer to an object representing an exchange which can /// be used to insert new IPv6 reservation. boost::shared_ptr host_ipv6_reservation_exchange_; + /// @brief Pointer to an object representing an exchange which can + /// be used to insert DHCPv4 option into 'dhcp4_options' table. + boost::shared_ptr host_ipv4_option_exchange_; + /// @brief MySQL connection MySqlConnection conn_; @@ -1134,9 +1706,12 @@ public: MySqlHostDataSourceImpl:: MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters) - : host_exchange_(new MySqlHostExchange()), - host_ipv6_exchange_(new MySqlHostIPv6Exchange()), + : host_exchange_(new MySqlHostExchangeOpts(MySqlHostExchangeOpts::DHCP4_ONLY)), + host_ipv6_exchange_(new MySqlHostIPv6Exchange(MySqlHostExchangeOpts::DHCP6_ONLY)), + host_ipv46_exchange_(new MySqlHostIPv6Exchange(MySqlHostExchangeOpts:: + DHCP4_AND_DHCP6)), host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()), + host_ipv4_option_exchange_(new MySqlOptionExchange()), conn_(parameters) { // Open the database. @@ -1182,6 +1757,7 @@ MySqlHostDataSourceImpl::addQuery(MySqlHostDataSource::StatementIndex stindex, // Execute the statement status = mysql_stmt_execute(conn_.statements_[stindex]); + if (status != 0) { // Failure: check for the special case of duplicate entry. if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) { @@ -1200,6 +1776,46 @@ MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv, addQuery(MySqlHostDataSource::INSERT_V6_RESRV, bind); } +void +MySqlHostDataSourceImpl::addOption(const MySqlHostDataSource::StatementIndex& stindex, + const OptionDescriptor& opt_desc, + const std::string& opt_space, + const OptionalValue& subnet_id, + const HostID& id) { + std::vector bind = + host_ipv4_option_exchange_->createBindForSend(opt_desc, opt_space, + subnet_id, id); + + addQuery(stindex, bind); +} + +void +MySqlHostDataSourceImpl::addOptions(const MySqlHostDataSource::StatementIndex& stindex, + const ConstCfgOptionPtr& options_cfg, + uint64_t& host_id) { + std::list option_spaces = options_cfg->getOptionSpaceNames(); + std::list vendor_spaces = options_cfg->getVendorIdsSpaceNames(); + option_spaces.insert(option_spaces.end(), vendor_spaces.begin(), + vendor_spaces.end()); + + // Retrieve host id only if there are any options to be added and the + // host id hasn't been retrieved yet. + if ((host_id == 0) && !option_spaces.empty()) { + host_id = mysql_insert_id(conn_.mysql_); + } + for (std::list::const_iterator space = option_spaces.begin(); + space != option_spaces.end(); ++space) { + OptionContainerPtr options = options_cfg->getAll(*space); + if (options && !options->empty()) { + for (OptionContainer::const_iterator opt = options->begin(); + opt != options->end(); ++opt) { + addOption(stindex, *opt, *space, OptionalValue(), + host_id); + } + } + } +} + void MySqlHostDataSourceImpl:: checkError(const int status, const MySqlHostDataSource::StatementIndex index, @@ -1336,17 +1952,26 @@ MySqlHostDataSource::add(const HostPtr& host) { // ... and call addHost() code. impl_->addQuery(INSERT_HOST, bind); + // Gets the last inserted hosts id + uint64_t host_id = 0; + IPv6ResrvRange v6resv = host->getIPv6Reservations(); - if (std::distance(v6resv.first, v6resv.second) == 0) { - // If there are no v6 reservations, we're done here. - return; + if (std::distance(v6resv.first, v6resv.second) > 0) { + host_id = mysql_insert_id(impl_->conn_.mysql_); + for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second; + ++resv) { + impl_->addResv(resv->second, host_id); + } } - // Gets the last inserted hosts id - uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_); - for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second; - ++resv) { - impl_->addResv(resv->second, host_id); + ConstCfgOptionPtr cfg_option4 = host->getCfgOption4(); + if (cfg_option4) { + impl_->addOptions(INSERT_V4_OPTION, cfg_option4, host_id); + } + + ConstCfgOptionPtr cfg_option6 = host->getCfgOption6(); + if (cfg_option6) { + impl_->addOptions(INSERT_V6_OPTION, cfg_option6, host_id); } } @@ -1392,7 +2017,7 @@ MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type, ConstHostCollection result; impl_->getHostCollection(GET_HOST_DHCPID, inbind, - impl_->host_ipv6_exchange_, + impl_->host_ipv46_exchange_, result, false); return (result); } diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 665dea2073..93bd49f860 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -257,6 +257,8 @@ public: enum StatementIndex { INSERT_HOST, // Insert new host to collection INSERT_V6_RESRV, // Insert v6 reservation + INSERT_V4_OPTION, // Insert DHCPv4 option + INSERT_V6_OPTION, // Insert DHCPv6 option GET_HOST_DHCPID, // Gets hosts by host identifier GET_HOST_ADDR, // Gets hosts by IPv4 address GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index b14fd0f519..cc20213de2 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -551,7 +551,7 @@ OptionDataParser::findOptionDefinition(const std::string& option_space, if (!def) { // Check if this is a vendor-option. If it is, get vendor-specific // definition. - uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space); + uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space); if (vendor_id) { def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key); } diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index d0e42e8965..adaccf4da2 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include using namespace isc; @@ -490,5 +492,34 @@ TEST(CfgOptionTest, addVendorOptions) { EXPECT_TRUE(options->empty()); } +// This test verifies that option space names for the vendor options are +// correct. +TEST(CfgOptionTest, getVendorIdsSpaceNames) { + CfgOption cfg; + + // Create 10 options, each goes under a different vendor id. + for (uint16_t code = 100; code < 110; ++code) { + OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF))); + // Generate space name for a unique vendor id. + std::ostringstream s; + s << "vendor-" << code; + ASSERT_NO_THROW(cfg.add(option, false, s.str())); + } + + // We should now have 10 different vendor ids. + std::list space_names = cfg.getVendorIdsSpaceNames(); + ASSERT_EQ(10, space_names.size()); + + // Check that the option space names for those vendor ids are correct. + for (std::list::iterator name = space_names.begin(); + name != space_names.end(); ++name) { + uint16_t id = static_cast(std::distance(space_names.begin(), + name)); + std::ostringstream s; + s << "vendor-" << (100 + id); + EXPECT_EQ(s.str(), *name); + } +} + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc index 01b5316a0f..992a9ef957 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc @@ -4,15 +4,30 @@ // 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 +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include #include +#include +#include +#include #include +#include using namespace std; using namespace isc::asiolink; +using namespace isc::util; namespace isc { namespace dhcp { @@ -20,10 +35,11 @@ namespace test { GenericHostDataSourceTest::GenericHostDataSourceTest() :hdsptr_() { - + LibDHCP::clearRuntimeOptionDefs(); } GenericHostDataSourceTest::~GenericHostDataSourceTest() { + LibDHCP::clearRuntimeOptionDefs(); } std::vector @@ -229,12 +245,15 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1, compareReservations6(host1->getIPv6Reservations(), host2->getIPv6Reservations()); - // And compare client classification details + // Compare client classification details compareClientClasses(host1->getClientClasses4(), host2->getClientClasses4()); compareClientClasses(host1->getClientClasses6(), host2->getClientClasses6()); + + compareOptions(host1->getCfgOption4(), host2->getCfgOption4()); + compareOptions(host1->getCfgOption6(), host2->getCfgOption6()); } DuidPtr @@ -313,6 +332,125 @@ GenericHostDataSourceTest::compareClientClasses(const ClientClasses& /*classes1* /// This is part of the work for #4213. } +void +GenericHostDataSourceTest::compareOptions(const ConstCfgOptionPtr& cfg1, + const ConstCfgOptionPtr& cfg2) const { + ASSERT_TRUE(cfg1); + ASSERT_TRUE(cfg2); + + std::list option_spaces = cfg2->getOptionSpaceNames(); + std::list vendor_spaces = cfg2->getVendorIdsSpaceNames(); + option_spaces.insert(option_spaces.end(), vendor_spaces.begin(), + vendor_spaces.end()); + + BOOST_FOREACH(std::string space, option_spaces) { + OptionContainerPtr options1 = cfg1->getAll(space); + OptionContainerPtr options2 = cfg2->getAll(space); + ASSERT_TRUE(options1); + ASSERT_TRUE(options2); + + ASSERT_EQ(options1->size(), options2->size()); + + BOOST_FOREACH(OptionDescriptor desc1, *options1) { + OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType()); + ASSERT_EQ(desc1.persistent_, desc2.persistent_); + Option* option1 = desc1.option_.get(); + Option* option2 = desc2.option_.get(); + + ASSERT_TRUE(typeid(*option1) == typeid(*option2)) + << "Comapared DHCP options, having option code " + << desc1.option_->getType() << " and belonging to the " + << space << " option space, are represented " + "by different C++ classes: " + << typeid(*option1).name() << " vs " + << typeid(*option2).name(); + + OutputBuffer buf1(option1->len()); + ASSERT_NO_THROW(option1->pack(buf1)); + OutputBuffer buf2(option2->len()); + ASSERT_NO_THROW(option2->pack(buf2)); + + ASSERT_EQ(buf1.getLength(), buf2.getLength()); + ASSERT_EQ(0, memcmp(buf1.getData(), buf2.getData(), buf1.getLength())); + } + } +} + + +OptionDescriptor +GenericHostDataSourceTest::createVendorOption(const Option::Universe& universe, + const bool persist, + const uint32_t vendor_id) const { + OptionVendorPtr option(new OptionVendor(universe, vendor_id)); + OptionDescriptor desc(option, persist); + return (desc); +} + +void +GenericHostDataSourceTest::addTestOptions(const HostPtr& host) const { + // Add DHCPv4 options. + CfgOptionPtr opts = host->getCfgOption4(); + opts->add(createOption(Option::V4, DHO_BOOT_FILE_NAME, + true, "my-boot-file"), + DHCP4_OPTION_SPACE); + opts->add(createOption(Option::V4, DHO_DEFAULT_IP_TTL, + false, 64), + DHCP4_OPTION_SPACE); + opts->add(createOption(Option::V4, 1, false, 312131), + "vendor-encapsulated-options"); + opts->add(createAddressOption(254, false, "192.0.2.3"), + DHCP4_OPTION_SPACE); + opts->add(createOption