bool
OptionDescriptor::equals(const OptionDescriptor& other) const {
return (persistent_ == other.persistent_ &&
+ formatted_value_ == other.formatted_value_ &&
option_->equals(other.option_));
}
/// to DHCP client only on request (persistent = false) or always
/// (persistent = true).
struct OptionDescriptor {
- /// Option instance.
+ /// @brief Option instance.
OptionPtr option_;
- /// Persistent flag, if true option is always sent to the client,
- /// if false option is sent to the client on request.
+
+ /// @briefPersistence flag.
+ ///
+ /// If true option is always sent to the client, if false option is
+ /// sent to the client on request.
bool persistent_;
+ /// @brief Option value in textual (CSV) format.
+ ///
+ /// This field is used to convey option value in human readable format,
+ /// the same as used to specify option value in the server configuration.
+ /// This value is optional and can be held in the host reservations
+ /// database instead of the binary format.
+ ///
+ /// Note that this value is carried in the option descriptor, rather than
+ /// @c Option instance because it is a server specific value.
+ ///
+ /// An example of the formatted value is: 2001:db8:1::1, 23, some text
+ /// for the option which carries IPv6 address, a number and a text.
+ std::string formatted_value_;
+
/// @brief Constructor.
///
/// @param opt option
/// @param persist if true option is always sent.
- OptionDescriptor(const OptionPtr& opt, bool persist)
- : option_(opt), persistent_(persist) {};
+ /// @param formatted_value option value in the textual format. Default
+ /// value is empty indicating that the value is not set.
+ OptionDescriptor(const OptionPtr& opt, bool persist,
+ const std::string& formatted_value = "")
+ : option_(opt), persistent_(persist),
+ formatted_value_(formatted_value) {};
/// @brief Constructor
///
/// @param persist if true option is always sent.
OptionDescriptor(bool persist)
- : option_(OptionPtr()), persistent_(persist) {};
+ : option_(OptionPtr()), persistent_(persist),
+ formatted_value_() {};
/// @brief Checks if the one descriptor is equal to another.
///
#include <util/buffer.h>
#include <util/optional_value.h>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
#include <boost/pointer_cast.hpp>
#include <boost/static_assert.hpp>
// 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 (?, ?, ?, ?, ?, ?, ?, ?)"},
+ "INSERT INTO dhcp4_options(option_id, code, value, formatted_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 (?, ?, ?, ?, ?, ?, ?, ?)"},
+ "INSERT INTO dhcp6_options(option_id, code, value, formatted_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
if (most_recent_option_id_ < option_id_) {
most_recent_option_id_ = option_id_;
- space_[space_length_] = '\0';
- std::string space(space_);
+ std::string space;
+ if (space_null_ == MLM_FALSE) {
+ space_[space_length_] = '\0';
+ space.assign(space_);
+ }
+
+ std::string formatted_value;
+ if (formatted_value_null_ == MLM_FALSE) {
+ formatted_value_[formatted_value_length_] = '\0';
+ formatted_value.assign(formatted_value_);
+ }
OptionDefinitionPtr def;
if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
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 {
+
+ if (!def) {
+ OptionBuffer buf(value_, value_ + value_length_);
option.reset(new Option(universe_, code_, buf.begin(),
buf.end()));
+ } else {
+ if (formatted_value.empty()) {
+ OptionBuffer buf(value_, value_ + value_length_);
+ option = def->optionFactory(universe_, code_, buf.begin(),
+ buf.end());
+ } else {
+ std::vector<std::string> split_vec;
+ boost::split(split_vec, formatted_value, boost::is_any_of(","));
+ option = def->optionFactory(universe_, code_, split_vec);
+ }
}
- OptionDescriptor desc(option, persistent_);
+ OptionDescriptor desc(option, persistent_, formatted_value);
cfg->add(desc, space);
}
}
class MySqlOptionExchange {
private:
- static const size_t OPTION_COLUMNS = 8;
+ static const size_t OPTION_COLUMNS = 9;
public:
MySqlOptionExchange()
- : type_(0), value_len_(0), space_(), space_len_(0),
+ : type_(0), value_len_(0), formatted_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);
+ BOOST_STATIC_ASSERT(8 < OPTION_COLUMNS);
}
// value: BLOB NULL
OutputBuffer buf(opt_desc.option_->len());
opt_desc.option_->pack(buf);
- if (buf.getLength() > opt_desc.option_->getHeaderLen()) {
+ if ((buf.getLength() > opt_desc.option_->getHeaderLen()) &&
+ opt_desc.formatted_value_.empty()) {
const char* buf_ptr = static_cast<const char*>(buf.getData());
value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
buf_ptr + buf.getLength());
bind_[2].buffer_type = MYSQL_TYPE_NULL;
}
+ // formatted_value: TEXT NULL,
+ if (!opt_desc.formatted_value_.empty()) {
+ formatted_value_len_ = opt_desc.formatted_value_.size();
+ bind_[3].buffer_type = MYSQL_TYPE_STRING;
+ bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
+ bind_[3].buffer_length = formatted_value_len_;
+ bind_[3].length = &formatted_value_len_;
+
+ } else {
+ bind_[3].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<char*>(space_.c_str());
- bind_[3].buffer_length = space_len_;
- bind_[3].length = &space_len_;
+ bind_[4].buffer_type = MYSQL_TYPE_STRING;
+ bind_[4].buffer = const_cast<char*>(space_.c_str());
+ bind_[4].buffer_length = space_len_;
+ bind_[4].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<char*>(&persistent_);
- bind_[4].is_unsigned = MLM_TRUE;
+ bind_[5].buffer_type = MYSQL_TYPE_TINY;
+ bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
+ bind_[5].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<char*>(client_class_.c_str());
- bind_[5].buffer_length = client_class_len_;
- bind_[5].length = &client_class_len_;
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = const_cast<char*>(client_class_.c_str());
+ bind_[6].buffer_length = client_class_len_;
+ bind_[6].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<char*>(subnet_id_);
- bind_[6].is_unsigned = MLM_TRUE;
+ bind_[7].buffer_type = MYSQL_TYPE_LONG;
+ bind_[7].buffer = reinterpret_cast<char*>(subnet_id_);
+ bind_[7].is_unsigned = MLM_TRUE;
} else {
- bind_[6].buffer_type = MYSQL_TYPE_NULL;
+ bind_[7].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<char*>(&host_id_);
- bind_[7].is_unsigned = MLM_TRUE;
+ bind_[8].buffer_type = MYSQL_TYPE_LONG;
+ bind_[8].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[8].is_unsigned = MLM_TRUE;
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
size_t value_len_;
+ /// Formatted option value length.
+ unsigned long formatted_value_len_;
+
std::string space_;
size_t space_len_;
BOOST_FOREACH(OptionDescriptor desc1, *options1) {
OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType());
ASSERT_EQ(desc1.persistent_, desc2.persistent_);
+ ASSERT_EQ(desc1.formatted_value_, desc2.formatted_value_);
Option* option1 = desc1.option_.get();
Option* option2 = desc2.option_.get();
}
}
+OptionDescriptor
+GenericHostDataSourceTest::createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist) const {
+ OptionPtr option(new Option(universe, option_type));
+ OptionDescriptor desc(option, persist);
+ return (desc);
+}
+
OptionDescriptor
GenericHostDataSourceTest::createVendorOption(const Option::Universe& universe,
const bool persist,
+ const bool formatted,
const uint32_t vendor_id) const {
OptionVendorPtr option(new OptionVendor(universe, vendor_id));
- OptionDescriptor desc(option, persist);
+
+ std::ostringstream s;
+ if (formatted) {
+ s << vendor_id;
+ }
+
+ OptionDescriptor desc(option, persist, s.str());
return (desc);
}
void
-GenericHostDataSourceTest::addTestOptions(const HostPtr& host) const {
+GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
+ const bool formatted) const {
// Add DHCPv4 options.
CfgOptionPtr opts = host->getCfgOption4();
opts->add(createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
- true, "my-boot-file"),
+ true, formatted, "my-boot-file"),
DHCP4_OPTION_SPACE);
opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
- false, 64),
+ false, formatted, 64),
DHCP4_OPTION_SPACE);
- opts->add(createOption<OptionUint32>(Option::V4, 1, false, 312131),
+ opts->add(createOption<OptionUint32>(Option::V4, 1, false, formatted, 312131),
"vendor-encapsulated-options");
- opts->add(createAddressOption<Option4AddrLst>(254, false, "192.0.2.3"),
+ opts->add(createAddressOption<Option4AddrLst>(254, false, formatted, "192.0.2.3"),
DHCP4_OPTION_SPACE);
- opts->add(createOption<Option>(Option::V4, 1, true, OptionBuffer()),
- "isc");
- opts->add(createAddressOption<Option4AddrLst>(2, false, "10.0.0.5",
+ opts->add(createEmptyOption(Option::V4, 1, true), "isc");
+ opts->add(createAddressOption<Option4AddrLst>(2, false, formatted, "10.0.0.5",
"10.0.0.3", "10.0.3.4"),
"isc");
// Add DHCPv6 options.
opts = host->getCfgOption6();
opts->add(createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
- true, "my-boot-file"),
+ true, formatted, "my-boot-file"),
DHCP6_OPTION_SPACE);
opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
- false, 3600),
+ false, formatted, 3600),
DHCP6_OPTION_SPACE);
- opts->add(createVendorOption(Option::V6, false, 2495), DHCP6_OPTION_SPACE);
- opts->add(createAddressOption<Option6AddrLst>(1024, false, "2001:db8:1::1"),
+ opts->add(createVendorOption(Option::V6, false, formatted, 2495),
DHCP6_OPTION_SPACE);
- opts->add(createOption<Option>(Option::V6, 1, true, OptionBuffer()),
- "isc2");
- opts->add(createAddressOption<Option6AddrLst>(2, false, "3000::1", "3000::2",
- "3000::3"),
+ opts->add(createAddressOption<Option6AddrLst>(1024, false, formatted,
+ "2001:db8:1::1"),
+ DHCP6_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V6, 1, true), "isc2");
+ opts->add(createAddressOption<Option6AddrLst>(2, false, formatted, "3000::1",
+ "3000::2", "3000::3"),
"isc2");
// Add definitions for DHCPv6 non-standard options.
}
-void GenericHostDataSourceTest::testOptionsReservations4() {
+void GenericHostDataSourceTest::testOptionsReservations4(const bool formatted) {
HostPtr host = initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
// Add a bunch of DHCPv4 and DHCPv6 options for the host.
- ASSERT_NO_THROW(addTestOptions(host));
+ ASSERT_NO_THROW(addTestOptions(host, formatted));
// Insert host and the options into respective tables.
ASSERT_NO_THROW(hdsptr_->add(host));
// Subnet id will be used in quries to the database.
hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
}
-void GenericHostDataSourceTest::testOptionsReservations6() {
+void GenericHostDataSourceTest::testOptionsReservations6(const bool formatted) {
HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
// Add a bunch of DHCPv4 and DHCPv6 options for the host.
- ASSERT_NO_THROW(addTestOptions(host));
+ ASSERT_NO_THROW(addTestOptions(host, formatted));
// Insert host, options and IPv6 reservations into respective tables.
ASSERT_NO_THROW(hdsptr_->add(host));
// Subnet id will be used in queries to the database.
ASSERT_NO_FATAL_FAILURE(compareHosts(host, host_by_addr));
}
-void GenericHostDataSourceTest::testOptionsReservations46() {
+void GenericHostDataSourceTest::testOptionsReservations46(const bool formatted) {
HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
// Add a bunch of DHCPv4 and DHCPv6 options for the host.
- ASSERT_NO_THROW(addTestOptions(host));
+ ASSERT_NO_THROW(addTestOptions(host, formatted));
// Insert host, options and IPv6 reservations into respective tables.
ASSERT_NO_THROW(hdsptr_->add(host));
// Subnet id will be used in queries to the database.
#include <dhcpsrv/host.h>
#include <dhcp/classify.h>
#include <dhcp/option.h>
+#include <boost/algorithm/string/join.hpp>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
+#include <sstream>
#include <vector>
namespace isc {
void compareOptions(const ConstCfgOptionPtr& cfg1,
const ConstCfgOptionPtr& cfg2) const;
+ OptionDescriptor createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist) const;
+
/// @brief Creates an instance of the option for which it is possible to
/// specify universe, option type and value in the constructor.
///
OptionDescriptor createOption(const Option::Universe& universe,
const uint16_t option_type,
const bool persist,
+ const bool formatted,
const DataType& value) const {
boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
value));
- OptionDescriptor desc(option, persist);
+ std::ostringstream s;
+ if (formatted) {
+ s << value;
+ }
+ OptionDescriptor desc(option, persist, s.str());
return (desc);
}
template<typename OptionType, typename DataType>
OptionDescriptor createOption(const uint16_t option_type,
const bool persist,
+ const bool formatted,
const DataType& value) const {
boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
- OptionDescriptor desc(option, persist);
+
+ std::ostringstream s;
+ if (formatted) {
+ s << value;
+ }
+
+ OptionDescriptor desc(option, persist, s.str());
return (desc);
}
OptionDescriptor
createAddressOption(const uint16_t option_type,
const bool persist,
+ const bool formatted,
const std::string& address1 = "",
const std::string& address2 = "",
const std::string& address3 = "") const {
+ std::ostringstream s;
typename OptionType::AddressContainer addresses;
if (!address1.empty()) {
addresses.push_back(asiolink::IOAddress(address1));
+ if (formatted) {
+ s << address1;
+ }
}
if (!address2.empty()) {
addresses.push_back(asiolink::IOAddress(address2));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address2;
+ }
}
if (!address3.empty()) {
addresses.push_back(asiolink::IOAddress(address3));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address3;
+ }
}
+
boost::shared_ptr<OptionType> option(new OptionType(option_type, addresses));
- OptionDescriptor desc(option, persist);
+ OptionDescriptor desc(option, persist, s.str());
return (desc);
}
OptionDescriptor createVendorOption(const Option::Universe& universe,
const bool persist,
+ const bool formatted,
const uint32_t vendor_id) const;
- void addTestOptions(const HostPtr& host) const;
+ void addTestOptions(const HostPtr& host, const bool formatted) const;
/// @brief Pointer to the host data source
HostDataSourcePtr hdsptr_;
/// the database.
///
/// Uses gtest macros to report failures.
- void testOptionsReservations4();
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ void testOptionsReservations4(const bool formatted);
/// @brief Test that DHCPv6 options can be inserted and retrieved from
/// the database.
///
/// Uses gtest macros to report failures.
- void testOptionsReservations6();
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ void testOptionsReservations6(const bool formatted);
/// @brief Test that DHCPv4 and DHCPv6 options can be inserted and retrieved
/// with a single query to the database.
///
/// Uses gtest macros to report failures.
- void testOptionsReservations46();
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ void testOptionsReservations46(const bool formatted);
/// @brief Returns DUID with identical content as specified HW address
///
testAddDuplicate4();
}
-// This test verifies that DHCPv4 options can be inserted and retrieved from
-// the MySQL host database.
+// This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
TEST_F(MySqlHostDataSourceTest, optionsReservations4) {
- testOptionsReservations4();
+ testOptionsReservations4(false);
}
-// This test verifies that DHCPv6 options can be inserted and retrieved from
-// the MySQL host database.
+// This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
TEST_F(MySqlHostDataSourceTest, optionsReservations6) {
- testOptionsReservations6();
+ testOptionsReservations6(false);
}
-// This test verifies that DHCPv4 and DHCPv6 options can be inserted and
-// retrieved with a single query to the database.
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
TEST_F(MySqlHostDataSourceTest, optionsReservations46) {
- testOptionsReservations46();
+ testOptionsReservations46(false);
}
+// This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations4) {
+ testOptionsReservations4(true);
+}
+
+// This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations6) {
+ testOptionsReservations6(true);
+}
+
+// This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// textual format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations46) {
+ testOptionsReservations46(true);
+}
}; // Of anonymous namespace