Commit 5f33dc7d authored by Marcin Siodelski's avatar Marcin Siodelski

[#93,!51] Added options management in MySQL DHCPv4 config backend.

parent 57c4fa44
This diff is collapsed.
......@@ -169,7 +169,18 @@ public:
/// @param option Option to be added or updated.
virtual void
createUpdateOption4(const db::ServerSelector& server_selector,
const OptionPtr& option);
const OptionDescriptorPtr& option);
/// @brief Creates or updates shared network level option.
///
/// @param selector Server selector.
/// @param shared_network_name Name of a shared network to which option
/// belongs.
/// @param option Option to be added or updated.
virtual void
createUpdateOption4(const db::ServerSelector& selector,
const std::string& shared_network_name,
const OptionDescriptorPtr& option);
/// @brief Creates or updates subnet level option.
///
......@@ -179,7 +190,7 @@ public:
virtual void
createUpdateOption4(const db::ServerSelector& server_selector,
const SubnetID& subnet_id,
const OptionPtr& option);
const OptionDescriptorPtr& option);
/// @brief Creates or updates pool level option.
///
......@@ -193,7 +204,7 @@ public:
createUpdateOption4(const db::ServerSelector& server_selector,
const asiolink::IOAddress& pool_start_address,
const asiolink::IOAddress& pool_end_address,
const OptionPtr& option);
const OptionDescriptorPtr& option);
/// @brief Creates or updates global string parameter.
///
......@@ -282,6 +293,19 @@ public:
deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
const std::string& space);
/// @brief Deletes shared network level option.
///
/// @param selector Server selector.
/// @param shared_network_name Name of the shared network which deleted
/// option belongs to
/// @param code Code of the deleted option.
/// @param space Option space of the deleted option.
virtual void
deleteOption4(const db::ServerSelector& selector,
const std::string& shared_network_name,
const uint16_t code,
const std::string& space);
/// @brief Deletes subnet level option.
///
/// @param server_selector Server selector.
......
......@@ -6,6 +6,11 @@
#include <mysql_cb_impl.h>
#include <asiolink/io_address.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_space.h>
#include <util/buffer.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <mysql.h>
#include <mysqld_error.h>
#include <cstdint>
......@@ -13,6 +18,7 @@
using namespace isc::data;
using namespace isc::db;
using namespace isc::util;
namespace isc {
namespace dhcp {
......@@ -162,6 +168,100 @@ MySqlConfigBackendImpl::getOptionDefs(const int index,
});
}
void
MySqlConfigBackendImpl::getOptions(const int index,
const db::MySqlBindingCollection& in_bindings,
const Option::Universe& universe,
OptionContainer& options) {
// Create output bindings. The order must match that in the prepared
// statement.
MySqlBindingCollection out_bindings = {
MySqlBinding::createInteger<uint64_t>(), // option_id
MySqlBinding::createInteger<uint8_t>(), // code
MySqlBinding::createBlob(65536), // value
MySqlBinding::createString(8192), // formatted_value
MySqlBinding::createString(128), // space
MySqlBinding::createInteger<uint8_t>(), // persistent
MySqlBinding::createInteger<uint32_t>(), // dhcp4_subnet_id
MySqlBinding::createInteger<uint8_t>(), // scope_id
MySqlBinding::createString(65536), // user_context
MySqlBinding::createString(128), // shared_network_name
MySqlBinding::createInteger<uint64_t>(), // pool_id
MySqlBinding::createTimestamp() //modification_ts
};
uint64_t last_option_id = 0;
conn_.selectQuery(index, in_bindings, out_bindings,
[this, universe, &options, &last_option_id]
(MySqlBindingCollection& out_bindings) {
// Parse option.
if (!out_bindings[0]->amNull() &&
((last_option_id == 0) ||
(last_option_id < out_bindings[0]->getInteger<uint64_t>()))) {
last_option_id = out_bindings[0]->getInteger<uint64_t>();
OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin());
if (desc) {
options.push_back(*desc);
}
}
});
}
OptionDescriptorPtr
MySqlConfigBackendImpl::processOptionRow(const Option::Universe& universe,
MySqlBindingCollection::iterator first_binding) {
std::string space = (*(first_binding + 4))->getStringOrDefault(DHCP4_OPTION_SPACE);
uint16_t code = (*(first_binding + 1))->getInteger<uint8_t>();
OptionDefinitionPtr def = LibDHCP::getOptionDef(space, 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);
}
std::vector<uint8_t> blob;
if (!(*(first_binding + 2))->amNull()) {
blob = (*(first_binding + 2))->getBlob();
}
OptionBuffer buf(blob.begin(), blob.end());
std::string formatted_value = (*(first_binding + 3))->getStringOrDefault("");
OptionPtr option;
if (!def) {
option.reset(new Option(universe, code, buf.begin(), buf.end()));
} else {
if (formatted_value.empty()) {
option = def->optionFactory(universe, code, buf.begin(),
buf.end());
} else {
// Spit the value specified in comma separated values
// format.
std::vector<std::string> split_vec;
boost::split(split_vec, formatted_value, boost::is_any_of(","));
option = def->optionFactory(universe, code, split_vec);
}
}
bool persistent = static_cast<bool>((*(first_binding + 5))->getIntegerOrDefault<uint8_t>(0));
OptionDescriptorPtr desc(new OptionDescriptor(option, persistent, formatted_value));
desc->space_name_ = space;
desc->setModificationTime((*(first_binding + 11))->getTimestamp());
return (desc);
}
MySqlBindingPtr
MySqlConfigBackendImpl::createInputRelayBinding(const NetworkPtr& network) {
ElementPtr relay_element = Element::createList();
......@@ -192,5 +292,22 @@ MySqlConfigBackendImpl::createInputRequiredClassesBinding(const NetworkPtr& netw
MySqlBinding::createNull());
}
MySqlBindingPtr
MySqlConfigBackendImpl::createOptionValueBinding(const OptionDescriptorPtr& option) {
OptionPtr opt = option->option_;
if (option->formatted_value_.empty() && (opt->len() > opt->getHeaderLen())) {
OutputBuffer buf(opt->len());
opt->pack(buf);
const char* buf_ptr = static_cast<const char*>(buf.getData());
std::vector<uint8_t> blob(buf_ptr + opt->getHeaderLen(),
buf_ptr + buf.getLength());
return (MySqlBinding::createBlob(blob.begin(), blob.end()));
}
return (MySqlBinding::createNull());
}
} // end of namespace isc::dhcp
} // end of namespace isc
......@@ -8,7 +8,9 @@
#define MYSQL_CONFIG_BACKEND_IMPL_H
#include <database/database_connection.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/network.h>
#include <mysql/mysql_binding.h>
#include <mysql/mysql_connection.h>
......@@ -64,6 +66,47 @@ public:
const db::MySqlBindingCollection& in_bindings,
OptionDefContainer& option_defs);
/// @brief Sends query to the database to retrieve multiple options.
///
/// Query should order by option_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 universe Option universe, i.e. V4 or V6.
/// @param [out] options Reference to the container where fetched options
/// will be inserted.
void getOptions(const int index,
const db::MySqlBindingCollection& in_bindings,
const Option::Universe& universe,
OptionContainer& options);
/// @brief Returns DHCP option instance from output bindings.
///
/// The following is the expected order of columns specified in the SELECT
/// query:
/// - option_id,
/// - code,
/// - value,
/// - formatted_value,
/// - space,
/// - persistent,
/// - dhcp4_subnet_id,
/// - scope_id,
/// - user_context,
/// - shared_network_name,
/// - pool_id,
/// - modification_ts
///
/// @param universe V4 or V6.
/// @param first_binding Iterator of the output binding containing
/// option_id.
OptionDescriptorPtr
processOptionRow(const Option::Universe& universe,
db::MySqlBindingCollection::iterator first_binding);
/// @brief Creates input binding for relay addresses.
///
/// @param network Pointer to a shared network or subnet for which binding
......@@ -95,6 +138,13 @@ public:
db::MySqlBinding::createNull());
}
/// @brief Creates input binding for option value parameter.
///
/// @param option Option descriptor holding option for which binding is to
/// be created.
/// @return Pointer to the binding (possibly null binding if formatted
/// value is non-empty.
db::MySqlBindingPtr createOptionValueBinding(const OptionDescriptorPtr& option);
/// @brief Represents connection to the MySQL database.
db::MySqlConnection conn_;
......
......@@ -32,7 +32,8 @@ 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/testutils/libdhcpsrvtest.la
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
......
......@@ -5,10 +5,16 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <mysql_cb_dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int.h>
#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <mysql_cb_dhcp4.h>
#include <dhcpsrv/testutils/generic_backend_unittest.h>
#include <mysql/testutils/mysql_schema.h>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
......@@ -19,11 +25,12 @@ using namespace isc::db;
using namespace isc::db::test;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
namespace {
/// @brief Test fixture class for @c MySqlConfigBackendDHCPv4.
class MySqlConfigBackendDHCPv4Test : public ::testing::Test {
class MySqlConfigBackendDHCPv4Test : public GenericBackendTest {
public:
/// @brief Constructor.
......@@ -49,6 +56,7 @@ public:
}
// Create test data.
initTestOptions();
initTestSubnets();
initTestSharedNetworks();
initTestOptionDefs();
......@@ -96,6 +104,19 @@ public:
Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"), IOAddress("192.0.2.60")));
subnet->addPool(pool2);
// Add several options to the subnet.
subnet->getCfgOption()->add(test_options_[0]->option_,
test_options_[0]->persistent_,
test_options_[0]->space_name_);
subnet->getCfgOption()->add(test_options_[1]->option_,
test_options_[1]->persistent_,
test_options_[1]->space_name_);
subnet->getCfgOption()->add(test_options_[2]->option_,
test_options_[2]->persistent_,
test_options_[2]->space_name_);
test_subnets_.push_back(subnet);
// Adding another subnet with the same subnet id to test
......@@ -137,6 +158,19 @@ public:
shared_network->setContext(user_context);
shared_network->setValid(5555);
// Add several options to the shared network.
shared_network->getCfgOption()->add(test_options_[2]->option_,
test_options_[2]->persistent_,
test_options_[2]->space_name_);
shared_network->getCfgOption()->add(test_options_[3]->option_,
test_options_[3]->persistent_,
test_options_[3]->space_name_);
shared_network->getCfgOption()->add(test_options_[4]->option_,
test_options_[4]->persistent_,
test_options_[4]->space_name_);
test_networks_.push_back(shared_network);
// Adding another shared network called "level1" to test
......@@ -178,6 +212,58 @@ public:
test_option_defs_.push_back(option_def);
}
/// @brief Creates several DHCP options used in tests.
void initTestOptions() {
ElementPtr user_context = Element::createMap();
user_context->set("foo", Element::create("bar"));
OptionDefSpaceContainer defs;
OptionDescriptor desc =
createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
true, false, "my-boot-file");
desc.space_name_ = DHCP4_OPTION_SPACE;
desc.setContext(user_context);
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
false, true, 64);
desc.space_name_ = DHCP4_OPTION_SPACE;
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
desc = createOption<OptionUint32>(Option::V4, 1, false, false, 312131),
desc.space_name_ = "vendor-encapsulated-options";
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
desc = createAddressOption<Option4AddrLst>(254, true, true,
"192.0.2.3");
desc.space_name_ = DHCP4_OPTION_SPACE;
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
desc = createEmptyOption(Option::V4, 1, true);
desc.space_name_ = "isc";
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
desc = createAddressOption<Option4AddrLst>(2, false, true, "10.0.0.5",
"10.0.0.3", "10.0.3.4");
desc.space_name_ = "isc";
test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
// Add definitions for DHCPv4 non-standard options.
defs.addItem(OptionDefinitionPtr(new OptionDefinition(
"vendor-encapsulated-1", 1, "uint32")),
"vendor-encapsulated-options");
defs.addItem(OptionDefinitionPtr(new OptionDefinition(
"option-254", 254, "ipv4-address", true)),
DHCP4_OPTION_SPACE);
defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "empty")), "isc");
defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "ipv4-address", true)),
"isc");
// Register option definitions.
LibDHCP::setRuntimeOptionDefs(defs);
}
/// @brief Initialize posix time values used in tests.
void initTimestamps() {
// Current time minus 1 hour to make sure it is in the past.
......@@ -198,6 +284,9 @@ public:
/// @brief Holds pointers to option definitions used in tests.
std::vector<OptionDefinitionPtr> test_option_defs_;
/// @brief Holds pointers to options used in tests.
std::vector<OptionDescriptorPtr> test_options_;
/// @brief Holds timestamp values used in tests.
std::map<std::string, boost::posix_time::ptime> timestamps_;
......@@ -565,6 +654,104 @@ TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedOptionDefinitions4) {
ASSERT_TRUE(option_defs.empty());
}
// This test verifies that subnet level option can be added, updated and
// deleted.
TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSubnetOption4) {
// Insert new subnet.
Subnet4Ptr subnet = test_subnets_[1];
cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(), subnet);
// Fetch this subnet by subnet identifier.
Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
subnet->getID());
ASSERT_TRUE(returned_subnet);
OptionDescriptorPtr opt_boot_file_name = test_options_[0];
cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
opt_boot_file_name);
returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
subnet->getID());
ASSERT_TRUE(returned_subnet);
OptionDescriptor returned_opt_boot_file_name =
returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
ASSERT_TRUE(returned_opt_boot_file_name.option_);
EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
opt_boot_file_name);
returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
subnet->getID());
ASSERT_TRUE(returned_subnet);
returned_opt_boot_file_name =
returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
ASSERT_TRUE(returned_opt_boot_file_name.option_);
EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
cbptr_->deleteOption4(ServerSelector::UNASSIGNED(), subnet->getID(),
opt_boot_file_name->option_->getType(),
opt_boot_file_name->space_name_);
returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
subnet->getID());
ASSERT_TRUE(returned_subnet);
EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
}
// This test verifies that shared network level option can be added,
// updated and deleted.
TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteSharedNetworkOption4) {
// Insert new shared network.
SharedNetwork4Ptr shared_network = test_networks_[1];
cbptr_->createUpdateSharedNetwork4(ServerSelector::UNASSIGNED(),
shared_network);
// Fetch this shared network by name.
SharedNetwork4Ptr returned_network =
cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
shared_network->getName());
ASSERT_TRUE(returned_network);
OptionDescriptorPtr opt_boot_file_name = test_options_[0];
cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(),
shared_network->getName(),
opt_boot_file_name);
returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
shared_network->getName());
ASSERT_TRUE(returned_network);
OptionDescriptor returned_opt_boot_file_name =
returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
ASSERT_TRUE(returned_opt_boot_file_name.option_);
EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
cbptr_->createUpdateOption4(ServerSelector::UNASSIGNED(),
shared_network->getName(),
opt_boot_file_name);
returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
shared_network->getName());
ASSERT_TRUE(returned_network);
returned_opt_boot_file_name =
returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
ASSERT_TRUE(returned_opt_boot_file_name.option_);
EXPECT_TRUE(returned_opt_boot_file_name.equals(*opt_boot_file_name));
cbptr_->deleteOption4(ServerSelector::UNASSIGNED(),
shared_network->getName(),
opt_boot_file_name->option_->getType(),
opt_boot_file_name->space_name_);
returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
shared_network->getName());
ASSERT_TRUE(returned_network);
EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
}
}
......@@ -22,8 +22,9 @@ namespace dhcp {
bool
OptionDescriptor::equals(const OptionDescriptor& other) const {
return (persistent_ == other.persistent_ &&
formatted_value_ == other.formatted_value_ &&
return ((persistent_ == other.persistent_) &&
(formatted_value_ == other.formatted_value_) &&
(space_name_ == other.space_name_) &&
option_->equals(other.option_));
}
......
......@@ -10,6 +10,7 @@
#include <dhcp/option.h>
#include <dhcp/option_space_container.h>
#include <cc/cfg_to_element.h>
#include <cc/stamped_element.h>
#include <cc/user_context.h>
#include <dhcpsrv/key_from_key.h>
#include <boost/multi_index_container.hpp>
......@@ -31,7 +32,7 @@ namespace dhcp {
/// for this option. This information comprises whether this option is sent
/// to DHCP client only on request (persistent = false) or always
/// (persistent = true).
class OptionDescriptor : public data::UserContext {
class OptionDescriptor : public data::StampedElement, public data::UserContext {
public:
/// @brief Option instance.
OptionPtr option_;
......@@ -57,6 +58,17 @@ public:
/// for the option which carries IPv6 address, a number and a text.
std::string formatted_value_;
/// @brief Option space name.
///
/// Options are associated with option spaces. Typically, such association
/// is made when the option is stored in the @c OptionContainer. However,
/// in some cases it is also required to associate option with the particular
/// option space outside of the container. In particular, when the option
/// is fetched from a database. The database configuration backend will
/// set option space upon return of the option. In other cases this value
/// won't be set.
std::string space_name_;
/// @brief Constructor.
///
/// @param opt option
......@@ -68,7 +80,8 @@ public:
const std::string& formatted_value = "",
data::ConstElementPtr user_context = data::ConstElementPtr())
: option_(opt), persistent_(persist),
formatted_value_(formatted_value) {
formatted_value_(formatted_value),
space_name_() {
setContext(user_context);
};
......@@ -77,14 +90,15 @@ public:
/// @param persist if true option is always sent.
OptionDescriptor(bool persist)
: option_(OptionPtr()), persistent_(persist),
formatted_value_() {};
formatted_value_(), space_name_() {};
/// @brief Constructor.
///
/// @param desc descriptor
OptionDescriptor(const OptionDescriptor& desc)
: option_(desc.option_), persistent_(desc.persistent_),
formatted_value_(desc.formatted_value_) {
formatted_value_(desc.formatted_value_),
space_name_(desc.space_name_) {
setContext(desc.getContext());
};
......
......@@ -11,6 +11,7 @@
#include <database/server_selector.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
#include <util/optional_value.h>
......@@ -170,7 +171,18 @@ public:
/// @param option Option to be added or updated.
virtual void
createUpdateOption4(const db::ServerSelector& server_selector,
const OptionPtr& option) = 0;
const OptionDescriptorPtr& option) = 0;
/// @brief Creates or updates shared network level option.
///