]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1928] DHCPv4 client classes in MySQL
authorMarcin Siodelski <marcin@isc.org>
Wed, 16 Jun 2021 13:04:35 +0000 (15:04 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 21 Jul 2021 10:49:50 +0000 (10:49 +0000)
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.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_dhcp4_unittest.cc
src/lib/config_backend/constants.h

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