summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp
diff options
context:
space:
mode:
authorMarcin Siodelski <marcin@isc.org>2021-06-16 15:04:35 +0200
committerMarcin Siodelski <marcin@isc.org>2021-07-21 12:49:50 +0200
commit1faffe41ce47412841d723223c2e1923a81a5773 (patch)
tree5594c80aa37332c13a90c1406c2406e17e55c414 /src/hooks/dhcp
parent[#1928] Client classes added to CB API (diff)
downloadkea-1faffe41ce47412841d723223c2e1923a81a5773.tar.xz
kea-1faffe41ce47412841d723223c2e1923a81a5773.zip
[#1928] DHCPv4 client classes in MySQL
Diffstat (limited to 'src/hooks/dhcp')
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc698
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h9
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc118
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_impl.h32
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc20
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_messages.h10
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes29
-rw-r--r--src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h170
-rw-r--r--src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc600
9 files changed, 1608 insertions, 78 deletions
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
index 9dafd1043b..e248668334 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
@@ -24,6 +24,7 @@
#include <dhcpsrv/pool.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/timer_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <util/buffer.h>
#include <util/boost_time_utils.h>
#include <util/multi_threading_mgr.h>
@@ -61,6 +62,7 @@ public:
/// database.
enum StatementIndex {
CREATE_AUDIT_REVISION,
+ CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
GET_GLOBAL_PARAMETER4,
GET_ALL_GLOBAL_PARAMETERS4,
GET_MODIFIED_GLOBAL_PARAMETERS4,
@@ -93,6 +95,11 @@ public:
GET_OPTION4_SUBNET_ID_CODE_SPACE,
GET_OPTION4_POOL_ID_CODE_SPACE,
GET_OPTION4_SHARED_NETWORK_CODE_SPACE,
+ GET_CLIENT_CLASS4_NAME,
+ GET_ALL_CLIENT_CLASSES4,
+ GET_ALL_CLIENT_CLASSES4_UNASSIGNED,
+ GET_MODIFIED_CLIENT_CLASSES4,
+ GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED,
GET_AUDIT_ENTRIES4_TIME,
GET_SERVER4,
GET_ALL_SERVERS4,
@@ -104,18 +111,25 @@ public:
INSERT_SHARED_NETWORK4,
INSERT_SHARED_NETWORK4_SERVER,
INSERT_OPTION_DEF4,
+ INSERT_OPTION_DEF4_CLIENT_CLASS,
INSERT_OPTION_DEF4_SERVER,
INSERT_OPTION4,
INSERT_OPTION4_SERVER,
+ INSERT_CLIENT_CLASS4,
+ INSERT_CLIENT_CLASS4_SERVER,
+ INSERT_CLIENT_CLASS4_DEPENDENCY,
INSERT_SERVER4,
UPDATE_GLOBAL_PARAMETER4,
UPDATE_SUBNET4,
UPDATE_SHARED_NETWORK4,
UPDATE_OPTION_DEF4,
+ UPDATE_OPTION_DEF4_CLIENT_CLASS,
UPDATE_OPTION4,
UPDATE_OPTION4_SUBNET_ID,
UPDATE_OPTION4_POOL_ID,
UPDATE_OPTION4_SHARED_NETWORK,
+ UPDATE_OPTION4_CLIENT_CLASS,
+ UPDATE_CLIENT_CLASS4,
UPDATE_SERVER4,
DELETE_GLOBAL_PARAMETER4,
DELETE_ALL_GLOBAL_PARAMETERS4,
@@ -137,6 +151,7 @@ public:
DELETE_OPTION_DEF4_CODE_NAME,
DELETE_ALL_OPTION_DEFS4,
DELETE_ALL_OPTION_DEFS4_UNASSIGNED,
+ DELETE_OPTION_DEFS4_CLIENT_CLASS,
DELETE_OPTION4,
DELETE_ALL_GLOBAL_OPTIONS4_UNASSIGNED,
DELETE_OPTION4_SUBNET_ID,
@@ -144,6 +159,13 @@ public:
DELETE_OPTION4_SHARED_NETWORK,
DELETE_OPTIONS4_SUBNET_ID_PREFIX,
DELETE_OPTIONS4_SHARED_NETWORK,
+ DELETE_OPTIONS4_CLIENT_CLASS,
+ DELETE_CLIENT_CLASS4_DEPENDENCY,
+ DELETE_CLIENT_CLASS4_SERVER,
+ DELETE_ALL_CLIENT_CLASSES4,
+ DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED,
+ DELETE_CLIENT_CLASS4,
+ DELETE_CLIENT_CLASS4_ANY,
DELETE_SERVER4,
DELETE_ALL_SERVERS4,
NUM_STATEMENTS
@@ -1312,7 +1334,7 @@ public:
MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option: user_context
MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // option: shared_network_name
MySqlBinding::createInteger<uint64_t>(), // option: pool_id
- MySqlBinding::createTimestamp(), //option: modification_ts
+ MySqlBinding::createTimestamp(), // option: modification_ts
MySqlBinding::createInteger<uint8_t>(), // calculate_tee_times
MySqlBinding::createInteger<float>(), // t1_percent
MySqlBinding::createInteger<float>(), // t2_percent
@@ -2050,6 +2072,55 @@ public:
}
}
+ /// @brief Sends query to insert or update DHCP option in a client class.
+ ///
+ /// @param selector Server selector.
+ /// @param client_class Pointer to the client_class the option belongs to.
+ /// @param option Pointer to the option descriptor encapsulating the option..
+ void createUpdateOption4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const OptionDescriptorPtr& option) {
+
+ if (server_selector.amUnassigned()) {
+ isc_throw(NotImplemented, "managing configuration for no particular server"
+ " (unassigned) is unsupported at the moment");
+ }
+
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+ createOptionValueBinding(option),
+ MySqlBinding::condCreateString(option->formatted_value_),
+ MySqlBinding::condCreateString(option->space_name_),
+ MySqlBinding::createBool(option->persistent_),
+ MySqlBinding::createString(client_class->getName()),
+ MySqlBinding::createNull(),
+ MySqlBinding::createInteger<uint8_t>(2),
+ createInputContextBinding(option),
+ MySqlBinding::createNull(),
+ MySqlBinding::createNull(),
+ MySqlBinding::createTimestamp(option->getModificationTime()),
+ MySqlBinding::createString(client_class->getName()),
+ MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+ MySqlBinding::condCreateString(option->space_name_)
+ };
+
+ // Create scoped audit revision. As long as this instance exists
+ // no new audit revisions are created in any subsequent calls.
+ ScopedAuditRevision
+ audit_revision(this,
+ MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ server_selector, "client class specific option set",
+ true);
+
+ if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
+ UPDATE_OPTION4_CLIENT_CLASS,
+ in_bindings) == 0) {
+ // Remove the 3 bindings used only in case of update.
+ in_bindings.resize(in_bindings.size() - 3);
+ insertOption4(server_selector, in_bindings);
+ }
+ }
+
/// @brief Sends query to insert or update option definition.
///
/// @param server_selector Server selector.
@@ -2065,6 +2136,24 @@ public:
MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_SERVER);
}
+ /// @brief Sends query to insert or update option definition
+ /// for a client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Pointer to the option definition to be inserted or updated.
+ /// @param client_class Client class name.
+ void createUpdateOptionDef4(const ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def,
+ const std::string& client_class_name) {
+ createUpdateOptionDef(server_selector, option_def, DHCP4_OPTION_SPACE,
+ MySqlConfigBackendDHCPv4Impl::GET_OPTION_DEF4_CODE_SPACE,
+ MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_CLIENT_CLASS,
+ MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4_CLIENT_CLASS,
+ MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_SERVER,
+ client_class_name);
+ }
+
/// @brief Sends query to delete option definition by code and
/// option space name.
///
@@ -2089,6 +2178,26 @@ public:
in_bindings));
}
+ /// @brief Sends query to delete option definitions for a client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Pointer to the client class for which option
+ /// definitions should be deleted.
+ /// @return Number of deleted option definitions.
+ uint64_t deleteOptionDefs4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class) {
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createString(client_class->getName())
+ };
+
+ // Run DELETE.
+ return (deleteTransactional(DELETE_OPTION_DEFS4_CLIENT_CLASS, server_selector,
+ "deleting option definition for a client class",
+ "option definition deleted",
+ true,
+ in_bindings));
+ }
+
/// @brief Deletes global option.
///
/// @param server_selector Server selector.
@@ -2238,6 +2347,412 @@ public:
in_bindings));
}
+ /// @brief Deletes options belonging to a client class from the database.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Pointer to the client class for which options
+ /// should be deleted.
+ /// @return Number of deleted options.
+ uint64_t deleteOptions4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class) {
+
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createString(client_class->getName())
+ };
+
+ // Run DELETE.
+ return (deleteTransactional(DELETE_OPTIONS4_CLIENT_CLASS, server_selector,
+ "deleting options for a client class",
+ "client class specific options deleted",
+ true,
+ in_bindings));
+ }
+
+ /// @brief Common function to retrieve client classes.
+ ///
+ /// @param index Index of the query to be used.
+ /// @param server_selector Server selector.
+ /// @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] client_classes Reference to a container where fetched client
+ /// classes will be inserted.
+ void getClientClasses4(const StatementIndex& index,
+ const ServerSelector& server_selector,
+ const MySqlBindingCollection& in_bindings,
+ ClientClassDictionary& client_classes) {
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint64_t>(), // id
+ MySqlBinding::createString(CLIENT_CLASS_NAME_BUF_LENGTH), // name
+ MySqlBinding::createString(CLIENT_CLASS_TEST_BUF_LENGTH), // test
+ MySqlBinding::createInteger<uint32_t>(), // next server
+ MySqlBinding::createString(CLIENT_CLASS_SNAME_BUF_LENGTH), // sname
+ MySqlBinding::createString(CLIENT_CLASS_FILENAME_BUF_LENGTH), // filename
+ MySqlBinding::createInteger<uint8_t>(), // required
+ MySqlBinding::createInteger<uint32_t>(), // valid lifetime
+ MySqlBinding::createInteger<uint32_t>(), // min valid lifetime
+ MySqlBinding::createInteger<uint32_t>(), // max valid lifetime
+ MySqlBinding::createInteger<uint8_t>(), // depend on known directly
+ MySqlBinding::createInteger<uint8_t>(), // depend on known indirectly
+ MySqlBinding::createTimestamp(), // modification_ts
+ MySqlBinding::createInteger<uint64_t>(), // option def: id
+ MySqlBinding::createInteger<uint16_t>(), // option def: code
+ MySqlBinding::createString(OPTION_NAME_BUF_LENGTH), // option def: name
+ MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // option def: space
+ MySqlBinding::createInteger<uint8_t>(), // option def: type
+ MySqlBinding::createTimestamp(), // option def: modification_ts
+ MySqlBinding::createInteger<uint8_t>(), // option def: array
+ MySqlBinding::createString(OPTION_ENCAPSULATE_BUF_LENGTH), // option def: encapsulate
+ MySqlBinding::createString(OPTION_RECORD_TYPES_BUF_LENGTH), // option def: record_types
+ MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option def: user_context
+ MySqlBinding::createInteger<uint64_t>(), // option: option_id
+ MySqlBinding::createInteger<uint8_t>(), // option: code
+ MySqlBinding::createBlob(OPTION_VALUE_BUF_LENGTH), // option: value
+ MySqlBinding::createString(FORMATTED_OPTION_VALUE_BUF_LENGTH), // option: formatted_value
+ MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // option: space
+ MySqlBinding::createInteger<uint8_t>(), // option: persistent
+ MySqlBinding::createInteger<uint32_t>(), // option: dhcp4_subnet_id
+ MySqlBinding::createInteger<uint8_t>(), // option: scope_id
+ MySqlBinding::createString(USER_CONTEXT_BUF_LENGTH), // option: user_context
+ MySqlBinding::createString(SHARED_NETWORK_NAME_BUF_LENGTH), // option: shared_network_name
+ MySqlBinding::createInteger<uint64_t>(), // option: pool_id
+ MySqlBinding::createTimestamp(), // option: modification_ts
+ MySqlBinding::createString(SERVER_TAG_BUF_LENGTH) // server tag
+ };
+
+ std::list<ClientClassDefPtr> class_list;
+ uint64_t last_option_id = 0;
+ uint64_t last_option_def_id = 0;
+ std::string last_tag;
+
+ conn_.selectQuery(index,
+ in_bindings, out_bindings,
+ [this, &class_list, &last_option_id, &last_option_def_id, &last_tag]
+ (MySqlBindingCollection& out_bindings) {
+ ClientClassDefPtr last_client_class;
+ if (!class_list.empty()) {
+ last_client_class = *class_list.rbegin();
+ }
+
+ if (!last_client_class || (last_client_class->getId() != out_bindings[0]->getInteger<uint64_t>())) {
+
+ last_option_id = 0;
+ last_option_def_id = 0;
+ last_tag.clear();
+
+ auto options = boost::make_shared<CfgOption>();
+ auto option_defs = boost::make_shared<CfgOptionDef>();
+ auto expression = boost::make_shared<Expression>();
+
+ last_client_class = boost::make_shared<ClientClassDef>(out_bindings[1]->getString(), expression, options);
+ last_client_class->setCfgOptionDef(option_defs);
+
+ // id
+ last_client_class->setId(out_bindings[0]->getInteger<uint64_t>());
+
+ // name
+ last_client_class->setName(out_bindings[1]->getString());
+
+ // test
+ if (!out_bindings[2]->amNull()) {
+ last_client_class->setTest(out_bindings[2]->getString());
+ }
+
+ // next server
+ if (!out_bindings[3]->amNull()) {
+ last_client_class->setNextServer(IOAddress(out_bindings[3]->getInteger<uint32_t>()));
+ }
+
+ // sname
+ if (!out_bindings[4]->amNull()) {
+ last_client_class->setSname(out_bindings[4]->getString());
+ }
+
+ // filename
+ if (!out_bindings[5]->amNull()) {
+ last_client_class->setFilename(out_bindings[5]->getString());
+ }
+
+ // required
+ if (!out_bindings[6]->amNull()) {
+ last_client_class->setRequired(out_bindings[6]->getBool());
+ }
+
+ // valid lifetime: default, min, max
+ last_client_class->setValid(createTriplet(out_bindings[7], out_bindings[8], out_bindings[9]));
+
+ // depend on known directly or indirectly
+ last_client_class->setDependOnKnown(out_bindings[10]->getBool() || out_bindings[11]->getBool());
+
+ // modification_ts
+ last_client_class->setModificationTime(out_bindings[12]->getTimestamp());
+
+ class_list.push_back(last_client_class);
+ }
+
+ // server tag
+ if (!out_bindings[35]->amNull() &&
+ (last_tag != out_bindings[35]->getString())) {
+ last_tag = out_bindings[35]->getString();
+ if (!last_tag.empty() && !last_client_class->hasServerTag(ServerTag(last_tag))) {
+ last_client_class->setServerTag(last_tag);
+ }
+ }
+
+ // Parse client class specific option definition from 13 to 22.
+ if (!out_bindings[13]->amNull() &&
+ (last_option_def_id < out_bindings[13]->getInteger<uint64_t>())) {
+ last_option_def_id = out_bindings[13]->getInteger<uint64_t>();
+
+ auto def = processOptionDefRow(out_bindings.begin() + 13);
+ if (def) {
+ last_client_class->getCfgOptionDef()->add(def);
+ }
+ }
+
+ // Parse client class specific option from 23 to 34.
+ if (!out_bindings[23]->amNull() &&
+ (last_option_id < out_bindings[23]->getInteger<uint64_t>())) {
+ last_option_id = out_bindings[23]->getInteger<uint64_t>();
+
+ OptionDescriptorPtr desc = processOptionRow(Option::V4, out_bindings.begin() + 23);
+ if (desc) {
+ last_client_class->getCfgOption()->add(*desc, desc->space_name_);
+ }
+ }
+ });
+
+ tossNonMatchingElements(server_selector, class_list);
+
+ for (auto c : class_list) {
+ client_classes.addClass(c);
+ }
+ }
+
+ /// @brief Sends query to retrieve a client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be retrieved.
+ /// @return Pointer to the client class or null if the class is not found.
+ ClientClassDefPtr getClientClass4(const ServerSelector& server_selector,
+ const std::string& name) {
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createString(name)
+ };
+ ClientClassDictionary client_classes;
+ getClientClasses4(MySqlConfigBackendDHCPv4Impl::GET_CLIENT_CLASS4_NAME,
+ server_selector, in_bindings, client_classes);
+ return (client_classes.getClasses()->empty() ? ClientClassDefPtr() :
+ (*client_classes.getClasses()->begin()));
+ }
+
+ /// @brief Sends query to retrieve all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @param [out] client_classes Reference to the client classes collection
+ /// where retrieved classes will be stored.
+ void getAllClientClasses4(const ServerSelector& server_selector,
+ ClientClassDictionary& client_classes) {
+ MySqlBindingCollection in_bindings;
+ getClientClasses4(server_selector.amUnassigned() ?
+ MySqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4_UNASSIGNED :
+ MySqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4,
+ server_selector, in_bindings, client_classes);
+ }
+
+ /// @brief Sends query to retrieve modified client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_ts Lower bound modification timestamp.
+ /// @param [out] client_classes Reference to the client classes collection
+ /// where retrieved classes will be stored.
+ void getModifiedClientClasses4(const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_ts,
+ ClientClassDictionary& client_classes) {
+ if (server_selector.amAny()) {
+ isc_throw(InvalidOperation, "fetching modified client classes for ANY "
+ "server is not supported");
+ }
+
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createTimestamp(modification_ts)
+ };
+ getClientClasses4(server_selector.amUnassigned() ?
+ GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED :
+ GET_MODIFIED_CLIENT_CLASSES4,
+ server_selector,
+ in_bindings,
+ client_classes);
+ }
+
+
+ /// @brief Upserts client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Pointer to the upserted client class.
+ /// @param follow_client_class name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ void createUpdateClientClass4(const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_client_class) {
+ // We need to evaluate class expression to see if it references any
+ // other classes (dependencies). As part of this evaluation we will
+ // also check if the client class depends on KNOWN/UNKNOWN built-in
+ // classes.
+ std::list<std::string> dependencies;
+ auto depend_on_known = false;
+ if (!client_class->getTest().empty()) {
+ ExpressionPtr expression;
+ ExpressionParser parser;
+ // Parse the test expression. The callback function is normally used to
+ // interrupt config file parsing when one of the classes refers to a
+ // non-existing client class. It returns false in this case. Here,
+ // we use the callback to capture client classes referenced by the
+ // upserted client class and record whether this class depends on
+ // KNOWN/UNKNOWN built-ins. The callback always returns true to avoid
+ // reporting the parsing error. The dependency check is performed later
+ // at the database level.
+ parser.parse(expression, Element::create(client_class->getTest()), AF_INET,
+ [&dependencies, &depend_on_known](const ClientClass& client_class) -> bool {
+ if (isClientClassBuiltIn(client_class)) {
+ if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) {
+ depend_on_known = true;
+ }
+ } else {
+ dependencies.push_back(client_class);
+ }
+ return (true);
+ });
+ }
+
+
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createString(client_class->getName()),
+ MySqlBinding::createString(client_class->getTest()),
+ MySqlBinding::createInteger<uint32_t>(client_class->getNextServer().toUint32()),
+ MySqlBinding::createString(client_class->getSname()),
+ MySqlBinding::createString(client_class->getFilename()),
+ MySqlBinding::createBool(client_class->getRequired()),
+ MySqlBinding::createInteger<uint32_t>(client_class->getValid()),
+ MySqlBinding::createInteger<uint32_t>(client_class->getValid().getMin()),
+ MySqlBinding::createInteger<uint32_t>(client_class->getValid().getMax()),
+ MySqlBinding::createBool(depend_on_known),
+ (follow_client_class.empty() ? MySqlBinding::createNull() :
+ MySqlBinding::createString(follow_client_class)),
+ MySqlBinding::createTimestamp(client_class->getModificationTime()),
+ };
+
+ MySqlTransaction transaction(conn_);
+
+ ScopedAuditRevision audit_revision(this, MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
+ server_selector, "client class set", true);
+ // Keeps track of whether the client class is inserted or updated.
+ auto update = false;
+ try {
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4, in_bindings);
+
+ } catch (const DuplicateEntry&) {
+ // Such class already exists.
+
+ // Delete options and option definitions. They will be re-created from the new class
+ // instance.
+ deleteOptions4(ServerSelector::ANY(), client_class);
+ deleteOptionDefs4(ServerSelector::ANY(), client_class);
+
+ // Try to update the class.
+ in_bindings.push_back(MySqlBinding::createString(client_class->getName()));
+ conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4, in_bindings);
+
+ // Delete class associations with the servers and dependencies. We will re-create
+ // them according to the new class specification.
+ MySqlBindingCollection in_assoc_bindings = {
+ MySqlBinding::createString(client_class->getName())
+ };
+ conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_DEPENDENCY,
+ in_assoc_bindings);
+ conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_SERVER,
+ in_assoc_bindings);
+ update = true;
+ }
+
+ // Associate client class with the servers.
+ attachElementToServers(MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_SERVER,
+ server_selector,
+ MySqlBinding::createString(client_class->getName()),
+ MySqlBinding::createTimestamp(client_class->getModificationTime()));
+
+ // Iterate over the captured dependencies and try to insert them into the database.
+ for (auto dependency : dependencies) {
+ try {
+ MySqlBindingCollection in_dependency_bindings = {
+ MySqlBinding::createString(client_class->getName()),
+ MySqlBinding::createString(dependency)
+ };
+ // We deleted earlier dependencies, so we can simply insert new ones.
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_DEPENDENCY,
+ in_dependency_bindings);
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidOperation, "unmet dependency on client class: " << dependency);
+ }
+ }
+
+ // If we performed client class update we also have to verify that its dependency
+ // on KNOWN/UNKNOWN client classes hasn't changed.
+ if (update) {
+ MySqlBindingCollection in_check_bindings;
+ conn_.insertQuery(MySqlConfigBackendDHCPv4Impl::CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
+ in_check_bindings);
+ }
+
+ // (Re)create option definitions.
+ if (client_class->getCfgOptionDef()) {
+ auto option_defs = client_class->getCfgOptionDef()->getContainer();
+ auto option_spaces = option_defs.getOptionSpaceNames();
+ for (auto option_space : option_spaces) {
+ OptionDefContainerPtr defs = option_defs.getItems(option_space);
+ for (auto def = defs->begin(); def != defs->end(); ++def) {
+ createUpdateOptionDef4(server_selector, *def, client_class->getName());
+ }
+ }
+ }
+
+ // (Re)create options.
+ auto option_spaces = client_class->getCfgOption()->getOptionSpaceNames();
+ for (auto option_space : option_spaces) {
+ OptionContainerPtr options = client_class->getCfgOption()->getAll(option_space);
+ for (auto desc = options->begin(); desc != options->end(); ++desc) {
+ OptionDescriptorPtr desc_copy = OptionDescriptor::create(*desc);
+ desc_copy->space_name_ = option_space;
+ createUpdateOption4(server_selector, client_class, desc_copy);
+ }
+ }
+
+ // All ok. Commit the transaction.
+ transaction.commit();
+ }
+
+ /// @brief Removes client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Removed client class name.
+ /// @return Number of deleted client classes.
+ uint64_t deleteClientClass4(const ServerSelector& server_selector,
+ const std::string& name) {
+ int index = server_selector.amAny() ?
+ MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_ANY :
+ MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4;
+
+ uint64_t result = deleteTransactional(index, server_selector,
+ "deleting client class",
+ "client class deleted",
+ true,
+ name);
+ return (result);
+ }
+
/// @brief Removes unassigned global parameters, global options and
/// option definitions.
///
@@ -2434,7 +2949,10 @@ TaggedStatementArray tagged_statements = { {
{ MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
"CALL createAuditRevisionDHCP4(?, ?, ?, ?)"
},
-
+ // Verify that dependency on KNOWN/UNKNOWN class has not changed.
+ { MySqlConfigBackendDHCPv4Impl::CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE,
+ "CALL checkDHCPv4ClientClassKnownDependencyChange()"
+ },
// Select global parameter by name.
{ MySqlConfigBackendDHCPv4Impl::GET_GLOBAL_PARAMETER4,
MYSQL_GET_GLOBAL_PARAMETER(dhcp4, AND g.name = ?)
@@ -2596,6 +3114,31 @@ TaggedStatementArray tagged_statements = { {
MYSQL_GET_OPTION4(AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?)
},
+ // Select a client class by name.
+ { MySqlConfigBackendDHCPv4Impl::GET_CLIENT_CLASS4_NAME,
+ MYSQL_GET_CLIENT_CLASS4_WITH_TAG(WHERE c.name = ?)
+ },
+
+ // Select all client classes.
+ { MySqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4,
+ MYSQL_GET_CLIENT_CLASS4_WITH_TAG()
+ },
+
+ // Select all unassigned client classes.
+ { MySqlConfigBackendDHCPv4Impl::GET_ALL_CLIENT_CLASSES4_UNASSIGNED,
+ MYSQL_GET_CLIENT_CLASS4_UNASSIGNED()
+ },
+
+ // Select modified client classes.
+ { MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4,
+ MYSQL_GET_CLIENT_CLASS4_WITH_TAG(WHERE c.modification_ts >= ?)
+ },
+
+ // Select modified client classes.
+ { MySqlConfigBackendDHCPv4Impl::GET_MODIFIED_CLIENT_CLASSES4_UNASSIGNED,
+ MYSQL_GET_CLIENT_CLASS4_UNASSIGNED(AND c.modification_ts >= ?)
+ },
+
// Retrieves the most recent audit entries.
{ MySqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
MYSQL_GET_AUDIT_ENTRIES_TIME(dhcp4)
@@ -2720,6 +3263,11 @@ TaggedStatementArray tagged_statements = { {
MYSQL_INSERT_OPTION_DEF(dhcp4)
},
+ // Insert option definition for client class.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_CLIENT_CLASS,
+ MYSQL_INSERT_OPTION_DEF_CLIENT_CLASS(dhcp4)
+ },
+
// Insert association of the option definition with a server.
{ MySqlConfigBackendDHCPv4Impl::INSERT_OPTION_DEF4_SERVER,
MYSQL_INSERT_OPTION_DEF_SERVER(dhcp4)
@@ -2734,7 +3282,31 @@ TaggedStatementArray tagged_statements = { {
{ MySqlConfigBackendDHCPv4Impl::INSERT_OPTION4_SERVER,
MYSQL_INSERT_OPTION_SERVER(dhcp4)
},
-
+ // Insert client class.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4,
+ "INSERT INTO dhcp4_client_class("
+ " name,"
+ " test,"
+ " next_server,"
+ " server_hostname,"
+ " boot_file_name,"
+ " only_if_required,"
+ " valid_lifetime,"
+ " min_valid_lifetime,"
+ " max_valid_lifetime,"
+ " depend_on_known_directly,"
+ " follow_class_name,"
+ " modification_ts"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+ },
+ // Insert association of a client class with a server.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_SERVER,
+ MYSQL_INSERT_CLIENT_CLASS_SERVER(dhcp4)
+ },
+ // Insert client class dependency.
+ { MySqlConfigBackendDHCPv4Impl::INSERT_CLIENT_CLASS4_DEPENDENCY,
+ MYSQL_INSERT_CLIENT_CLASS_DEPENDENCY(dhcp4)
+ },
// Insert server with server tag and description.
{ MySqlConfigBackendDHCPv4Impl::INSERT_SERVER4,
MYSQL_INSERT_SERVER(dhcp4)
@@ -2827,6 +3399,11 @@ TaggedStatementArray tagged_statements = { {
MYSQL_UPDATE_OPTION_DEF(dhcp4)
},
+ // Update existing option definition.
+ { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION_DEF4_CLIENT_CLASS,
+ MYSQL_UPDATE_OPTION_DEF_CLIENT_CLASS(dhcp4)
+ },
+
// Update existing global option.
{ MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4,
MYSQL_UPDATE_OPTION4_WITH_TAG(AND o.scope_id = 0 AND o.code = ? AND o.space = ?)
@@ -2847,6 +3424,28 @@ TaggedStatementArray tagged_statements = { {
MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?)
},
+ // Update existing client class level option.
+ { MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4_CLIENT_CLASS,
+ MYSQL_UPDATE_OPTION4_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = ? AND o.code = ? AND o.space = ?)
+ },
+
+ { MySqlConfigBackendDHCPv4Impl::UPDATE_CLIENT_CLASS4,
+ "UPDATE dhcp4_client_class SET"
+ " name = ?,"
+ " test = ?,"
+ " next_server = ?,"
+ " server_hostname = ?,"
+ " boot_file_name = ?,"
+ " only_if_required = ?,"
+ " valid_lifetime = ?,"
+ " min_valid_lifetime = ?,"
+ " max_valid_lifetime = ?,"
+ " depend_on_known_directly = ?,"
+ " follow_class_name = ?,"
+ " modification_ts = ? "
+ "WHERE name = ?"
+ },
+
// Update existing server, e.g. server description.
{ MySqlConfigBackendDHCPv4Impl::UPDATE_SERVER4,
MYSQL_UPDATE_SERVER(dhcp4)
@@ -2952,6 +3551,11 @@ TaggedStatementArray tagged_statements = { {
MYSQL_DELETE_OPTION_DEF_UNASSIGNED(dhcp4)
},
+ // Delete client class specific option definitions.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION_DEFS4_CLIENT_CLASS,
+ MYSQL_DELETE_OPTION_DEFS_CLIENT_CLASS(dhcp4)
+ },
+
// Delete single global option.
{ MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4,
MYSQL_DELETE_OPTION_WITH_TAG(dhcp4, AND o.scope_id = 0 AND o.code = ? AND o.space = ?)
@@ -2989,6 +3593,41 @@ TaggedStatementArray tagged_statements = { {
MYSQL_DELETE_OPTION_NO_TAG(dhcp4, WHERE o.scope_id = 4 AND o.shared_network_name = ?)
},
+ // Delete options belonging to a client class.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_OPTIONS4_CLIENT_CLASS,
+ MYSQL_DELETE_OPTION_NO_TAG(dhcp4, WHERE o.scope_id = 2 AND o.dhcp_client_class = ?)
+ },
+
+ // Delete all dependencies of a client class.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_DEPENDENCY,
+ MYSQL_DELETE_CLIENT_CLASS_DEPENDENCY(dhcp4)
+ },
+
+ // Delete associations of a client class with server.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_SERVER,
+ MYSQL_DELETE_CLIENT_CLASS_SERVER(dhcp4),
+ },
+
+ // Delete all client classes.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4,
+ MYSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp4)
+ },
+
+ // Delete all unassigned client classes.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED,
+ MYSQL_DELETE_CLIENT_CLASS_UNASSIGNED(dhcp4)
+ },
+
+ // Delete specified client class.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4,
+ MYSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp4, AND name = ?)
+ },
+
+ // Delete any client class with a given name.
+ { MySqlConfigBackendDHCPv4Impl::DELETE_CLIENT_CLASS4_ANY,
+ MYSQL_DELETE_CLIENT_CLASS_ANY(dhcp4, AND name = ?)
+ },
+
// Delete a server by tag.
{ MySqlConfigBackendDHCPv4Impl::DELETE_SERVER4,
MYSQL_DELETE_SERVER(dhcp4)
@@ -3223,20 +3862,33 @@ MySqlConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector&
}
ClientClassDefPtr
-MySqlConfigBackendDHCPv4::getClientClientClass4(const db::ServerSelector& selector,
- const std::string& name) const {
- isc_throw(NotImplemented, "getClientClass4 is not implemented");
+MySqlConfigBackendDHCPv4::getClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_CLIENT_CLASS4)
+ .arg(name);
+ return (impl_->getClientClass4(server_selector, name));
}
ClientClassDictionary
-MySqlConfigBackendDHCPv4::getAllClientClasses4(const db::ServerSelector& selector) const {
- isc_throw(NotImplemented, "getAllClientClasses4 is not implemented");
+MySqlConfigBackendDHCPv4::getAllClientClasses4(const db::ServerSelector& server_selector) const {
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_ALL_CLIENT_CLASSES4);
+ ClientClassDictionary client_classes;
+ impl_->getAllClientClasses4(server_selector, client_classes);
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT)
+ .arg(client_classes.getClasses()->size());
+ return (client_classes);
}
ClientClassDictionary
-MySqlConfigBackendDHCPv4::getModifiedClientClasses4(const db::ServerSelector& selector,
+MySqlConfigBackendDHCPv4::getModifiedClientClasses4(const db::ServerSelector& server_selector,
const boost::posix_time::ptime& modification_time) const {
- isc_throw(NotImplemented, "getModifiedClientClasses4 is not implemented");
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4)
+ .arg(util::ptimeToText(modification_time));
+ ClientClassDictionary client_classes;
+ impl_->getModifiedClientClasses4(server_selector, modification_time, client_classes);
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT)
+ .arg(client_classes.getClasses()->size());
+ return (client_classes);
}
AuditEntryCollection
@@ -3345,11 +3997,13 @@ MySqlConfigBackendDHCPv4::createUpdateGlobalParameter4(const ServerSelector& ser
void
MySqlConfigBackendDHCPv4::createUpdateClientClass4(const db::ServerSelector& server_selector,
- const ClientClassDefPtr& client_class) {
- isc_throw(NotImplemented, "createUpdateClientClass4 is not implemented");
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_client_class) {
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4)
+ .arg(client_class->getName());
+ impl_->createUpdateClientClass4(server_selector, client_class, follow_client_class);
}
-
void
MySqlConfigBackendDHCPv4::createUpdateServer4(const ServerPtr& server) {
LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_CREATE_UPDATE_SERVER4)
@@ -3573,12 +4227,26 @@ MySqlConfigBackendDHCPv4::deleteAllGlobalParameters4(const ServerSelector& serve
uint64_t
MySqlConfigBackendDHCPv4::deleteClientClass4(const db::ServerSelector& server_selector,
const std::string& name) {
- isc_throw(NotImplemented, "deleteClientClass4 is not implemented");
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_CLIENT_CLASS4)
+ .arg(name);
+ auto result = impl_->deleteClientClass4(server_selector, name);
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT)
+ .arg(result);
+ return (result);
}
uint64_t
MySqlConfigBackendDHCPv4::deleteAllClientClasses4(const db::ServerSelector& server_selector) {
- isc_throw(NotImplemented, "deleteAllClientClasses4 is not implemented");
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4);
+
+ int index = (server_selector.amUnassigned() ?
+ MySqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4_UNASSIGNED :
+ MySqlConfigBackendDHCPv4Impl::DELETE_ALL_CLIENT_CLASSES4);
+ uint64_t result = impl_->deleteTransactional(index, server_selector, "deleting all client classes",
+ "deleted all client classes", true);
+ LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT)
+ .arg(result);
+ return (result);
}
uint64_t
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
index 26355c9a04..b006382c5d 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
@@ -232,7 +232,7 @@ public:
/// @param name Client class name.
/// @return Pointer to the retrieved client class.
virtual ClientClassDefPtr
- getClientClientClass4(const db::ServerSelector& selector, const std::string& name) const;
+ getClientClass4(const db::ServerSelector& selector, const std::string& name) const;
/// @brief Retrieves all client classes.
///
@@ -369,9 +369,14 @@ public:
///
/// @param server_selector Server selector.
/// @param client_class Client class to be added or updated.
+ /// @param follow_client_class name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
virtual void
createUpdateClientClass4(const db::ServerSelector& server_selector,
- const ClientClassDefPtr& client_class);
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_client_class);
/// @brief Creates or updates a server.
///
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
index c9b596939a..5a2f6f06c1 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
@@ -50,7 +50,7 @@ MySqlConfigBackendImpl(const DatabaseConnection::ParameterMap& parameters,
: conn_(parameters,
IOServiceAccessorPtr(new IOServiceAccessor(MySqlConfigBackendImpl::getIOService)),
db_reconnect_callback), timer_name_(""),
- audit_revision_created_(false), parameters_(parameters) {
+ audit_revision_ref_count_(0), parameters_(parameters) {
// Test schema version first.
std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
MYSQL_SCHEMA_VERSION_MINOR);
@@ -152,7 +152,7 @@ MySqlConfigBackendImpl::createAuditRevision(const int index,
const std::string& log_message,
const bool cascade_transaction) {
// Do not touch existing audit revision in case of the cascade update.
- if (audit_revision_created_) {
+ if (++audit_revision_ref_count_ > 1) {
return;
}
@@ -175,12 +175,13 @@ MySqlConfigBackendImpl::createAuditRevision(const int index,
MySqlBinding::createInteger<uint8_t>(static_cast<uint8_t>(cascade_transaction))
};
conn_.insertQuery(index, in_bindings);
- audit_revision_created_ = true;
}
void
MySqlConfigBackendImpl::clearAuditRevision() {
- audit_revision_created_ = false;
+ if (audit_revision_ref_count_ > 0) {
+ --audit_revision_ref_count_;
+ }
}
void
@@ -406,7 +407,7 @@ MySqlConfigBackendImpl::getOptionDefs(const int index,
// Run select query.
conn_.selectQuery(index, in_bindings, out_bindings,
- [&local_option_defs, &last_def_id]
+ [this, &local_option_defs, &last_def_id]
(MySqlBindingCollection& out_bindings) {
// Get pointer to last fetched option definition.
OptionDefinitionPtr last_def;
@@ -422,51 +423,7 @@ MySqlConfigBackendImpl::getOptionDefs(const int index,
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 = OptionDefinition::create(out_bindings[2]->getString(),
- out_bindings[1]->getInteger<uint16_t>(),
- out_bindings[3]->getString(),
- static_cast<OptionDataType>
- (out_bindings[4]->getInteger<uint8_t>()),
- array_type);
- } else {
- // Create non-array option.
- last_def = OptionDefinition::create(out_bindings[2]->getString(),
- out_bindings[1]->getInteger<uint16_t>(),
- out_bindings[3]->getString(),
- static_cast<OptionDataType>
- (out_bindings[4]->getInteger<uint8_t>()),
- out_bindings[7]->getStringOrDefault("").c_str());
- }
-
- // id
- last_def->setId(last_def_id);
-
- // 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());
+ last_def = processOptionDefRow(out_bindings.begin());
// server_tag
ServerTag last_def_server_tag(out_bindings[10]->getString());
@@ -520,7 +477,8 @@ MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s
const int& insert_option_def,
const int& update_option_def,
const int& create_audit_revision,
- const int& insert_option_def_server) {
+ const int& insert_option_def_server,
+ const std::string& client_class_name) {
if (server_selector.amUnassigned()) {
isc_throw(NotImplemented, "managing configuration for no particular server"
@@ -536,6 +494,9 @@ MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s
MySqlBindingPtr record_types_binding = record_types->empty() ?
MySqlBinding::createNull() : MySqlBinding::createString(record_types->str());
+ MySqlBindingPtr client_class_binding = client_class_name.empty() ?
+ MySqlBinding::createNull() : MySqlBinding::createString(client_class_name);
+
MySqlBindingCollection in_bindings = {
MySqlBinding::createInteger<uint16_t>(option_def->getCode()),
MySqlBinding::createString(option_def->getName()),
@@ -546,9 +507,10 @@ MySqlConfigBackendImpl::createUpdateOptionDef(const db::ServerSelector& server_s
MySqlBinding::createString(option_def->getEncapsulatedSpace()),
record_types_binding,
createInputContextBinding(option_def),
+ client_class_binding,
MySqlBinding::createString(tag),
MySqlBinding::createInteger<uint16_t>(option_def->getCode()),
- MySqlBinding::createString(option_def->getOptionSpaceName())
+ MySqlBinding::createString(option_def->getOptionSpaceName()),
};
MySqlTransaction transaction(conn_);
@@ -895,6 +857,58 @@ MySqlConfigBackendImpl::processOptionRow(const Option::Universe& universe,
return (desc);
}
+OptionDefinitionPtr
+MySqlConfigBackendImpl::processOptionDefRow(MySqlBindingCollection::iterator first_binding) {
+ OptionDefinitionPtr def;
+
+ // Check array type, because depending on this value we have to use
+ // different constructor.
+ bool array_type = static_cast<bool>((*(first_binding + 6))->getInteger<uint8_t>());
+ if (array_type) {
+ // Create array option.
+ def = OptionDefinition::create((*(first_binding + 2))->getString(),
+ (*(first_binding + 1))->getInteger<uint16_t>(),
+ (*(first_binding + 3))->getString(),
+ static_cast<OptionDataType>
+ ((*(first_binding + 4))->getInteger<uint8_t>()),
+ array_type);
+ } else {
+ // Create non-array option.
+ def = OptionDefinition::create((*(first_binding + 2))->getString(),
+ (*(first_binding + 1))->getInteger<uint16_t>(),
+ (*(first_binding + 3))->getString(),
+ static_cast<OptionDataType>
+ ((*(first_binding + 4))->getInteger<uint8_t>()),
+ (*(first_binding + 7))->getStringOrDefault("").c_str());
+ }
+
+ // id
+ def->setId((*(first_binding))->getInteger<uint64_t>());
+
+ // record_types
+ ElementPtr record_types_element = (*(first_binding + 8))->getJSON();
+ if (record_types_element) {
+ if (record_types_element->getType() != Element::list) {
+ isc_throw(BadValue, "invalid record_types value "
+ << (*(first_binding + 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");
+ }
+ def->addRecordField(static_cast<OptionDataType>(type_element->intValue()));
+ }
+ }
+
+ // Update modification time.
+ def->setModificationTime((*(first_binding + 5))->getTimestamp());
+
+ return (def);
+}
+
void
MySqlConfigBackendImpl::attachElementToServers(const int index,
const ServerSelector& server_selector,
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
index 7aa14bf26a..1a70b5a799 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_impl.h
@@ -421,6 +421,9 @@ public:
/// @param create_audit_revision Statement creating audit revision.
/// @param insert_option_def_server Statement associating option
/// definition with a server.
+ /// @param client_class_name Optional client class name to which
+ /// the option definition belongs. If this value is not specified,
+ /// it is a global option definition.
/// @throw NotImplemented if server selector is "unassigned".
void createUpdateOptionDef(const db::ServerSelector& server_selector,
const OptionDefinitionPtr& option_def,
@@ -429,7 +432,8 @@ public:
const int& insert_option_def,
const int& update_option_def,
const int& create_audit_revision,
- const int& insert_option_def_server);
+ const int& insert_option_def_server,
+ const std::string& client_class_name = "");
/// @brief Sends query to retrieve single global option by code and
/// option space.
@@ -573,6 +577,27 @@ public:
processOptionRow(const Option::Universe& universe,
db::MySqlBindingCollection::iterator first_binding);
+ /// @brief Returns DHCP option definition instance from output bindings.
+ ///
+ /// The following is the expected order of columns specified in the SELECT
+ /// query:
+ /// - id,
+ /// - code,
+ /// - name,
+ /// - space,
+ /// - type,
+ /// - modification_ts,
+ /// - is_array,
+ /// - encapsulate,
+ /// - record_types,
+ /// - user_context
+ ///
+ /// @param first_binding Iterator of the output binding containing
+ /// option definition id.
+ /// @return Pointer to the option definition.
+ OptionDefinitionPtr
+ processOptionDefRow(db::MySqlBindingCollection::iterator first_binding);
+
/// @brief Associates a configuration element with multiple servers.
///
/// @param index Query index.
@@ -832,9 +857,8 @@ protected:
private:
- /// @brief Boolean flag indicating if audit revision has been created
- /// using @c ScopedAuditRevision object.
- bool audit_revision_created_;
+ /// @brief Reference counter for @ScopedAuditRevision instances.
+ int audit_revision_ref_count_;
/// @brief Connection parameters
isc::db::DatabaseConnection::ParameterMap parameters_;
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc
index dda3cff55e..b934cf793c 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc
@@ -12,6 +12,7 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_POOL_OPTION6 = "MYSQL
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6 = "MYSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4 = "MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6 = "MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6";
+extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4 = "MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4 = "MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6 = "MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_OPTION4 = "MYSQL_CB_CREATE_UPDATE_OPTION4";
@@ -27,6 +28,8 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SHARED_NETWORK_OPTION6 =
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SUBNET4 = "MYSQL_CB_CREATE_UPDATE_SUBNET4";
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SUBNET6 = "MYSQL_CB_CREATE_UPDATE_SUBNET6";
extern const isc::log::MessageID MYSQL_CB_DEINIT_OK = "MYSQL_CB_DEINIT_OK";
+extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4 = "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4";
+extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT = "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4 = "MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4";
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT = "MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6 = "MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6";
@@ -65,6 +68,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4 = "MYSQL_C
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT = "MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6 = "MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6";
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT = "MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT";
+extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS4 = "MYSQL_CB_DELETE_CLIENT_CLASS4";
+extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT = "MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER4 = "MYSQL_CB_DELETE_GLOBAL_PARAMETER4";
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT = "MYSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER6 = "MYSQL_CB_DELETE_GLOBAL_PARAMETER6";
@@ -93,6 +98,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4 = "MYSQ
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT = "MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6 = "MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6";
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT = "MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT";
+extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES4 = "MYSQL_CB_GET_ALL_CLIENT_CLASSES4";
+extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT = "MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT";
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4 = "MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4";
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT = "MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS6 = "MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS6";
@@ -117,10 +124,13 @@ extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS4 = "MYSQL_CB_GET_ALL_S
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS4_RESULT = "MYSQL_CB_GET_ALL_SUBNETS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS6 = "MYSQL_CB_GET_ALL_SUBNETS6";
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS6_RESULT = "MYSQL_CB_GET_ALL_SUBNETS6_RESULT";
+extern const isc::log::MessageID MYSQL_CB_GET_CLIENT_CLASS4 = "MYSQL_CB_GET_CLIENT_CLASS4";
extern const isc::log::MessageID MYSQL_CB_GET_GLOBAL_PARAMETER4 = "MYSQL_CB_GET_GLOBAL_PARAMETER4";
extern const isc::log::MessageID MYSQL_CB_GET_GLOBAL_PARAMETER6 = "MYSQL_CB_GET_GLOBAL_PARAMETER6";
extern const isc::log::MessageID MYSQL_CB_GET_HOST4 = "MYSQL_CB_GET_HOST4";
extern const isc::log::MessageID MYSQL_CB_GET_HOST6 = "MYSQL_CB_GET_HOST6";
+extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4 = "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4";
+extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT = "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT";
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4 = "MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4";
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT = "MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT";
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6 = "MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6";
@@ -188,6 +198,7 @@ const char* values[] = {
"MYSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6", "create or update option prefix: %1 prefix len: %2",
"MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4", "create or update option by subnet id: %1",
"MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6", "create or update option by subnet id: %1",
+ "MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4", "create or update client class: %1",
"MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4", "create or update global parameter: %1",
"MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6", "create or update global parameter: %1",
"MYSQL_CB_CREATE_UPDATE_OPTION4", "create or update option",
@@ -203,6 +214,8 @@ const char* values[] = {
"MYSQL_CB_CREATE_UPDATE_SUBNET4", "create or update subnet: %1",
"MYSQL_CB_CREATE_UPDATE_SUBNET6", "create or update subnet: %1",
"MYSQL_CB_DEINIT_OK", "unloading MYSQL CB hooks library successful",
+ "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4", "delete all client classes",
+ "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4", "delete all global parameters",
"MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6", "delete all global parameters",
@@ -241,6 +254,8 @@ const char* values[] = {
"MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6", "delete subnet by subnet id: %1",
"MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT", "deleted: %1 entries",
+ "MYSQL_CB_DELETE_CLIENT_CLASS4", "delete client class: %1",
+ "MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_GLOBAL_PARAMETER4", "delete global parameter: %1",
"MYSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_GLOBAL_PARAMETER6", "delete global parameter: %1",
@@ -269,6 +284,8 @@ const char* values[] = {
"MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT", "deleted: %1 entries",
"MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6", "delete shared network: %1 subnets",
"MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT", "deleted: %1 entries",
+ "MYSQL_CB_GET_ALL_CLIENT_CLASSES4", "retrieving all client classes",
+ "MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT", "retrieving: %1 elements",
"MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4", "retrieving all global parameters",
"MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT", "retrieving: %1 elements",
"MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS6", "retrieving all global parameters",
@@ -293,10 +310,13 @@ const char* values[] = {
"MYSQL_CB_GET_ALL_SUBNETS4_RESULT", "retrieving: %1 elements",
"MYSQL_CB_GET_ALL_SUBNETS6", "retrieving all subnets",
"MYSQL_CB_GET_ALL_SUBNETS6_RESULT", "retrieving: %1 elements",
+ "MYSQL_CB_GET_CLIENT_CLASS4", "retrieving client class: %1",
"MYSQL_CB_GET_GLOBAL_PARAMETER4", "retrieving global parameter: %1",
"MYSQL_CB_GET_GLOBAL_PARAMETER6", "retrieving global parameter: %1",
"MYSQL_CB_GET_HOST4", "get host",
"MYSQL_CB_GET_HOST6", "get host",
+ "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4", "retrieving modified client classes from: %1",
+ "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT", "retrieving: %1 elements",
"MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4", "retrieving modified global parameters from: %1",
"MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT", "retrieving: %1 elements",
"MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6", "retrieving modified global parameters from: %1",
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h
index 1e8ce7f7f6..1b51c564e0 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h
@@ -13,6 +13,7 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_POOL_OPTION6;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION4;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6;
+extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER6;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_OPTION4;
@@ -28,6 +29,8 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SHARED_NETWORK_OPTION6;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SUBNET4;
extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SUBNET6;
extern const isc::log::MessageID MYSQL_CB_DEINIT_OK;
+extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4;
+extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4;
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS6;
@@ -66,6 +69,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4;
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6;
extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT;
+extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS4;
+extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER4;
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_GLOBAL_PARAMETER6;
@@ -94,6 +99,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4;
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6;
extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT;
+extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES4;
+extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS6;
@@ -118,10 +125,13 @@ extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS4;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS6;
extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS6_RESULT;
+extern const isc::log::MessageID MYSQL_CB_GET_CLIENT_CLASS4;
extern const isc::log::MessageID MYSQL_CB_GET_GLOBAL_PARAMETER4;
extern const isc::log::MessageID MYSQL_CB_GET_GLOBAL_PARAMETER6;
extern const isc::log::MessageID MYSQL_CB_GET_HOST4;
extern const isc::log::MessageID MYSQL_CB_GET_HOST6;
+extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4;
+extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT;
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4;
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4_RESULT;
extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS6;
diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes
index d92edc3c07..ab7458eb96 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes
+++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes
@@ -17,6 +17,8 @@ Debug message issued when triggered an action to create or update option by subn
% MYSQL_CB_CREATE_UPDATE_BY_SUBNET_ID_OPTION6 create or update option by subnet id: %1
Debug message issued when triggered an action to create or update option by subnet id
+% MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4 create or update client class: %1
+
% MYSQL_CB_CREATE_UPDATE_GLOBAL_PARAMETER4 create or update global parameter: %1
Debug message issued when triggered an action to create or update global parameter
@@ -65,6 +67,12 @@ Debug message issued when triggered an action to create or update subnet
This informational message indicates that the MySQL Configuration Backend hooks
library has been unloaded successfully.
+% MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4 delete all client classes
+Debug message issued when triggered an action to delete all client classes
+
+% MYSQL_CB_DELETE_ALL_CLIENT_CLASSES4_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete all client classes
+
% MYSQL_CB_DELETE_ALL_GLOBAL_PARAMETERS4 delete all global parameters
Debug message issued when triggered an action to delete all global parameters
@@ -179,6 +187,12 @@ Debug message issued when triggered an action to delete subnet by subnet id
% MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6_RESULT deleted: %1 entries
Debug message indicating the result of an action to delete subnet by subnet id
+% MYSQL_CB_DELETE_CLIENT_CLASS4 delete client class: %1
+Debug message issued when triggered an action to delete client class
+
+% MYSQL_CB_DELETE_CLIENT_CLASS4_RESULT deleted: %1 entries
+Debug message indicating the result of an action to delete client class
+
% MYSQL_CB_DELETE_GLOBAL_PARAMETER4 delete global parameter: %1
Debug message issued when triggered an action to delete global parameter
@@ -263,6 +277,12 @@ Debug message issued when triggered an action to delete shared network subnets
% MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6_RESULT deleted: %1 entries
Debug message indicating the result of an action to delete shared network subnets
+% MYSQL_CB_GET_ALL_CLIENT_CLASSES4 retrieving all client classes
+Debug message issued when triggered an action to retrieve all client classes
+
+% MYSQL_CB_GET_ALL_CLIENT_CLASSES4_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve all client classes
+
% MYSQL_CB_GET_ALL_GLOBAL_PARAMETERS4 retrieving all global parameters
Debug message issued when triggered an action to retrieve all global parameters
@@ -339,6 +359,9 @@ Debug message issued when triggered an action to retrieve all subnets
% MYSQL_CB_GET_ALL_SUBNETS6_RESULT retrieving: %1 elements
Debug message indicating the result of an action to retrieve all subnets
+% MYSQL_CB_GET_CLIENT_CLASS4 retrieving client class: %1
+Debug message issued when triggered an action to retrieve a client class
+
% MYSQL_CB_GET_GLOBAL_PARAMETER4 retrieving global parameter: %1
Debug message issued when triggered an action to retrieve global parameter
@@ -351,6 +374,12 @@ Debug message issued when triggered an action to retrieve host
% MYSQL_CB_GET_HOST6 get host
Debug message issued when triggered an action to retrieve host
+% MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4 retrieving modified client classes from: %1
+Debug message issued when triggered an action to retrieve modified client classes from specified time
+
+% MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES4_RESULT retrieving: %1 elements
+Debug message indicating the result of an action to retrieve modified client classes from specified time
+
% MYSQL_CB_GET_MODIFIED_GLOBAL_PARAMETERS4 retrieving modified global parameters from: %1
Debug message issued when triggered an action to retrieve modified global parameters from specified time
diff --git a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
index 04f2a907c2..363811b22e 100644
--- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
+++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
@@ -661,6 +661,72 @@ namespace {
MYSQL_GET_SERVERS_COMMON(table_prefix, "AND s.tag = ? ")
#endif
+#ifndef MYSQL_GET_CLIENT_CLASS4_COMMON
+#define MYSQL_GET_CLIENT_CLASS4_COMMON(server_join, ...) \
+ "SELECT " \
+ " c.id," \
+ " c.name," \
+ " c.test," \
+ " c.next_server," \
+ " c.server_hostname," \
+ " c.boot_file_name," \
+ " c.only_if_required," \
+ " c.valid_lifetime," \
+ " c.min_valid_lifetime," \
+ " c.max_valid_lifetime," \
+ " c.depend_on_known_directly," \
+ " o.depend_on_known_indirectly, " \
+ " c.modification_ts," \
+ " d.id," \
+ " d.code," \
+ " d.name," \
+ " d.space," \
+ " d.type," \
+ " d.modification_ts," \
+ " d.is_array," \
+ " d.encapsulate," \
+ " d.record_types," \
+ " d.user_context," \
+ " x.option_id," \
+ " x.code," \
+ " x.value," \
+ " x.formatted_value," \
+ " x.space," \
+ " x.persistent," \
+ " x.dhcp4_subnet_id," \
+ " x.scope_id," \
+ " x.user_context," \
+ " x.shared_network_name," \
+ " x.pool_id," \
+ " x.modification_ts," \
+ " s.tag " \
+ "FROM dhcp4_client_class AS c " \
+ "INNER JOIN dhcp4_client_class_order AS o " \
+ " ON c.id = o.class_id " \
+ server_join \
+ "LEFT JOIN dhcp4_option_def AS d ON c.id = d.class_id " \
+ "LEFT JOIN dhcp4_options AS x ON x.scope_id = 2 AND c.name = x.dhcp_client_class " \
+ #__VA_ARGS__ \
+ " ORDER BY o.order_index, d.id, x.option_id"
+
+#define MYSQL_GET_CLIENT_CLASS4_WITH_TAG(...) \
+ MYSQL_GET_CLIENT_CLASS4_COMMON( \
+ "INNER JOIN dhcp4_client_class_server AS a " \
+ " ON c.id = a.class_id " \
+ "INNER JOIN dhcp4_server AS s " \
+ " ON a.server_id = s.id ", \
+ __VA_ARGS__)
+
+#define MYSQL_GET_CLIENT_CLASS4_UNASSIGNED(...) \
+ MYSQL_GET_CLIENT_CLASS4_COMMON( \
+ "LEFT JOIN dhcp4_client_class_server AS a " \
+ " ON c.id = a.class_id " \
+ "LEFT JOIN dhcp4_server AS s " \
+ " ON a.server_id = s.id ", \
+ WHERE a.class_id IS NULL __VA_ARGS__)
+
+#endif
+
#ifndef MYSQL_INSERT_GLOBAL_PARAMETER
#define MYSQL_INSERT_GLOBAL_PARAMETER(table_prefix) \
"INSERT INTO " #table_prefix "_global_parameter(" \
@@ -741,8 +807,25 @@ namespace {
" is_array," \
" encapsulate," \
" record_types," \
- " user_context" \
- ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
+ " user_context," \
+ " class_id" \
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+#endif
+
+#ifndef MYSQL_INSERT_OPTION_DEF_CLIENT_CLASS
+#define MYSQL_INSERT_OPTION_DEF_CLIENT_CLASS(table_prefix) \
+ "INSERT INTO " #table_prefix "_option_def (" \
+ " code," \
+ " name," \
+ " space," \
+ " type," \
+ " modification_ts," \
+ " is_array," \
+ " encapsulate," \
+ " record_types," \
+ " user_context," \
+ " class_id" \
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT id FROM dhcp4_client_class WHERE name = ?))"
#endif
#ifndef MYSQL_INSERT_OPTION_DEF_SERVER
@@ -787,6 +870,23 @@ namespace {
") VALUES (?, ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
#endif
+#ifndef MYSQL_INSERT_CLIENT_CLASS_SERVER
+#define MYSQL_INSERT_CLIENT_CLASS_SERVER(table_prefix) \
+ "INSERT INTO " #table_prefix "_client_class_server (" \
+ " class_id," \
+ " modification_ts," \
+ " server_id" \
+ ") VALUES ((SELECT id FROM " #table_prefix "_client_class WHERE name = ?), ?, (SELECT id FROM " #table_prefix "_server WHERE tag = ?))"
+#endif
+
+#ifndef MYSQL_INSERT_CLIENT_CLASS_DEPENDENCY
+#define MYSQL_INSERT_CLIENT_CLASS_DEPENDENCY(table_prefix) \
+ "INSERT INTO " #table_prefix "_client_class_dependency (" \
+ " class_id," \
+ " dependency_id" \
+ ") VALUES ((SELECT id FROM " #table_prefix "_client_class WHERE name = ?), (SELECT id FROM " #table_prefix "_client_class WHERE name = ?))"
+#endif
+
#ifndef MYSQL_INSERT_SERVER
#define MYSQL_INSERT_SERVER(table_prefix) \
"INSERT INTO " #table_prefix "_server (" \
@@ -827,10 +927,32 @@ namespace {
" d.is_array = ?," \
" d.encapsulate = ?," \
" d.record_types = ?," \
- " d.user_context = ? " \
+ " d.user_context = ?, " \
+ " d.class_id = ? " \
"WHERE s.tag = ? AND d.code = ? AND d.space = ?"
#endif
+#ifndef MYSQL_UPDATE_OPTION_DEF_CLIENT_CLASS
+#define MYSQL_UPDATE_OPTION_DEF_CLIENT_CLASS(table_prefix) \
+ "UPDATE " #table_prefix "_option_def AS d " \
+ "INNER JOIN " #table_prefix "_option_def_server AS a" \
+ " ON d.id = a.option_def_id " \
+ "INNER JOIN " #table_prefix "_server AS s" \
+ " ON a.server_id = s.id " \
+ "SET" \
+ " d.code = ?," \
+ " d.name = ?," \
+ " d.space = ?," \
+ " d.type = ?," \
+ " d.modification_ts = ?," \
+ " d.is_array = ?," \
+ " d.encapsulate = ?," \
+ " d.record_types = ?," \
+ " d.user_context = ? " \
+ "WHERE d.class_id = (SELECT id FROM dhcp4_client_class WHERE name = ?) " \
+ " AND s.tag = ? AND d.code = ? AND d.space = ?"
+#endif
+
#ifndef MYSQL_UPDATE_OPTION_COMMON
#define MYSQL_UPDATE_OPTION_COMMON(table_prefix, server_join, pd_pool_id, ...) \
"UPDATE " #table_prefix "_options AS o " \
@@ -997,6 +1119,12 @@ namespace {
"WHERE a.option_def_id IS NULL " #__VA_ARGS__
#endif
+#ifndef MYSQL_DELETE_OPTION_DEFS_CLIENT_CLASS
+#define MYSQL_DELETE_OPTION_DEFS_CLIENT_CLASS(table_prefix) \
+ "DELETE FROM " #table_prefix "_option_def " \
+ "WHERE class_id = (SELECT id FROM " #table_prefix "_client_class WHERE name = ?)"
+#endif
+
#ifndef MYSQL_DELETE_OPTION_WITH_TAG
#define MYSQL_DELETE_OPTION_WITH_TAG(table_prefix, ...) \
"DELETE o FROM " #table_prefix "_options AS o " \
@@ -1047,6 +1175,42 @@ namespace {
" WHERE prefix = ? AND prefix_length = ?)"
#endif
+#ifndef MYSQL_DELETE_CLIENT_CLASS_DEPENDENCY
+#define MYSQL_DELETE_CLIENT_CLASS_DEPENDENCY(table_prefix) \
+ "DELETE FROM " #table_prefix "_client_class_dependency " \
+ "WHERE class_id = (SELECT id FROM " #table_prefix "_client_class WHERE name = ?)"
+#endif
+
+#ifndef MYSQL_DELETE_CLIENT_CLASS_SERVER
+#define MYSQL_DELETE_CLIENT_CLASS_SERVER(table_prefix) \
+ "DELETE FROM " #table_prefix "_client_class_server " \
+ "WHERE class_id = " \
+ "(SELECT id FROM " #table_prefix "_client_class WHERE name = ?)"
+#endif
+
+#ifndef MYSQL_DELETE_CLIENT_CLASS_COMMON
+#define MYSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, ...) \
+ "DELETE c FROM " #table_prefix "_client_class AS c " \
+ "INNER JOIN " #table_prefix "_client_class_server AS a" \
+ " ON c.id = a.class_id " \
+ "INNER JOIN " #table_prefix "_server AS s" \
+ " ON a.server_id = s.id " \
+ #__VA_ARGS__
+
+#define MYSQL_DELETE_CLIENT_CLASS_WITH_TAG(table_prefix, ...) \
+ MYSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, WHERE s.tag = ? __VA_ARGS__)
+
+#define MYSQL_DELETE_CLIENT_CLASS_ANY(table_prefix, ...) \
+ MYSQL_DELETE_CLIENT_CLASS_COMMON(table_prefix, __VA_ARGS__)
+
+#define MYSQL_DELETE_CLIENT_CLASS_UNASSIGNED(table_prefix, ...) \
+ "DELETE c FROM " #table_prefix "_client_class AS c " \
+ "LEFT JOIN " #table_prefix "_client_class_server AS a" \
+ " ON c.id = a.class_id " \
+ "WHERE a.class_id IS NULL " #__VA_ARGS__
+
+#endif
+
#ifndef MYSQL_DELETE_SERVER
#define MYSQL_DELETE_SERVER(table_prefix) \
"DELETE FROM " #table_prefix "_server " \
diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
index 1a546e0725..5d8f3b9c52 100644
--- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
+++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
@@ -18,6 +18,7 @@
#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
#include <dhcpsrv/config_backend_dhcp4_mgr.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
@@ -26,6 +27,7 @@
#include <mysql/testutils/mysql_schema.h>
#include <testutils/multi_threading_utils.h>
+#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
#include <mysql.h>
@@ -79,8 +81,8 @@ public:
/// @brief Constructor.
MySqlConfigBackendDHCPv4Test()
: test_subnets_(), test_networks_(), test_option_defs_(),
- test_options_(), test_servers_(), timestamps_(), cbptr_(),
- audit_entries_() {
+ test_options_(), test_client_classes_(), test_servers_(), timestamps_(),
+ cbptr_(), audit_entries_() {
// Ensure we have the proper schema with no transient data.
createMySQLSchema();
@@ -105,6 +107,7 @@ public:
initTestSubnets();
initTestSharedNetworks();
initTestOptionDefs();
+ initTestClientClasses();
initTimestamps();
}
@@ -422,6 +425,27 @@ public:
LibDHCP::setRuntimeOptionDefs(defs);
}
+ /// @brief Creates several client classes used in tests.
+ void initTestClientClasses() {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+ auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+ class1->setRequired(true);
+ class1->setNextServer(IOAddress("1.2.3.4"));
+ class1->setSname("cool");
+ class1->setFilename("epc.cfg");
+ class1->setValid(Triplet<uint32_t>(30, 60, 90));
+ test_client_classes_.push_back(class1);
+
+ auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+ class2->setTest("member('foo')");
+ test_client_classes_.push_back(class2);
+
+ auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+ class3->setTest("member('foo') and member('bar')");
+ test_client_classes_.push_back(class3);
+ }
+
/// @brief Initialize posix time values used in tests.
void initTimestamps() {
// Current time minus 1 hour to make sure it is in the past.
@@ -544,6 +568,9 @@ public:
/// @brief Holds pointers to options used in tests.
std::vector<OptionDescriptorPtr> test_options_;
+ /// @brief Holds pointers to classes used in tests.
+ std::vector<ClientClassDefPtr> test_client_classes_;
+
/// @brief Holds pointers to the servers used in tests.
std::vector<ServerPtr> test_servers_;
@@ -4125,6 +4152,575 @@ TEST_F(MySqlConfigBackendDHCPv4Test, sharedNetworkOptionIdOrder) {
}
}
+// This test verifies that it is possible to create client classes, update them
+// and retrieve all classes for a given server.
+TEST_F(MySqlConfigBackendDHCPv4Test, setAndGetAllClientClasses4) {
+ // Create a server.
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create first class.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create second class.
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create third class.
+ auto class3 = test_client_classes_[2];
+ ASSERT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Update the third class to depend on the second class.
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class bar is updated");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::UPDATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Only the first class should be returned for the server selector ALL.
+ auto client_classes = cbptr_->getAllClientClasses4(ServerSelector::ALL());
+ ASSERT_EQ(1, client_classes.getClasses()->size());
+ // All three classes should be returned for the server1.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ auto classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 2))->getName());
+
+
+ // Move the third class between the first and second class.
+ ASSERT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, "foo"));
+
+ // Ensure that the classes order has changed.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+}
+
+// This test verifies that a single class can be retrieved from the database.
+TEST_F(MySqlConfigBackendDHCPv4Test, getClientClass4) {
+ // Create a server.
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add classes.
+ auto class1 = test_client_classes_[0];
+ EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+
+ // Get the first client class and validate its contents.
+ ClientClassDefPtr client_class;
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+ EXPECT_EQ("foo", client_class->getName());
+ EXPECT_TRUE(client_class->getRequired());
+ EXPECT_EQ("1.2.3.4", client_class->getNextServer().toText());
+ EXPECT_EQ("cool", client_class->getSname());
+ EXPECT_EQ("epc.cfg", client_class->getFilename());
+ EXPECT_EQ(30, client_class->getValid().getMin());
+ EXPECT_EQ(60, client_class->getValid().get());
+ EXPECT_EQ(90, client_class->getValid().getMax());
+
+ // Validate options belonging to this class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Fetch the same class using different server selectors.
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class1->getName()));
+ EXPECT_FALSE(client_class);
+
+ // Fetch the second client class using different selectors. This time the
+ // class should not be returned for the ALL server selector because it is
+ // associated with the server1.
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ALL(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+}
+
+// This test verifies that client class specific DHCP options can be
+// modified during the class update.
+TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateClientClass4Options) {
+ // Add class with two options and two option definitions.
+ auto class1 = test_client_classes_[0];
+ EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->space_name_));
+ EXPECT_NO_THROW(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->space_name_));
+ auto cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ EXPECT_NO_THROW(class1->getCfgOptionDef()->add(test_option_defs_[0]));
+ EXPECT_NO_THROW(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Fetch the class and the options from the database.
+ ClientClassDefPtr client_class;
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Validate options belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Validate option definitions belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ ASSERT_TRUE(returned_def_foo);
+ EXPECT_EQ(234, returned_def_foo->getCode());
+ EXPECT_EQ("foo", returned_def_foo->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_foo->getOptionSpaceName());
+ EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace());
+ EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType());
+ EXPECT_FALSE(returned_def_foo->getArrayType());
+
+ auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ ASSERT_TRUE(returned_def_fish);
+ EXPECT_EQ(235, returned_def_fish->getCode());
+ EXPECT_EQ("fish", returned_def_fish->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_fish->getOptionSpaceName());
+ EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty());
+ EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType());
+ EXPECT_TRUE(returned_def_fish->getArrayType());
+
+ // Replace client class specific option definitions. Leave only one option
+ // definition.
+ cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ EXPECT_NO_THROW(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+
+ // Delete one of the options and update the class.
+ class1->getCfgOption()->del(test_options_[0]->space_name_,
+ test_options_[0]->option_->getType());
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ EXPECT_NO_THROW(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Ensure that the first option definition is gone.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ EXPECT_FALSE(returned_def_foo);
+
+ // The second option definition should be present.
+ returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ EXPECT_TRUE(returned_def_fish);
+
+ // Make sure that the first option is gone.
+ ASSERT_TRUE(client_class->getCfgOption());
+ returned_opt_boot_file_name = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_BOOT_FILE_NAME);
+ EXPECT_FALSE(returned_opt_boot_file_name.option_);
+
+ // The second option should be there.
+ returned_opt_ip_ttl = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+}
+
+// This test verifies that modified client classes can be retrieved from the database.
+TEST_F(MySqlConfigBackendDHCPv4Test, getModifiedClientClasses4) {
+ // Create server1.
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add three classes to the database with different timestamps.
+ auto class1 = test_client_classes_[0];
+ class1->setModificationTime(timestamps_["yesterday"]);
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ class2->setModificationTime(timestamps_["today"]);
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ auto class3 = test_client_classes_[2];
+ class3->setModificationTime(timestamps_["tomorrow"]);
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+
+ // Get modified client classes configured for all servers.
+ auto client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get modified client classes appropriate for server1. It includes classes
+ // for all servers and for the server1.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get the classes again but use the timestamp equal to the modification
+ // time of the first class.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get modified classes starting from today. It should return only two.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["today"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get client classes modified in the future. It should return none.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["after tomorrow"]);
+ EXPECT_EQ(0, client_classes.getClasses()->size());
+
+ // Getting modified client classes for any server is unsupported.
+ EXPECT_THROW(cbptr_->getModifiedClientClasses4(ServerSelector::ANY(),
+ timestamps_["two days ago"]),
+ InvalidOperation);
+}
+
+// This test verifies that a specified client class can be deleted.
+TEST_F(MySqlConfigBackendDHCPv4Test, deleteClientClass4) {
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+ EXPECT_NO_THROW(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class bar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+
+ EXPECT_NO_THROW(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server2"),
+ class3->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foobar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+
+ EXPECT_NO_THROW(result = cbptr_->deleteClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+}
+
+// This test verifies that all client classes can be deleted using
+// a specified server selector.
+TEST_F(MySqlConfigBackendDHCPv4Test, deleteAllClientClasses4) {
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::UNASSIGNED()));
+ EXPECT_EQ(0, result);
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server2 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server2"));
+ }
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(0, result);
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server1 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(0, result);
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for all deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(0, result);
+
+ // Deleting multiple objects using ANY server tag is unsupported.
+ EXPECT_THROW(cbptr_->deleteAllClientClasses4(ServerSelector::ANY()), InvalidOperation);
+}
+
+// This test verifies that client class dependencies are tracked when the
+// classes are added to the database. It verifies that an attempt to update
+// a class violiting the dependencies results in an error.
+TEST_F(MySqlConfigBackendDHCPv4Test, clientClassDependencies4) {
+ // Create a server.
+ EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create first class. It depends on KNOWN built-in class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("member('KNOWN')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Create second class which depends on the first class. This yelds indirect
+ // dependency on KNOWN class.
+ auto class2 = test_client_classes_[1];
+ class2->setTest("member('foo')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ // Create third class depending on the second class. This also yelds indirect
+ // dependency on KNOWN class.
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('bar')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+
+ // Try to change the dependency of the first class. There are other classes
+ // having indirect dependency on KNOWN class via this class. Therefore, the
+ // update should be unsuccessful.
+ class1->setTest("member('HA_server1')");
+ EXPECT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""),
+ DbOperationError);
+
+ // Try to change the dependency of the second class. This should result in
+ // an error because the third class depends on it.
+ class2->setTest("member('HA_server1')");
+ EXPECT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""),
+ DbOperationError);
+
+ // Changing the indirect dependency of the third class should succeed, because
+ // no other classes depend on this class.
+ class3->setTest("member('HA_server1')");
+ EXPECT_NO_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+}
+
/// This test verifies that audit entries can be retrieved from a given
/// timestamp and id including when two entries can get the same timestamp.
/// (either it is a common even and this should catch it, or it is a rare