diff options
author | Marcin Siodelski <marcin@isc.org> | 2021-06-16 15:04:35 +0200 |
---|---|---|
committer | Marcin Siodelski <marcin@isc.org> | 2021-07-21 12:49:50 +0200 |
commit | 1faffe41ce47412841d723223c2e1923a81a5773 (patch) | |
tree | 5594c80aa37332c13a90c1406c2406e17e55c414 /src/hooks/dhcp | |
parent | [#1928] Client classes added to CB API (diff) | |
download | kea-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.cc | 698 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h | 9 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc | 118 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_impl.h | 32 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc | 20 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_messages.h | 10 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes | 29 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h | 170 | ||||
-rw-r--r-- | src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc | 600 |
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 |