#include <database/db_exceptions.h>
#include <dhcp/classify.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/option_data_types.h>
#include <dhcpsrv/network.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/lease.h>
GET_SHARED_NETWORK4_NAME,
GET_ALL_SHARED_NETWORKS4,
GET_MODIFIED_SHARED_NETWORKS4,
+ GET_OPTION_DEF4_CODE_SPACE,
+ GET_ALL_OPTION_DEFS4,
+ GET_MODIFIED_OPTION_DEFS4,
INSERT_SUBNET4,
INSERT_POOL4,
INSERT_SHARED_NETWORK4,
+ INSERT_OPTION_DEF4,
UPDATE_SUBNET4,
UPDATE_SHARED_NETWORK4,
+ UPDATE_OPTION_DEF4,
DELETE_POOLS4_SUBNET_ID,
NUM_STATEMENTS
};
(shared_network ? MySqlBinding::createString(shared_network->getName()) :
MySqlBinding::createNull());
- // Create user context binding if user context exists.
- auto context_element = subnet->getContext();
- MySqlBindingPtr context_binding =
- (context_element ? MySqlBinding::createString(context_element->str()) :
- MySqlBinding::createNull());
-
// Create input bindings.
MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint32_t>(subnet->getID()),
MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(subnet->getHostReservationMode())),
MySqlBinding::condCreateString(subnet->getSname()),
shared_network_binding,
- context_binding,
+ createInputContextBinding(subnet),
MySqlBinding::createInteger<uint32_t>(subnet->getValid())
};
if ((last_network_id == 0) ||
(last_network_id != out_bindings[0]->getInteger<uint64_t>())) {
+ last_network_id = out_bindings[0]->getInteger<uint64_t>();
last_network.reset(new SharedNetwork4(out_bindings[1]->getString()));
// client_class
conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_SHARED_NETWORK4,
in_bindings);
}
+ }
+
+ /// @brief Sends query to the database to retrieve multiple option
+ /// definitions.
+ ///
+ /// Query should order option definitions by id.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param in_bindings Input bindings specifying selection criteria. The
+ /// size of the bindings collection must match the number of placeholders
+ /// in the prepared statement. The input bindings collection must be empty
+ /// if the query contains no WHERE clause.
+ /// @param [out] option_defs Reference to the container where fetched
+ /// option definitions will be inserted.
+ void getOptionDefs4(const StatementIndex& index,
+ const MySqlBindingCollection& in_bindings,
+ OptionDefContainer& option_defs) {
+ // Create output bindings. The order must match that in the prepared
+ // statement.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint64_t>(), // id
+ MySqlBinding::createInteger<uint8_t>(), // code
+ MySqlBinding::createString(128), // name
+ MySqlBinding::createString(128), // space
+ MySqlBinding::createInteger<uint8_t>(), // type
+ MySqlBinding::createTimestamp(), // modification_ts
+ MySqlBinding::createInteger<uint8_t>(), // array
+ MySqlBinding::createString(128), // encapsulate
+ MySqlBinding::createString(512), // record_types
+ MySqlBinding::createString(65536) // user_context
+ };
+
+ uint64_t last_def_id = 0;
+
+ // Run select query.
+ conn_.selectQuery(index, in_bindings, out_bindings,
+ [&option_defs, &last_def_id]
+ (MySqlBindingCollection& out_bindings) {
+ // Get pointer to last fetched option definition.
+ OptionDefinitionPtr last_def;
+ if (!option_defs.empty()) {
+ last_def = *option_defs.rbegin();
+ }
+
+ // See if the last fetched definition is the one for which we now got
+ // the row of data. If not, it means that we need to create new option
+ // definition.
+ if ((last_def_id == 0) ||
+ (last_def_id != out_bindings[0]->getInteger<uint64_t>())) {
+
+ last_def_id = out_bindings[0]->getInteger<uint64_t>();
+
+ // Check array type, because depending on this value we have to use
+ // different constructor.
+ bool array_type = static_cast<bool>(out_bindings[6]->getInteger<uint8_t>());
+ if (array_type) {
+ // Create array option.
+ last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint8_t>(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ array_type));
+ } else {
+ // Create non-array option.
+ last_def.reset(new OptionDefinition(out_bindings[2]->getString(),
+ out_bindings[1]->getInteger<uint8_t>(),
+ static_cast<OptionDataType>
+ (out_bindings[4]->getInteger<uint8_t>()),
+ out_bindings[7]->getStringOrDefault("").c_str()));
+ }
+
+ // space
+ last_def->setOptionSpaceName(out_bindings[3]->getStringOrDefault(""));
+
+ // record_types
+ ElementPtr record_types_element = out_bindings[8]->getJSON();
+ if (record_types_element) {
+ if (record_types_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid record_types value "
+ << out_bindings[8]->getString());
+ }
+ // This element must contain a list of integers specifying
+ // types of the record fields.
+ for (auto i = 0; i < record_types_element->size(); ++i) {
+ auto type_element = record_types_element->get(i);
+ if (type_element->getType() != Element::integer) {
+ isc_throw(BadValue, "record type values must be integers");
+ }
+ last_def->addRecordField(static_cast<OptionDataType>
+ (type_element->intValue()));
+ }
+ }
+
+ // Update modification time.
+ last_def->setModificationTime(out_bindings[5]->getTimestamp());
+
+ // Store created option definition.
+ option_defs.push_back(last_def);
+ }
+ });
+ }
+
+ /// @brief Sends query to retrieve single option definition by code and
+ /// option space.
+ ///
+ /// @param selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the returned option definition or NULL if such
+ /// option definition doesn't exist.
+ OptionDefinitionPtr getOptionDef4(const ServerSelector& selector,
+ const uint16_t code,
+ const std::string& space) {
+ OptionDefContainer option_defs;
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(code)),
+ MySqlBinding::createString(space)
+ };
+ getOptionDefs4(GET_OPTION_DEF4_CODE_SPACE, in_bindings, option_defs);
+ return (option_defs.empty() ? OptionDefinitionPtr() : *option_defs.begin());
+ }
+
+ /// @brief Sends query to insert or update option definition.
+ ///
+ /// @param selector Server selector.
+ /// @param option_def Pointer to the option definition to be inserted or updated.
+ void createUpdateOptionDef4(const ServerSelector& selector,
+ const OptionDefinitionPtr& option_def) {
+ ElementPtr record_types = Element::createList();
+ for (auto field : option_def->getRecordFields()) {
+ record_types->add(Element::create(static_cast<int>(field)));
+ }
+ MySqlBindingPtr record_types_binding = record_types->empty() ?
+ MySqlBinding::createNull() : MySqlBinding::createString(record_types->str());
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getCode())),
+ MySqlBinding::createString(option_def->getName()),
+ MySqlBinding::createString(option_def->getOptionSpaceName().empty() ?
+ "dhcp4" : option_def->getOptionSpaceName()),
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getType())),
+ MySqlBinding::createTimestamp(option_def->getModificationTime()),
+ MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(option_def->getArrayType())),
+ MySqlBinding::createString(option_def->getEncapsulatedSpace()),
+ record_types_binding,
+ createInputContextBinding(option_def)
+ };
+
+ // If the shared network exists we are going to update this network.
+ OptionDefinitionPtr existing_definition = getOptionDef4(selector,
+ option_def->getCode(),
+ option_def->getOptionSpaceName());
+ if (existing_definition) {
+ // Need to add two more bindings for WHERE clause.
+ in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(existing_definition->getCode()));
+ in_bindings.push_back(MySqlBinding::createString(existing_definition->getOptionSpaceName()));
+ conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4,
+ in_bindings);
+
+ } else {
+ // If the option definition doesn't exist, let's insert it.
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4,
+ in_bindings);
+ }
}
/// @brief Creates input binding for relay addresses.
/// @brief Creates input binding for user context parameter.
///
- /// @param network Pointer to a shared network or subnet for which binding
- /// should be created.
+ /// @param network Pointer to a shared network, subnet or other configuration
+ /// element for which binding should be created.
/// @return Pointer to the binding (possibly null binding if context is
/// null).
- MySqlBindingPtr createInputContextBinding(const NetworkPtr& network) {
+ template<typename T>
+ MySqlBindingPtr createInputContextBinding(const T& config_element) {
// Create user context binding if user context exists.
- auto context_element = network->getContext();
+ auto context_element = config_element->getContext();
return (context_element ? MySqlBinding::createString(context_element->str()) :
MySqlBinding::createNull());
}
"WHERE n.modification_ts > ? "
"ORDER BY n.id" },
+ // Retrieves option definition by code and space.
+ { MySqlConfigBackendDHCPv4Impl::GET_OPTION_DEF4_CODE_SPACE,
+ "SELECT"
+ " d.id,"
+ " d.code,"
+ " d.name,"
+ " d.space,"
+ " d.type,"
+ " d.modification_ts,"
+ " d.array,"
+ " d.encapsulate,"
+ " d.record_types,"
+ " d.user_context "
+ "FROM dhcp4_option_def AS d "
+ "WHERE d.code = ? AND d.space = ? "
+ "ORDER BY d.id" },
+
+ // Retrieves all option definitions.
+ { MySqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
+ "SELECT"
+ " d.id,"
+ " d.code,"
+ " d.name,"
+ " d.space,"
+ " d.type,"
+ " d.modification_ts,"
+ " d.array,"
+ " d.encapsulate,"
+ " d.record_types,"
+ " d.user_context "
+ "FROM dhcp4_option_def AS d "
+ "ORDER BY d.id" },
+
+ // Retrieves modified option definitions.
+ { MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
+ "SELECT"
+ " d.id,"
+ " d.code,"
+ " d.name,"
+ " d.space,"
+ " d.type,"
+ " d.modification_ts,"
+ " d.array,"
+ " d.encapsulate,"
+ " d.record_types,"
+ " d.user_context "
+ "FROM dhcp4_option_def AS d "
+ "WHERE modification_ts > ? "
+ "ORDER BY d.id" },
+
// Insert a subnet.
{ MySqlConfigBackendDHCPv4Impl::INSERT_SUBNET4,
"INSERT INTO dhcp4_subnet("
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,"
"?, ?, ?, ?, ?, ?, ?, ?)" },
+ // Insert pool for a subnet.
{ MySqlConfigBackendDHCPv4Impl::INSERT_POOL4,
"INSERT INTO dhcp4_pool("
" start_address,"
"valid_lifetime"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" },
+ // Insert option definition.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4,
+ "INSERT INTO dhcp4_option_def ("
+ "code,"
+ "name,"
+ "space,"
+ "type,"
+ "modification_ts,"
+ "array,"
+ "encapsulate,"
+ "record_types,"
+ "user_context"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" },
+
// Update existing subnet.
{ MySqlConfigBackendDHCPv4Impl::UPDATE_SUBNET4,
"UPDATE dhcp4_subnet SET"
" valid_lifetime = ? "
"WHERE name = ?" },
+ // Update existing option definition.
+ { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4,
+ "UPDATE dhcp4_option_def SET"
+ " code = ?,"
+ " name = ?,"
+ " space = ?,"
+ " type = ?,"
+ " modification_ts = ?,"
+ " array = ?,"
+ " encapsulate = ?,"
+ " record_types = ?,"
+ " user_context = ? "
+ "WHERE code = ? AND space = ?" },
+
// Delete pools for a subnet.
{ MySqlConfigBackendDHCPv4Impl::DELETE_POOLS4_SUBNET_ID,
"DELETE FROM dhcp4_pool "
MySqlConfigBackendDHCPv4::getOptionDef4(const ServerSelector& selector,
const uint16_t code,
const std::string& space) const {
- isc_throw(NotImplemented, "not implemented");
+ return (impl_->getOptionDef4(selector, code, space));
}
OptionDefContainer
MySqlConfigBackendDHCPv4::getAllOptionDefs4(const ServerSelector& selector) const {
- isc_throw(NotImplemented, "not implemented");
+ OptionDefContainer option_defs;
+ MySqlBindingCollection in_bindings;
+ impl_->getOptionDefs4(MySqlConfigBackendDHCPv4Impl::GET_ALL_OPTION_DEFS4,
+ in_bindings, option_defs);
+ return (option_defs);
}
OptionDefContainer
MySqlConfigBackendDHCPv4::
getModifiedOptionDefs4(const ServerSelector& selector,
const boost::posix_time::ptime& modification_time) const {
- isc_throw(NotImplemented, "not implemented");
+ OptionDefContainer option_defs;
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createTimestamp(modification_time)
+ };
+ impl_->getOptionDefs4(MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_OPTION_DEFS4,
+ in_bindings, option_defs);
+ return (option_defs);
}
util::OptionalValue<std::string>
void
MySqlConfigBackendDHCPv4::createUpdateOptionDef4(const ServerSelector& selector,
const OptionDefinitionPtr& option_def) {
+ impl_->createUpdateOptionDef4(selector, option_def);
}
void
// Create test data.
initTestSubnets();
initTestSharedNetworks();
+ initTestOptionDefs();
initTimestamps();
}
test_networks_.push_back(shared_network);
}
+ /// @brief Creates several option definitions used in tests.
+ void initTestOptionDefs() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefinitionPtr option_def(new OptionDefinition("foo", 234, "string",
+ "espace"));
+ option_def->setOptionSpaceName("dhcp4");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 234, "uint32", true));
+ option_def->setOptionSpaceName("dhcp4");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("fish", 235, "record", true));
+ option_def->setOptionSpaceName("dhcp4");
+ option_def->addRecordField("uint32");
+ option_def->addRecordField("string");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("whale", 236, "string"));
+ option_def->setOptionSpaceName("xyz");
+ test_option_defs_.push_back(option_def);
+ }
+
/// @brief Initialize posix time values used in tests.
void initTimestamps() {
// Current time minus 1 hour to make sure it is in the past.
/// @brief Holds pointers to shared networks used in tests.
std::vector<SharedNetwork4Ptr> test_networks_;
+ /// @brief Holds pointers to option definitions used in tests.
+ std::vector<OptionDefinitionPtr> test_option_defs_;
+
/// @brief Holds timestamp values used in tests.
std::map<std::string, boost::posix_time::ptime> timestamps_;
ASSERT_TRUE(networks.empty());
}
+// Test that subnet can be inserted, fetched, updated and then fetched again.
+TEST_F(MySqlConfigBackendDHCPv4Test, getOptionDef4) {
+ // Insert new option definition.
+ OptionDefinitionPtr option_def = test_option_defs_[0];
+ cbptr_->createUpdateOptionDef4(ServerSelector::UNASSIGNED(), option_def);
+
+ // Fetch this option_definition by subnet identifier.
+ OptionDefinitionPtr returned_option_def =
+ cbptr_->getOptionDef4(ServerSelector::UNASSIGNED(),
+ test_option_defs_[0]->getCode(),
+ test_option_defs_[0]->getOptionSpaceName());
+ ASSERT_TRUE(returned_option_def);
+
+ EXPECT_TRUE(returned_option_def->equals(*option_def));
+
+ // Update the option definition in the database.
+ OptionDefinitionPtr option_def2 = test_option_defs_[1];
+ cbptr_->createUpdateOptionDef4(ServerSelector::UNASSIGNED(), option_def2);
+
+ // Fetch updated option definition and see if it matches.
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::UNASSIGNED(),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+}
+
+// Test that all shared networks can be fetched.
+TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptionDefs4) {
+ // Insert test option definitions into the database. Note that the second
+ // option definition will overwrite the first option definition as they use
+ // the same code and space.
+ for (auto option_def : test_option_defs_) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::UNASSIGNED(), option_def);
+ }
+
+ // Fetch all option_definitions.
+ OptionDefContainer option_defs = cbptr_->getAllOptionDefs4(ServerSelector::UNASSIGNED());
+ ASSERT_EQ(test_option_defs_.size() - 1, option_defs.size());
+
+ // See if option definitions are returned ok.
+ for (auto def = option_defs.begin(); def != option_defs.end(); ++def) {
+ bool success = false;
+ for (auto i = 1; i < test_option_defs_.size(); ++i) {
+ if ((*def)->equals(*test_option_defs_[i])) {
+ success = true;
+ }
+ }
+ ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode()
+ << ", option space " << (*def)->getOptionSpaceName();
+ }
+}
+
+// Test that option definitions modified after given time can be fetched.
+TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedOptionDefinitions4) {
+ // Explicitly set timestamps of option definitions. First option
+ // definition has a timestamp pointing to the future. Second option
+ // definition has timestamp pointing to the past (yesterday).
+ // Third option definitions has a timestamp pointing to the
+ // past (an hour ago).
+ test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_option_defs_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert option definitions into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::UNASSIGNED(),
+ test_option_defs_[i]);
+ }
+
+ // Fetch option definitions with timestamp later than today. Only one
+ // option definition should be returned.
+ OptionDefContainer
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::UNASSIGNED(),
+ timestamps_["today"]);
+ ASSERT_EQ(1, option_defs.size());
+
+ // Fetch option definitions with timestamp later than yesterday. We
+ // should get two option definitions.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]);
+ ASSERT_EQ(2, option_defs.size());
+
+ // Fetch option definitions with timestamp later than tomorrow. Nothing
+ // should be returned.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::UNASSIGNED(),
+ timestamps_["tomorrow"]);
+ ASSERT_TRUE(option_defs.empty());
+}
+
+
}