]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1928] DHCPv6 MySQL Config Backend
authorMarcin Siodelski <marcin@isc.org>
Mon, 5 Jul 2021 08:55:49 +0000 (10:55 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 21 Jul 2021 11:03:29 +0000 (13:03 +0200)
Implemented DHCP6 Config Backend similar to the DHCPv4.

src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.h
src/hooks/dhcp/mysql_cb/mysql_cb_messages.cc
src/hooks/dhcp/mysql_cb/mysql_cb_messages.h
src/hooks/dhcp/mysql_cb/mysql_cb_messages.mes
src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc
src/lib/dhcpsrv/config_backend_dhcp6.h
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h

index 1c7f385f19697d393b0e4b98d13473da90636b5b..fb85fefac5928b98937fc3752fffe43e8229a7e6 100644 (file)
@@ -25,6 +25,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>
@@ -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<uint16_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::createNull(),
+            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,
+                           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<uint64_t>(), // id
+            MySqlBinding::createString(CLIENT_CLASS_NAME_BUF_LENGTH), // name
+            MySqlBinding::createString(CLIENT_CLASS_TEST_BUF_LENGTH), // test
+            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<uint16_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());
+                }
+
+                // 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<uint64_t>())) {
+                last_option_def_id = out_bindings[10]->getInteger<uint64_t>();
+
+                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<uint64_t>())) {
+                last_option_id = out_bindings[20]->getInteger<uint64_t>();
+
+                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<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::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, 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)
index 08722cafd44c202e38a420ffd6aca24a353ad1b3..8c80741e3b80f220a5814dc2ea62019d4f1a725d 100644 (file)
@@ -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.
index b934cf793ca9d87fe5c28e8ba4703218d1118921..a38af9f5c6212ce61e0a9053998f45f62892c701 100644 (file)
@@ -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",
index 1b51c564e02ef732c59afbaf81802c79ded447d4..418cefc5cc4c34351a9ba79145815ba71e755159 100644 (file)
@@ -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;
index 75c26e9736020f3ded9e38c51266440d87e54407..8776e15d6784ca0f19f593297b472d1769dd9687 100644 (file)
@@ -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
 
index 503bd179ed17e4db5493d82195ae44c8abc79888..7808fd5be973f72bb3a19380f5b0182dd7e7af7c 100644 (file)
@@ -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
index d66f61066988592ff8c972e9ec1f94c4488b3d48..a9bb548557f0e4d09cbee12911d6cc03e9cdc803 100644 (file)
@@ -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<Expression>();
+        CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+        auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+        class1->setRequired(true);
+        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.
@@ -591,6 +610,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_;
 
@@ -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<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_->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<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_->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
index dd200cb7609bca6e8ad60dfe08c32e50f2382861..deacfecd1d499451b9169e3fb218a639e5bdbfe8 100644 (file)
@@ -17,6 +17,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/client_class_def.h>
 #include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <boost/shared_ptr.hpp>
@@ -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.
index cbc207535886a602aff3d907d4eec2a536ebb90e..d69b286899e352e5d8bf887a29b2644c90bb5368 100644 (file)
@@ -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<ClientClassDef>(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<ClientClassDef>(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<ClientClassDef>(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<std::string> 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<ServerTagIndexTag>();
index c13b50522febdd84094fd4c4c1d49bc7186f33ed..012120194bd2cd8ed316b6d2fbeab8eda2f47223 100644 (file)
@@ -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_;
 /// @}
 };