From: Marcin Siodelski Date: Mon, 5 Jul 2021 08:55:49 +0000 (+0200) Subject: [#1928] DHCPv6 MySQL Config Backend X-Git-Tag: Kea-1.9.10~74 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1825ac95ca18f24cdeb626a9203f2561a4203e5f;p=thirdparty%2Fkea.git [#1928] DHCPv6 MySQL Config Backend Implemented DHCP6 Config Backend similar to the DHCPv4. --- diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc index 1c7f385f19..fb85fefac5 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ public: /// database. enum StatementIndex { CREATE_AUDIT_REVISION, + CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE, GET_GLOBAL_PARAMETER6, GET_ALL_GLOBAL_PARAMETERS6, GET_MODIFIED_GLOBAL_PARAMETERS6, @@ -97,6 +99,11 @@ public: GET_OPTION6_POOL_ID_CODE_SPACE, GET_OPTION6_PD_POOL_ID_CODE_SPACE, GET_OPTION6_SHARED_NETWORK_CODE_SPACE, + GET_CLIENT_CLASS6_NAME, + GET_ALL_CLIENT_CLASSES6, + GET_ALL_CLIENT_CLASSES6_UNASSIGNED, + GET_MODIFIED_CLIENT_CLASSES6, + GET_MODIFIED_CLIENT_CLASSES6_UNASSIGNED, GET_AUDIT_ENTRIES6_TIME, GET_SERVER6, GET_ALL_SERVERS6, @@ -109,19 +116,26 @@ public: INSERT_SHARED_NETWORK6, INSERT_SHARED_NETWORK6_SERVER, INSERT_OPTION_DEF6, + INSERT_OPTION_DEF6_CLIENT_CLASS, INSERT_OPTION_DEF6_SERVER, INSERT_OPTION6, INSERT_OPTION6_SERVER, + INSERT_CLIENT_CLASS6, + INSERT_CLIENT_CLASS6_SERVER, + INSERT_CLIENT_CLASS6_DEPENDENCY, INSERT_SERVER6, UPDATE_GLOBAL_PARAMETER6, UPDATE_SUBNET6, UPDATE_SHARED_NETWORK6, UPDATE_OPTION_DEF6, + UPDATE_OPTION_DEF6_CLIENT_CLASS, UPDATE_OPTION6, UPDATE_OPTION6_SUBNET_ID, UPDATE_OPTION6_POOL_ID, UPDATE_OPTION6_PD_POOL_ID, UPDATE_OPTION6_SHARED_NETWORK, + UPDATE_OPTION6_CLIENT_CLASS, + UPDATE_CLIENT_CLASS6, UPDATE_SERVER6, DELETE_GLOBAL_PARAMETER6, DELETE_ALL_GLOBAL_PARAMETERS6, @@ -144,6 +158,7 @@ public: DELETE_OPTION_DEF6_CODE_NAME, DELETE_ALL_OPTION_DEFS6, DELETE_ALL_OPTION_DEFS6_UNASSIGNED, + DELETE_OPTION_DEFS6_CLIENT_CLASS, DELETE_OPTION6, DELETE_ALL_GLOBAL_OPTIONS6_UNASSIGNED, DELETE_OPTION6_SUBNET_ID, @@ -152,6 +167,13 @@ public: DELETE_OPTION6_SHARED_NETWORK, DELETE_OPTIONS6_SUBNET_ID_PREFIX, DELETE_OPTIONS6_SHARED_NETWORK, + DELETE_OPTIONS6_CLIENT_CLASS, + DELETE_CLIENT_CLASS6_DEPENDENCY, + DELETE_CLIENT_CLASS6_SERVER, + DELETE_ALL_CLIENT_CLASSES6, + DELETE_ALL_CLIENT_CLASSES6_UNASSIGNED, + DELETE_CLIENT_CLASS6, + DELETE_CLIENT_CLASS6_ANY, DELETE_SERVER6, DELETE_ALL_SERVERS6, NUM_STATEMENTS @@ -2464,6 +2486,56 @@ 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 createUpdateOption6(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(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(2), + createInputContextBinding(option), + MySqlBinding::createNull(), + MySqlBinding::createNull(), + MySqlBinding::createTimestamp(option->getModificationTime()), + MySqlBinding::createNull(), + MySqlBinding::createString(client_class->getName()), + MySqlBinding::createInteger(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, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + server_selector, "client class specific option set", + true); + + if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl:: + UPDATE_OPTION6_CLIENT_CLASS, + in_bindings) == 0) { + // Remove the 3 bindings used only in case of update. + in_bindings.resize(in_bindings.size() - 3); + insertOption6(server_selector, in_bindings); + } + } + /// @brief Sends query to insert or update option definition. /// /// @param server_selector Server selector. @@ -2479,6 +2551,24 @@ public: MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_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 createUpdateOptionDef6(const ServerSelector& server_selector, + const OptionDefinitionPtr& option_def, + const std::string& client_class_name) { + createUpdateOptionDef(server_selector, option_def, DHCP6_OPTION_SPACE, + MySqlConfigBackendDHCPv6Impl::GET_OPTION_DEF6_CODE_SPACE, + MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_CLIENT_CLASS, + MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION_DEF6_CLIENT_CLASS, + MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION, + MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_SERVER, + client_class_name); + } + /// @brief Sends query to delete option definition by code and /// option space name. /// @@ -2503,6 +2593,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 deleteOptionDefs6(const ServerSelector& server_selector, + const ClientClassDefPtr& client_class) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(client_class->getName()) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTION_DEFS6_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. @@ -2681,6 +2791,390 @@ 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 deleteOptions6(const ServerSelector& server_selector, + const ClientClassDefPtr& client_class) { + + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(client_class->getName()) + }; + + // Run DELETE. + return (deleteTransactional(DELETE_OPTIONS6_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 getClientClasses6(const StatementIndex& index, + const ServerSelector& server_selector, + const MySqlBindingCollection& in_bindings, + ClientClassDictionary& client_classes) { + MySqlBindingCollection out_bindings = { + MySqlBinding::createInteger(), // id + MySqlBinding::createString(CLIENT_CLASS_NAME_BUF_LENGTH), // name + MySqlBinding::createString(CLIENT_CLASS_TEST_BUF_LENGTH), // test + MySqlBinding::createInteger(), // required + MySqlBinding::createInteger(), // valid lifetime + MySqlBinding::createInteger(), // min valid lifetime + MySqlBinding::createInteger(), // max valid lifetime + MySqlBinding::createInteger(), // depend on known directly + MySqlBinding::createInteger(), // depend on known indirectly + MySqlBinding::createTimestamp(), // modification_ts + MySqlBinding::createInteger(), // option def: id + MySqlBinding::createInteger(), // option def: code + MySqlBinding::createString(OPTION_NAME_BUF_LENGTH), // option def: name + MySqlBinding::createString(OPTION_SPACE_BUF_LENGTH), // option def: space + MySqlBinding::createInteger(), // option def: type + MySqlBinding::createTimestamp(), // option def: modification_ts + MySqlBinding::createInteger(), // 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(), // option: option_id + MySqlBinding::createInteger(), // 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(), // option: persistent + MySqlBinding::createInteger(), // option: dhcp4_subnet_id + MySqlBinding::createInteger(), // 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(), // option: pool_id + MySqlBinding::createTimestamp(), // option: modification_ts + MySqlBinding::createString(SERVER_TAG_BUF_LENGTH) // server tag + }; + + std::list 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())) { + + last_option_id = 0; + last_option_def_id = 0; + last_tag.clear(); + + auto options = boost::make_shared(); + auto option_defs = boost::make_shared(); + auto expression = boost::make_shared(); + + last_client_class = boost::make_shared(out_bindings[1]->getString(), expression, options); + last_client_class->setCfgOptionDef(option_defs); + + // id + last_client_class->setId(out_bindings[0]->getInteger()); + + // name + last_client_class->setName(out_bindings[1]->getString()); + + // test + if (!out_bindings[2]->amNull()) { + last_client_class->setTest(out_bindings[2]->getString()); + } + + // required + if (!out_bindings[3]->amNull()) { + last_client_class->setRequired(out_bindings[3]->getBool()); + } + + // valid lifetime: default, min, max + last_client_class->setValid(createTriplet(out_bindings[4], out_bindings[5], out_bindings[6])); + + // depend on known directly or indirectly + last_client_class->setDependOnKnown(out_bindings[7]->getBool() || out_bindings[8]->getBool()); + + // modification_ts + last_client_class->setModificationTime(out_bindings[9]->getTimestamp()); + + class_list.push_back(last_client_class); + } + + // server tag + if (!out_bindings[32]->amNull() && + (last_tag != out_bindings[32]->getString())) { + last_tag = out_bindings[32]->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 10 to 19. + if (!out_bindings[10]->amNull() && + (last_option_def_id < out_bindings[10]->getInteger())) { + last_option_def_id = out_bindings[10]->getInteger(); + + auto def = processOptionDefRow(out_bindings.begin() + 10); + if (def) { + last_client_class->getCfgOptionDef()->add(def); + } + } + + // Parse client class specific option from 20 to 31. + if (!out_bindings[20]->amNull() && + (last_option_id < out_bindings[20]->getInteger())) { + last_option_id = out_bindings[20]->getInteger(); + + OptionDescriptorPtr desc = processOptionRow(Option::V6, out_bindings.begin() + 20); + 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 getClientClass6(const ServerSelector& server_selector, + const std::string& name) { + MySqlBindingCollection in_bindings = { + MySqlBinding::createString(name) + }; + ClientClassDictionary client_classes; + getClientClasses6(MySqlConfigBackendDHCPv6Impl::GET_CLIENT_CLASS6_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 getAllClientClasses6(const ServerSelector& server_selector, + ClientClassDictionary& client_classes) { + MySqlBindingCollection in_bindings; + getClientClasses6(server_selector.amUnassigned() ? + MySqlConfigBackendDHCPv6Impl::GET_ALL_CLIENT_CLASSES6_UNASSIGNED : + MySqlConfigBackendDHCPv6Impl::GET_ALL_CLIENT_CLASSES6, + 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 getModifiedClientClasses6(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) + }; + getClientClasses6(server_selector.amUnassigned() ? + GET_MODIFIED_CLIENT_CLASSES6_UNASSIGNED : + GET_MODIFIED_CLIENT_CLASSES6, + 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 createUpdateClientClass6(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 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::createBool(client_class->getRequired()), + MySqlBinding::createInteger(client_class->getValid()), + MySqlBinding::createInteger(client_class->getValid().getMin()), + MySqlBinding::createInteger(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, MySqlConfigBackendDHCPv6Impl::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(MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6, in_bindings); + + } catch (const DuplicateEntry&) { + // Such class already exists. + + // Delete options and option definitions. They will be re-created from the new class + // instance. + deleteOptions6(ServerSelector::ANY(), client_class); + deleteOptionDefs6(ServerSelector::ANY(), client_class); + + // Try to update the class. + in_bindings.push_back(MySqlBinding::createString(client_class->getName())); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_CLIENT_CLASS6, 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(MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_DEPENDENCY, + in_assoc_bindings); + conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_SERVER, + in_assoc_bindings); + update = true; + } + + // Associate client class with the servers. + attachElementToServers(MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6_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(MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6_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(MySqlConfigBackendDHCPv6Impl::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) { + createUpdateOptionDef6(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; + createUpdateOption6(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 deleteClientClass6(const ServerSelector& server_selector, + const std::string& name) { + int index = server_selector.amAny() ? + MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_ANY : + MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6; + + 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. /// @@ -2878,6 +3372,11 @@ TaggedStatementArray tagged_statements = { { "CALL createAuditRevisionDHCP6(?, ?, ?, ?)" }, + // Verify that dependency on KNOWN/UNKNOWN class has not changed. + { MySqlConfigBackendDHCPv6Impl::CHECK_CLIENT_CLASS_KNOWN_DEPENDENCY_CHANGE, + "CALL checkDHCPv6ClientClassKnownDependencyChange()" + }, + // Select global parameter by name. { MySqlConfigBackendDHCPv6Impl::GET_GLOBAL_PARAMETER6, MYSQL_GET_GLOBAL_PARAMETER(dhcp6, AND g.name = ?) @@ -3060,6 +3559,31 @@ TaggedStatementArray tagged_statements = { { MYSQL_GET_OPTION6(AND o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) }, + // Select a client class by name. + { MySqlConfigBackendDHCPv6Impl::GET_CLIENT_CLASS6_NAME, + MYSQL_GET_CLIENT_CLASS6_WITH_TAG(WHERE c.name = ?) + }, + + // Select all client classes. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_CLIENT_CLASSES6, + MYSQL_GET_CLIENT_CLASS6_WITH_TAG() + }, + + // Select all unassigned client classes. + { MySqlConfigBackendDHCPv6Impl::GET_ALL_CLIENT_CLASSES6_UNASSIGNED, + MYSQL_GET_CLIENT_CLASS6_UNASSIGNED() + }, + + // Select modified client classes. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_CLIENT_CLASSES6, + MYSQL_GET_CLIENT_CLASS6_WITH_TAG(WHERE c.modification_ts >= ?) + }, + + // Select modified client classes. + { MySqlConfigBackendDHCPv6Impl::GET_MODIFIED_CLIENT_CLASSES6_UNASSIGNED, + MYSQL_GET_CLIENT_CLASS6_UNASSIGNED(AND c.modification_ts >= ?) + }, + // Retrieves the most recent audit entries. { MySqlConfigBackendDHCPv6Impl::GET_AUDIT_ENTRIES6_TIME, MYSQL_GET_AUDIT_ENTRIES_TIME(dhcp6) @@ -3186,6 +3710,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_INSERT_OPTION_DEF(dhcp6) }, + // Insert option definition for client class. + { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_CLIENT_CLASS, + MYSQL_INSERT_OPTION_DEF_CLIENT_CLASS(dhcp6) + }, + // Insert association of the option definition with a server. { MySqlConfigBackendDHCPv6Impl::INSERT_OPTION_DEF6_SERVER, MYSQL_INSERT_OPTION_DEF_SERVER(dhcp6) @@ -3201,6 +3730,29 @@ TaggedStatementArray tagged_statements = { { MYSQL_INSERT_OPTION_SERVER(dhcp6) }, + // Insert client class. + { MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6, + "INSERT INTO dhcp6_client_class(" + " name," + " test," + " 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. + { MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6_SERVER, + MYSQL_INSERT_CLIENT_CLASS_SERVER(dhcp6) + }, + // Insert client class dependency. + { MySqlConfigBackendDHCPv6Impl::INSERT_CLIENT_CLASS6_DEPENDENCY, + MYSQL_INSERT_CLIENT_CLASS_DEPENDENCY(dhcp6) + }, + // Insert server with server tag and description. { MySqlConfigBackendDHCPv6Impl::INSERT_SERVER6, MYSQL_INSERT_SERVER(dhcp6) @@ -3290,6 +3842,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_UPDATE_OPTION_DEF(dhcp6) }, + // Update existing option definition. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION_DEF6_CLIENT_CLASS, + MYSQL_UPDATE_OPTION_DEF_CLIENT_CLASS(dhcp6) + }, + // Update existing global option. { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6, MYSQL_UPDATE_OPTION6_WITH_TAG(AND o.scope_id = 0 AND o.code = ? AND o.space = ?) @@ -3315,6 +3872,25 @@ TaggedStatementArray tagged_statements = { { MYSQL_UPDATE_OPTION6_NO_TAG(o.scope_id = 4 AND o.shared_network_name = ? AND o.code = ? AND o.space = ?) }, + // Update existing client class level option. + { MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6_CLIENT_CLASS, + MYSQL_UPDATE_OPTION6_NO_TAG(o.scope_id = 2 AND o.dhcp_client_class = ? AND o.code = ? AND o.space = ?) + }, + + { MySqlConfigBackendDHCPv6Impl::UPDATE_CLIENT_CLASS6, + "UPDATE dhcp6_client_class SET" + " name = ?," + " test = ?," + " 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. { MySqlConfigBackendDHCPv6Impl::UPDATE_SERVER6, MYSQL_UPDATE_SERVER(dhcp6) @@ -3420,6 +3996,11 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION_DEF_UNASSIGNED(dhcp6) }, + // Delete client class specific option definitions. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION_DEFS6_CLIENT_CLASS, + MYSQL_DELETE_OPTION_DEFS_CLIENT_CLASS(dhcp6) + }, + // Delete single global option. { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6, MYSQL_DELETE_OPTION_WITH_TAG(dhcp6, AND o.scope_id = 0 AND o.code = ? AND o.space = ?) @@ -3462,6 +4043,41 @@ TaggedStatementArray tagged_statements = { { MYSQL_DELETE_OPTION_NO_TAG(dhcp6, WHERE o.scope_id = 4 AND o.shared_network_name = ?) }, + // Delete options belonging to a client class. + { MySqlConfigBackendDHCPv6Impl::DELETE_OPTIONS6_CLIENT_CLASS, + MYSQL_DELETE_OPTION_NO_TAG(dhcp6, WHERE o.scope_id = 2 AND o.dhcp_client_class = ?) + }, + + // Delete all dependencies of a client class. + { MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_DEPENDENCY, + MYSQL_DELETE_CLIENT_CLASS_DEPENDENCY(dhcp6) + }, + + // Delete associations of a client class with server. + { MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_SERVER, + MYSQL_DELETE_CLIENT_CLASS_SERVER(dhcp6), + }, + + // Delete all client classes. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_CLIENT_CLASSES6, + MYSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp6) + }, + + // Delete all unassigned client classes. + { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_CLIENT_CLASSES6_UNASSIGNED, + MYSQL_DELETE_CLIENT_CLASS_UNASSIGNED(dhcp6) + }, + + // Delete specified client class. + { MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6, + MYSQL_DELETE_CLIENT_CLASS_WITH_TAG(dhcp6, AND name = ?) + }, + + // Delete any client class with a given name. + { MySqlConfigBackendDHCPv6Impl::DELETE_CLIENT_CLASS6_ANY, + MYSQL_DELETE_CLIENT_CLASS_ANY(dhcp6, AND name = ?) + }, + // Delete a server by tag. { MySqlConfigBackendDHCPv6Impl::DELETE_SERVER6, MYSQL_DELETE_SERVER(dhcp6) @@ -3695,6 +4311,36 @@ MySqlConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector& return (parameters); } +ClientClassDefPtr +MySqlConfigBackendDHCPv6::getClientClass6(const db::ServerSelector& server_selector, + const std::string& name) const { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_CLIENT_CLASS6) + .arg(name); + return (impl_->getClientClass6(server_selector, name)); +} + +ClientClassDictionary +MySqlConfigBackendDHCPv6::getAllClientClasses6(const db::ServerSelector& server_selector) const { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_ALL_CLIENT_CLASSES6); + ClientClassDictionary client_classes; + impl_->getAllClientClasses6(server_selector, client_classes); + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT) + .arg(client_classes.getClasses()->size()); + return (client_classes); +} + +ClientClassDictionary +MySqlConfigBackendDHCPv6::getModifiedClientClasses6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6) + .arg(util::ptimeToText(modification_time)); + ClientClassDictionary client_classes; + impl_->getModifiedClientClasses6(server_selector, modification_time, client_classes); + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT) + .arg(client_classes.getClasses()->size()); + return (client_classes); +} + AuditEntryCollection MySqlConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector& server_selector, const boost::posix_time::ptime& modification_time, @@ -3810,6 +4456,15 @@ MySqlConfigBackendDHCPv6::createUpdateGlobalParameter6(const ServerSelector& ser impl_->createUpdateGlobalParameter6(server_selector, value); } +void +MySqlConfigBackendDHCPv6::createUpdateClientClass6(const db::ServerSelector& server_selector, + const ClientClassDefPtr& client_class, + const std::string& follow_client_class) { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS6) + .arg(client_class->getName()); + impl_->createUpdateClientClass6(server_selector, client_class, follow_client_class); +} + void MySqlConfigBackendDHCPv6::createUpdateServer6(const ServerPtr& server) { LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_CREATE_UPDATE_SERVER6) @@ -4036,6 +4691,31 @@ MySqlConfigBackendDHCPv6::deleteAllGlobalParameters6(const ServerSelector& serve return (result); } +uint64_t +MySqlConfigBackendDHCPv6::deleteClientClass6(const db::ServerSelector& server_selector, + const std::string& name) { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_CLIENT_CLASS6) + .arg(name); + auto result = impl_->deleteClientClass6(server_selector, name); + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_CLIENT_CLASS6_RESULT) + .arg(result); + return (result); +} + +uint64_t +MySqlConfigBackendDHCPv6::deleteAllClientClasses6(const db::ServerSelector& server_selector) { + LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6); + + int index = (server_selector.amUnassigned() ? + MySqlConfigBackendDHCPv6Impl::DELETE_ALL_CLIENT_CLASSES6_UNASSIGNED : + MySqlConfigBackendDHCPv6Impl::DELETE_ALL_CLIENT_CLASSES6); + 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_CLASSES6_RESULT) + .arg(result); + return (result); +} + uint64_t MySqlConfigBackendDHCPv6::deleteServer6(const ServerTag& server_tag) { LOG_DEBUG(mysql_cb_logger, DBGLVL_TRACE_BASIC, MYSQL_CB_DELETE_SERVER6) diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h index 08722cafd4..8c80741e3b 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -225,6 +225,30 @@ public: getModifiedGlobalParameters6(const db::ServerSelector& server_selector, const boost::posix_time::ptime& modification_time) const; + /// @brief Retrieves a client class by name. + /// + /// @param server_selector Server selector. + /// @param name Client class name. + /// @return Pointer to the retrieved client class. + virtual ClientClassDefPtr + getClientClass6(const db::ServerSelector& selector, const std::string& name) const; + + /// @brief Retrieves all client classes. + /// + /// @param selector Server selector. + /// @return Collection of client classes. + virtual ClientClassDictionary + getAllClientClasses6(const db::ServerSelector& selector) const; + + /// @brief Retrieves client classes modified after specified time. + /// + /// @param selector Server selector. + /// @param modification_time Modification time. + /// @return Collection of client classes. + virtual ClientClassDictionary + getModifiedClientClasses6(const db::ServerSelector& selector, + const boost::posix_time::ptime& modification_time) const; + /// @brief Retrieves the most recent audit entries. /// /// @param selector Server selector. @@ -355,6 +379,19 @@ public: createUpdateGlobalParameter6(const db::ServerSelector& server_selector, const data::StampedValuePtr& value); + /// @brief Creates or updates DHCPv6 client class. + /// + /// @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 + createUpdateClientClass6(const db::ServerSelector& server_selector, + const ClientClassDefPtr& client_class, + const std::string& follow_client_class); + /// @brief Creates or updates a server. /// /// @param server Instance of the server to be stored. @@ -528,6 +565,22 @@ public: virtual uint64_t deleteAllGlobalParameters6(const db::ServerSelector& server_selector); + /// @brief Deletes DHCPv6 client class. + /// + /// @param server_selector Server selector. + /// @param name Name of the class to be deleted. + /// @return Number of deleted client classes. + virtual uint64_t + deleteClientClass6(const db::ServerSelector& server_selector, + const std::string& name); + + /// @brief Deletes all client classes. + /// + /// @param server_selector Server selector. + /// @return Number of deleted client classes. + virtual uint64_t + deleteAllClientClasses6(const db::ServerSelector& server_selector); + /// @brief Deletes a server from the backend. /// /// @param server_tag Tag of the server to be deleted. diff --git a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc index b934cf793c..a38af9f5c6 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc @@ -13,6 +13,7 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_BY_PREFIX_OPTION6 = "MYS 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_CLIENT_CLASS6 = "MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS6"; 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"; @@ -30,6 +31,8 @@ extern const isc::log::MessageID MYSQL_CB_CREATE_UPDATE_SUBNET6 = "MYSQL_CB_CREA 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_CLIENT_CLASSES6 = "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6"; +extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6_RESULT = "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6_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"; @@ -70,6 +73,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_BY_SUBNET_ID_SUBNET6 = "MYSQL_C 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_CLIENT_CLASS6 = "MYSQL_CB_DELETE_CLIENT_CLASS6"; +extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS6_RESULT = "MYSQL_CB_DELETE_CLIENT_CLASS6_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"; @@ -100,6 +105,8 @@ extern const isc::log::MessageID MYSQL_CB_DELETE_SHARED_NETWORK_SUBNETS6 = "MYSQ 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_CLIENT_CLASSES6 = "MYSQL_CB_GET_ALL_CLIENT_CLASSES6"; +extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES6_RESULT = "MYSQL_CB_GET_ALL_CLIENT_CLASSES6_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"; @@ -125,12 +132,15 @@ extern const isc::log::MessageID MYSQL_CB_GET_ALL_SUBNETS4_RESULT = "MYSQL_CB_GE 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_CLIENT_CLASS6 = "MYSQL_CB_GET_CLIENT_CLASS6"; 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_CLIENT_CLASSES6 = "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6"; +extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_RESULT = "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_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"; @@ -199,6 +209,7 @@ const char* values[] = { "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_CLIENT_CLASS6", "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", @@ -216,6 +227,8 @@ const char* values[] = { "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_CLIENT_CLASSES6", "delete all client classes", + "MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6_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", @@ -256,6 +269,8 @@ const char* values[] = { "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_CLIENT_CLASS6", "delete client class: %1", + "MYSQL_CB_DELETE_CLIENT_CLASS6_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", @@ -286,6 +301,8 @@ const char* values[] = { "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_CLIENT_CLASSES6", "retrieving all client classes", + "MYSQL_CB_GET_ALL_CLIENT_CLASSES6_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", @@ -311,12 +328,15 @@ const char* values[] = { "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_CLIENT_CLASS6", "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_CLIENT_CLASSES6", "retrieving modified client classes from: %1", + "MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_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 1b51c564e0..418cefc5cc 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.h @@ -14,6 +14,7 @@ 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_CLIENT_CLASS6; 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; @@ -31,6 +32,8 @@ 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_CLIENT_CLASSES6; +extern const isc::log::MessageID MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6_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; @@ -71,6 +74,8 @@ 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_CLIENT_CLASS6; +extern const isc::log::MessageID MYSQL_CB_DELETE_CLIENT_CLASS6_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; @@ -101,6 +106,8 @@ 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_CLIENT_CLASSES6; +extern const isc::log::MessageID MYSQL_CB_GET_ALL_CLIENT_CLASSES6_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; @@ -126,12 +133,15 @@ 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_CLIENT_CLASS6; 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_CLIENT_CLASSES6; +extern const isc::log::MessageID MYSQL_CB_GET_MODIFIED_CLIENT_CLASSES6_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 75c26e9736..8776e15d67 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes +++ b/src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes @@ -20,6 +20,9 @@ Debug message issued when triggered an action to create or update option by subn % MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS4 create or update client class: %1 Debug message issued when triggered an action to create or update client class +% MYSQL_CB_CREATE_UPDATE_CLIENT_CLASS6 create or update client class: %1 +Debug message issued when triggered an action to create or update client class + % 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 @@ -74,6 +77,12 @@ 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_CLIENT_CLASSES6 delete all client classes +Debug message issued when triggered an action to delete all client classes + +% MYSQL_CB_DELETE_ALL_CLIENT_CLASSES6_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 @@ -194,6 +203,12 @@ 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_CLIENT_CLASS6 delete client class: %1 +Debug message issued when triggered an action to delete client class + +% MYSQL_CB_DELETE_CLIENT_CLASS6_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 @@ -284,6 +299,12 @@ 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_CLIENT_CLASSES6 retrieving all client classes +Debug message issued when triggered an action to retrieve all client classes + +% MYSQL_CB_GET_ALL_CLIENT_CLASSES6_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 @@ -363,6 +384,9 @@ 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_CLIENT_CLASS6 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 @@ -381,6 +405,12 @@ Debug message issued when triggered an action to retrieve modified client classe % 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_CLIENT_CLASSES6 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_CLASSES6_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 503bd179ed..7808fd5be9 100644 --- a/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h +++ b/src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h @@ -727,6 +727,69 @@ namespace { #endif +#ifndef MYSQL_GET_CLIENT_CLASS6_COMMON +#define MYSQL_GET_CLIENT_CLASS6_COMMON(server_join, ...) \ + "SELECT " \ + " c.id," \ + " c.name," \ + " c.test," \ + " 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.dhcp6_subnet_id," \ + " x.scope_id," \ + " x.user_context," \ + " x.shared_network_name," \ + " x.pool_id," \ + " x.modification_ts," \ + " s.tag " \ + "FROM dhcp6_client_class AS c " \ + "INNER JOIN dhcp6_client_class_order AS o " \ + " ON c.id = o.class_id " \ + server_join \ + "LEFT JOIN dhcp6_option_def AS d ON c.id = d.class_id " \ + "LEFT JOIN dhcp6_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_CLASS6_WITH_TAG(...) \ + MYSQL_GET_CLIENT_CLASS6_COMMON( \ + "INNER JOIN dhcp6_client_class_server AS a " \ + " ON c.id = a.class_id " \ + "INNER JOIN dhcp6_server AS s " \ + " ON a.server_id = s.id ", \ + __VA_ARGS__) + +#define MYSQL_GET_CLIENT_CLASS6_UNASSIGNED(...) \ + MYSQL_GET_CLIENT_CLASS6_COMMON( \ + "LEFT JOIN dhcp6_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(" \ @@ -825,7 +888,7 @@ namespace { " record_types," \ " user_context," \ " class_id" \ - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT id FROM dhcp4_client_class WHERE name = ?))" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT id FROM " #table_prefix "_client_class WHERE name = ?))" #endif #ifndef MYSQL_INSERT_OPTION_DEF_SERVER diff --git a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc index d66f610669..a9bb548557 100644 --- a/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc +++ b/src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc @@ -78,8 +78,8 @@ public: /// @brief Constructor. MySqlConfigBackendDHCPv6Test() : 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(); @@ -104,6 +104,7 @@ public: initTestSubnets(); initTestSharedNetworks(); initTestOptionDefs(); + initTestClientClasses(); initTimestamps(); } @@ -470,6 +471,24 @@ public: LibDHCP::setRuntimeOptionDefs(defs); } + /// @brief Creates several client classes used in tests. + void initTestClientClasses() { + ExpressionPtr match_expr = boost::make_shared(); + CfgOptionPtr cfg_option = boost::make_shared(); + auto class1 = boost::make_shared("foo", match_expr, cfg_option); + class1->setRequired(true); + class1->setValid(Triplet(30, 60, 90)); + test_client_classes_.push_back(class1); + + auto class2 = boost::make_shared("bar", match_expr, cfg_option); + class2->setTest("member('foo')"); + test_client_classes_.push_back(class2); + + auto class3 = boost::make_shared("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. @@ -591,6 +610,9 @@ public: /// @brief Holds pointers to options used in tests. std::vector test_options_; + /// @brief Holds pointers to classes used in tests. + std::vector test_client_classes_; + /// @brief Holds pointers to the servers used in tests. std::vector test_servers_; @@ -4300,6 +4322,574 @@ TEST_F(MySqlConfigBackendDHCPv6Test, sharedNetworkOptionIdOrder) { } } +// This test verifies that it is possible to create client classes, update them +// and retrieve all classes for a given server. +TEST_F(MySqlConfigBackendDHCPv6Test, setAndGetAllClientClasses6) { + // Create a server. + EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + // Create first class. + auto class1 = test_client_classes_[0]; + ASSERT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Create second class. + auto class2 = test_client_classes_[1]; + ASSERT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + // Create third class. + auto class3 = test_client_classes_[2]; + ASSERT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_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_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + { + SCOPED_TRACE("client class bar is updated"); + testNewAuditEntry("dhcp6_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_->getAllClientClasses6(ServerSelector::ALL()); + ASSERT_EQ(1, client_classes.getClasses()->size()); + // All three classes should be returned for the server1. + client_classes = cbptr_->getAllClientClasses6(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_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "foo")); + + // Ensure that the classes order has changed. + client_classes = cbptr_->getAllClientClasses6(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(MySqlConfigBackendDHCPv6Test, getClientClass6) { + // Create a server. + EXPECT_NO_THROW(cbptr_->createUpdateServer6(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_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + auto class2 = test_client_classes_[1]; + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + + // Get the first client class and validate its contents. + ClientClassDefPtr client_class; + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName())); + ASSERT_TRUE(client_class); + EXPECT_EQ("foo", client_class->getName()); + EXPECT_TRUE(client_class->getRequired()); + 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_new_posix_timezone = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_new_posix_timezone.option_); + + OptionDescriptor returned_opt_preference = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.option_); + + // Fetch the same class using different server selectors. + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), + class1->getName())); + EXPECT_TRUE(client_class); + + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), + class1->getName())); + EXPECT_TRUE(client_class); + + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(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_->getClientClass6(ServerSelector::ALL(), + class2->getName())); + EXPECT_FALSE(client_class); + + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ANY(), + class2->getName())); + EXPECT_TRUE(client_class); + + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"), + class2->getName())); + EXPECT_TRUE(client_class); + + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(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(MySqlConfigBackendDHCPv6Test, createUpdateClientClass6Options) { + // 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(); + 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_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + // Fetch the class and the options from the database. + ClientClassDefPtr client_class; + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName())); + ASSERT_TRUE(client_class); + + // Validate options belonging to the class. + ASSERT_TRUE(client_class->getCfgOption()); + OptionDescriptor returned_opt_new_posix_timezone = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE); + ASSERT_TRUE(returned_opt_new_posix_timezone.option_); + + OptionDescriptor returned_opt_preference = + client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.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(1234, returned_def_foo->getCode()); + EXPECT_EQ("foo", returned_def_foo->getName()); + EXPECT_EQ(DHCP6_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(5235, returned_def_fish->getCode()); + EXPECT_EQ("fish", returned_def_fish->getName()); + EXPECT_EQ(DHCP6_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(); + 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_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + EXPECT_NO_THROW(client_class = cbptr_->getClientClass6(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_new_posix_timezone = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_NEW_POSIX_TIMEZONE); + EXPECT_FALSE(returned_opt_new_posix_timezone.option_); + + // The second option should be there. + returned_opt_preference = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, + D6O_PREFERENCE); + ASSERT_TRUE(returned_opt_preference.option_); +} + +// This test verifies that modified client classes can be retrieved from the database. +TEST_F(MySqlConfigBackendDHCPv6Test, getModifiedClientClasses6) { + // Create server1. + EXPECT_NO_THROW(cbptr_->createUpdateServer6(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_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + + auto class2 = test_client_classes_[1]; + class2->setModificationTime(timestamps_["today"]); + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, "")); + + auto class3 = test_client_classes_[2]; + class3->setModificationTime(timestamps_["tomorrow"]); + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "")); + + // Get modified client classes configured for all servers. + auto client_classes = cbptr_->getModifiedClientClasses6(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_->getModifiedClientClasses6(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_->getModifiedClientClasses6(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_->getModifiedClientClasses6(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_->getModifiedClientClasses6(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_->getModifiedClientClasses6(ServerSelector::ANY(), + timestamps_["two days ago"]), + InvalidOperation); +} + +// This test verifies that a specified client class can be deleted. +TEST_F(MySqlConfigBackendDHCPv6Test, deleteClientClass6) { + EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server1 is created and available for server1"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created and available for server2"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + auto class1 = test_client_classes_[0]; + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created and available for server1"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("client class foo is created and available for server 2"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + auto class2 = test_client_classes_[1]; + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_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_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + uint64_t result; + EXPECT_NO_THROW(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server1"), + class2->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class bar is deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server1")); + } + + EXPECT_NO_THROW(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server2"), + class3->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class foobar is deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "client class deleted", + ServerSelector::ONE("server2")); + } + + EXPECT_NO_THROW(result = cbptr_->deleteClientClass6(ServerSelector::ANY(), + class1->getName())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client class foo is deleted and no longer available for the server1"); + testNewAuditEntry("dhcp6_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("dhcp6_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(MySqlConfigBackendDHCPv6Test, deleteAllClientClasses6) { + EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[0])); + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2])); + { + SCOPED_TRACE("server1 is created and available for server1"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("server1 is created and available for server2"); + testNewAuditEntry("dhcp6_server", + AuditEntry::ModificationType::CREATE, + "server set", + ServerSelector::ONE("server2")); + } + + auto class1 = test_client_classes_[0]; + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "")); + { + SCOPED_TRACE("client class foo is created and available for server1"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server1")); + } + { + SCOPED_TRACE("client class foo is created and available for server 2"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + auto class2 = test_client_classes_[1]; + EXPECT_NO_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, "")); + { + SCOPED_TRACE("client class bar is created"); + testNewAuditEntry("dhcp6_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_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, "")); + { + SCOPED_TRACE("client class foobar is created"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::CREATE, + "client class set", + ServerSelector::ONE("server2")); + } + + uint64_t result; + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::UNASSIGNED())); + EXPECT_EQ(0, result); + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2"))); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for server2 deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server2")); + } + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2"))); + EXPECT_EQ(0, result); + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1"))); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for server1 deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server1")); + } + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1"))); + EXPECT_EQ(0, result); + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL())); + EXPECT_EQ(1, result); + { + SCOPED_TRACE("client classes for all deleted"); + testNewAuditEntry("dhcp6_client_class", + AuditEntry::ModificationType::DELETE, + "deleted all client classes", + ServerSelector::ONE("server1")); + } + + EXPECT_NO_THROW(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL())); + EXPECT_EQ(0, result); + + // Deleting multiple objects using ANY server tag is unsupported. + EXPECT_THROW(cbptr_->deleteAllClientClasses6(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(MySqlConfigBackendDHCPv6Test, clientClassDependencies6) { + // Create a server. + EXPECT_NO_THROW(cbptr_->createUpdateServer6(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_->createUpdateClientClass6(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_->createUpdateClientClass6(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_->createUpdateClientClass6(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_->createUpdateClientClass6(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_->createUpdateClientClass6(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_->createUpdateClientClass6(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 diff --git a/src/lib/dhcpsrv/config_backend_dhcp6.h b/src/lib/dhcpsrv/config_backend_dhcp6.h index dd200cb760..deacfecd1d 100644 --- a/src/lib/dhcpsrv/config_backend_dhcp6.h +++ b/src/lib/dhcpsrv/config_backend_dhcp6.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -294,6 +295,30 @@ public: getModifiedGlobalParameters6(const db::ServerSelector& selector, const boost::posix_time::ptime& modification_time) const = 0; + /// @brief Retrieves a client class by name. + /// + /// @param server_selector Server selector. + /// @param name Client class name. + /// @return Pointer to the retrieved client class. + virtual ClientClassDefPtr + getClientClass6(const db::ServerSelector& selector, const std::string& name) const = 0; + + /// @brief Retrieves all client classes. + /// + /// @param selector Server selector. + /// @return Collection of client classes. + virtual ClientClassDictionary + getAllClientClasses6(const db::ServerSelector& selector) const = 0; + + /// @brief Retrieves client classes modified after specified time. + /// + /// @param selector Server selector. + /// @param modification_time Modification time. + /// @return Collection of client classes. + virtual ClientClassDictionary + getModifiedClientClasses6(const db::ServerSelector& selector, + const boost::posix_time::ptime& modification_time) const = 0; + /// @brief Retrieves the most recent audit entries. /// /// Allowed server selectors: ALL, ONE. @@ -444,6 +469,19 @@ public: createUpdateGlobalParameter6(const db::ServerSelector& server_selector, const data::StampedValuePtr& value) = 0; + /// @brief Creates or updates a client class. + /// + /// @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 + createUpdateClientClass6(const db::ServerSelector& server_selector, + const ClientClassDefPtr& client_class, + const std::string& follow_class_name) = 0; + /// @brief Creates or updates a server. /// /// @param server Instance of the server to be stored. @@ -651,6 +689,22 @@ public: virtual uint64_t deleteAllGlobalParameters6(const db::ServerSelector& server_selector) = 0; + /// @brief Deletes a client class. + /// + /// @param server_selector Server selector. + /// @param name Name of the class to be deleted. + /// @return Number of deleted client classes. + virtual uint64_t + deleteClientClass6(const db::ServerSelector& server_selector, + const std::string& name) = 0; + + /// @brief Deletes all client classes. + /// + /// @param server_selector Server selector. + /// @return Number of deleted client classes. + virtual uint64_t + deleteAllClientClasses6(const db::ServerSelector& server_selector) = 0; + /// @brief Deletes a server from the backend. /// /// @param server_tag Tag of the server to be deleted. diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc index cbc2075358..d69b286899 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -508,6 +508,100 @@ TestConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector& return (globals); } +ClientClassDefPtr +TestConfigBackendDHCPv6::getClientClass6(const db::ServerSelector& server_selector, + const std::string& name) const { + auto client_class = classes_.findClass(name); + if (!client_class) { + return (client_class); + } + if (server_selector.amUnassigned()) { + return (client_class->getServerTags().empty() ? client_class : ClientClassDefPtr()); + } + auto tags = server_selector.getTags(); + for (auto tag : tags) { + if (client_class->hasServerTag(ServerTag(tag))) { + return (client_class); + } + } + return (client_class->hasAllServerTag() ? client_class : ClientClassDefPtr()); +} + +ClientClassDictionary +TestConfigBackendDHCPv6::getAllClientClasses6(const db::ServerSelector& server_selector) const { + auto tags = server_selector.getTags(); + ClientClassDictionary all_classes; + for (auto client_class : *classes_.getClasses()) { + auto non_const_client_class = boost::const_pointer_cast(client_class); + if (server_selector.amAny()) { + all_classes.addClass(non_const_client_class); + continue; + } + if (server_selector.amUnassigned()) { + if (client_class->getServerTags().empty()) { + all_classes.addClass(non_const_client_class); + } + continue; + } + bool got = false; + for (auto tag : tags) { + if (client_class->hasServerTag(ServerTag(tag))) { + all_classes.addClass(non_const_client_class); + got = true; + break; + } + } + if (got) { + continue; + } + if (client_class->hasAllServerTag()) { + all_classes.addClass(non_const_client_class); + } + } + return (all_classes); +} + +ClientClassDictionary +TestConfigBackendDHCPv6::getModifiedClientClasses6(const db::ServerSelector& server_selector, + const boost::posix_time::ptime& modification_time) const { + auto tags = server_selector.getTags(); + ClientClassDictionary modified_classes; + for (auto client_class : *classes_.getClasses()) { + if (client_class->getModificationTime() >= modification_time) { + auto non_const_client_class = boost::const_pointer_cast(client_class); + bool got = false; + for (auto tag : tags) { + if (client_class->hasServerTag(ServerTag(tag))) { + modified_classes.addClass(non_const_client_class); + got = true; + break; + } + } + if (got) { + continue; + } + if (client_class->hasAllServerTag()) { + modified_classes.addClass(non_const_client_class); + } + } + } + return (modified_classes); +} + +void +TestConfigBackendDHCPv6::createUpdateClientClass6(const db::ServerSelector& server_selector, + const ClientClassDefPtr& client_class, + const std::string& follow_client_class) { + mergeServerTags(client_class, server_selector); + + auto existing_class = classes_.findClass(client_class->getName()); + if (existing_class) { + classes_.removeClass(client_class->getName()); + } + auto non_const_client_class = boost::const_pointer_cast(client_class); + classes_.addClass(non_const_client_class); +} + AuditEntryCollection TestConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector&, const boost::posix_time::ptime&, @@ -1329,6 +1423,72 @@ TestConfigBackendDHCPv6::deleteAllGlobalParameters6(const db::ServerSelector& se return (cnt); } +uint64_t +TestConfigBackendDHCPv6::deleteClientClass6(const db::ServerSelector& server_selector, + const std::string& name) { + auto existing_class = classes_.findClass(name); + if (!existing_class) { + return (0); + } + if ((server_selector.amUnassigned()) && + !existing_class->getServerTags().empty()) { + return (0); + } + if (!server_selector.amAny()) { + bool got = false; + auto tags = server_selector.getTags(); + for (auto tag : tags) { + if (existing_class->hasServerTag(ServerTag(tag))) { + got = true; + break; + } + } + if (!got && !existing_class->hasAllServerTag()) { + return (0); + } + } + classes_.removeClass(name); + return (1); +} + +uint64_t +TestConfigBackendDHCPv6::deleteAllClientClasses6(const db::ServerSelector& server_selector) { + std::list class_names; + for (auto client_class : (*classes_.getClasses())) { + if (server_selector.amAny()) { + class_names.push_back(client_class->getName()); + continue; + } + if (server_selector.amUnassigned()) { + if (client_class->getServerTags().empty()) { + class_names.push_back(client_class->getName()); + } + continue; + } + bool got = false; + auto tags = server_selector.getTags(); + for (auto tag : tags) { + if (client_class->hasServerTag(ServerTag(tag))) { + class_names.push_back(client_class->getName()); + got = true; + break; + } + } + if (got) { + continue; + } + if (client_class->hasAllServerTag()) { + class_names.push_back(client_class->getName()); + } + } + + // Erase client classes. + for (auto name : class_names) { + classes_.removeClass(name); + } + return (class_names.size()); +} + uint64_t TestConfigBackendDHCPv6::deleteServer6(const ServerTag& server_tag) { auto& index = servers_.get(); diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h index c13b50522f..012120194b 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h +++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h @@ -215,6 +215,30 @@ public: getModifiedGlobalParameters6(const db::ServerSelector& server_selector, const boost::posix_time::ptime& modification_time) const; + /// @brief Retrieves a client class by name. + /// + /// @param server_selector Server selector. + /// @param name Client class name. + /// @return Pointer to the retrieved client class. + virtual ClientClassDefPtr + getClientClass6(const db::ServerSelector& selector, const std::string& name) const; + + /// @brief Retrieves all client classes. + /// + /// @param selector Server selector. + /// @return Collection of client classes. + virtual ClientClassDictionary + getAllClientClasses6(const db::ServerSelector& selector) const; + + /// @brief Retrieves client classes modified after specified time. + /// + /// @param selector Server selector. + /// @param modification_time Modification time. + /// @return Collection of client classes. + virtual ClientClassDictionary + getModifiedClientClasses6(const db::ServerSelector& selector, + const boost::posix_time::ptime& modification_time) const; + /// @brief Retrieves the most recent audit entries. /// /// @param server_selector Server selector. @@ -332,6 +356,19 @@ public: createUpdateGlobalParameter6(const db::ServerSelector& server_selector, const data::StampedValuePtr& value); + /// @brief Creates or updates DHCPv6 client class. + /// + /// @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 + createUpdateClientClass6(const db::ServerSelector& server_selector, + const ClientClassDefPtr& client_class, + const std::string& follow_client_class); + /// @brief Creates or updates a server. /// /// @param server Instance of the server to be stored. @@ -490,6 +527,22 @@ public: virtual uint64_t deleteAllGlobalParameters6(const db::ServerSelector& server_selector); + /// @brief Deletes DHCPv6 client class. + /// + /// @param server_selector Server selector. + /// @param name Name of the class to be deleted. + /// @return Number of deleted client classes. + virtual uint64_t + deleteClientClass6(const db::ServerSelector& server_selector, + const std::string& name); + + /// @brief Deletes all client classes. + /// + /// @param server_selector Server selector. + /// @return Number of deleted client classes. + virtual uint64_t + deleteAllClientClasses6(const db::ServerSelector& server_selector); + /// @brief Deletes a server from the backend. /// /// @param server_tag Tag of the server to be deleted. @@ -511,6 +564,7 @@ public: OptionDefContainer option_defs_; OptionContainer options_; data::StampedValueCollection globals_; + ClientClassDictionary classes_; db::ServerCollection servers_; /// @} };