]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3770] Add client-classes to host back end
authorThomas Markwalder <tmark@isc.org>
Wed, 16 Jul 2025 17:01:33 +0000 (13:01 -0400)
committerThomas Markwalder <tmark@isc.org>
Wed, 16 Jul 2025 17:01:33 +0000 (13:01 -0400)
/src/hooks/dhcp/mysql/mysql_host_data_source.cc
/src/hooks/dhcp/pgsql/pgsql_host_data_source.cc
    Set and fetch host options' client-classes

/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
    Update tests

/src/share/database/scripts/mysql/upgrade_030_to_031.sh.in
/src/share/database/scripts/mysql/dhcpdb_create.mysql
/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
/src/share/database/scripts/pgsql/upgrade_029_to_030.sh.in
    update dhcp6_options table

src/bin/admin/tests/mysql_tests.sh.in
src/hooks/dhcp/mysql/mysql_host_data_source.cc
src/hooks/dhcp/pgsql/pgsql_host_data_source.cc
src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/testutils/host_data_source_utils.cc
src/share/database/scripts/mysql/dhcpdb_create.mysql
src/share/database/scripts/mysql/upgrade_030_to_031.sh.in
src/share/database/scripts/pgsql/dhcpdb_create.pgsql
src/share/database/scripts/pgsql/upgrade_029_to_030.sh.in

index d2058125cec8f4c804df394e6b5b00e29dafeea6..ad4f19deb50c29489f6f01ca2dc2c6de30c88ea6 100755 (executable)
@@ -1788,7 +1788,8 @@ INSERT INTO dhcp4_client_class(name, modification_ts) VALUES ('foo', now());\
 INSERT INTO dhcp4_options(code, scope_id, dhcp_client_class, modification_ts, client_classes)\
             VALUES (222, 2, 'foo', now(), '[  ]');\
 INSERT INTO dhcp6_client_class(name, modification_ts) VALUES ('foo', now());\
-INSERT INTO dhcp6_options(code, scope_id, dhcp_client_class, modification_ts) VALUES (222, 2, 'foo', now());\
+INSERT INTO dhcp6_options(code, scope_id, dhcp_client_class, modification_ts, client_classes)\
+            VALUES (222, 2, 'foo', now(), '[  ]');\
 SET @disable_audit = 0"
     run_command \
         mysql_execute "$qry"
index 900bb5a9b6d0303e1be26fa27c369c25d1ea6417..d4bfe0000240c6632c58a3b9f1b9c429c34dace4 100644 (file)
@@ -859,7 +859,7 @@ class MySqlHostWithOptionsExchange : public MySqlHostExchange {
 private:
 
     /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
-    static const size_t OPTION_COLUMNS = 8;
+    static const size_t OPTION_COLUMNS = 9;
 
     /// @brief Receives DHCPv4 or DHCPv6 options information from the
     /// dhcp4_options or dhcp6_options tables respectively.
@@ -900,6 +900,7 @@ private:
           persistent_index_(start_column_ + 5),
           cancelled_index_(start_column_ + 6),
           user_context_index_(start_column_ + 7),
+          client_classes_index_(start_column_ + 8),
           most_recent_option_id_(0) {
 
             memset(value_, 0, sizeof(value_));
@@ -958,20 +959,27 @@ private:
                          DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
             }
 
-            // Convert formatted_value to string as well.
+            // Convert formatted_value to string.
             std::string formatted_value;
             if (formatted_value_null_ == MLM_FALSE) {
                 formatted_value_[formatted_value_length_] = '\0';
                 formatted_value.assign(formatted_value_);
             }
 
-            // Convert user_context to string as well.
+            // Convert user_context to string.
             std::string user_context;
             if (user_context_null_ == MLM_FALSE) {
                 user_context_[user_context_length_] = '\0';
                 user_context.assign(user_context_);
             }
 
+            // Convert clietn classes to string.
+            std::string client_classes;
+            if (client_classes_null_ == MLM_FALSE) {
+                client_classes_[client_classes_length_] = '\0';
+                client_classes.assign(client_classes_);
+            }
+
             // Options are held in a binary or textual format in the database.
             // This is similar to having an option specified in a server
             // configuration file. Such option is converted to appropriate C++
@@ -1056,6 +1064,19 @@ private:
                               << "' is invalid JSON: " << ex.what());
                 }
             }
+
+            // Set option's class tag client classes. Should always be content
+            // per schema rules, even if its's an empty list '[  ]'.
+            if (!client_classes.empty()) {
+                try {
+                    auto cclist_element = Element::fromJSON(client_classes);
+                    desc.client_classes_.fromElement(cclist_element);
+                } catch (const std::exception& ex) {
+                    isc_throw(BadValue, "client classes '" << client_classes
+                              << "' is invalid JSON: " << ex.what());
+                }
+            }
+
             cfg->add(desc, space);
         }
 
@@ -1072,6 +1093,7 @@ private:
             columns[persistent_index_] = "persistent";
             columns[cancelled_index_] = "cancelled";
             columns[user_context_index_] = "user_context";
+            columns[client_classes_index_] = "client_classes";
         }
 
         /// @brief Initialize binding table fields for options.
@@ -1096,6 +1118,7 @@ private:
             formatted_value_null_ = MLM_FALSE;
             space_null_ = MLM_FALSE;
             user_context_null_ = MLM_FALSE;
+            client_classes_null_ = MLM_FALSE;
 
             memset(value_, 0, sizeof(value_));
             memset(formatted_value_, 0, sizeof(formatted_value_));
@@ -1155,6 +1178,14 @@ private:
             bind[user_context_index_].buffer_length = user_context_length_;
             bind[user_context_index_].length = &user_context_length_;
             bind[user_context_index_].is_null = &user_context_null_;
+
+            // client_classes : TEXT NOT NULL
+            client_classes_length_ = sizeof(client_classes_);
+            bind[client_classes_index_].buffer_type = MYSQL_TYPE_STRING;
+            bind[client_classes_index_].buffer = reinterpret_cast<char*>(client_classes_);
+            bind[client_classes_index_].buffer_length = client_classes_length_;
+            bind[client_classes_index_].length = &client_classes_length_;
+            bind[client_classes_index_].is_null = &client_classes_null_;
         }
 
     private:
@@ -1202,6 +1233,12 @@ private:
         /// @brief User context length.
         unsigned long user_context_length_;
 
+        /// @brief Buffer holding textual client classes of an option.
+        char client_classes_[CLIENT_CLASSES_MAX_LEN];
+
+        /// @brief User context length.
+        unsigned long client_classes_length_;
+
         /// @name Boolean values indicating if values of specific columns in
         /// the database are NULL.
         //@{
@@ -1223,6 +1260,9 @@ private:
 
         /// @brief Boolean flag indicating if the DHCPv4 option user context is NULL.
         my_bool user_context_null_;
+
+        /// @brief Boolean flag indicating if the DHCPv4 option client classes is NULL.
+        my_bool client_classes_null_;
         //@}
 
         /// @name Indexes of the specific columns
@@ -1252,6 +1292,9 @@ private:
         /// @brief User context
         size_t user_context_index_;
 
+        /// @brief Client classes
+        size_t client_classes_index_;
+
         /// @brief Option id for last processed row.
         uint32_t most_recent_option_id_;
     };
@@ -1893,7 +1936,7 @@ private:
 /// This class supports inserting both DHCPv4 and DHCPv6 options.
 class MySqlOptionExchange {
     /// @brief Number of columns in the option tables holding bindable values.
-    static const size_t OPTION_COLUMNS = 10;
+    static const size_t OPTION_COLUMNS = 11;
 
 public:
 
@@ -1901,10 +1944,12 @@ public:
     MySqlOptionExchange()
         : type_(0), value_len_(0), formatted_value_len_(0), space_(),
           space_len_(0), persistent_(false), cancelled_(false),
-          user_context_(), user_context_len_(0), subnet_id_(SUBNET_ID_UNUSED),
+          user_context_(), user_context_len_(0),
+          client_classes_(), client_classes_len_(0),
+          subnet_id_(SUBNET_ID_UNUSED),
           host_id_(0), option_() {
 
-        BOOST_STATIC_ASSERT(10 <= OPTION_COLUMNS);
+        BOOST_STATIC_ASSERT(11 <= OPTION_COLUMNS);
     }
 
     /// @brief Creates binding array to insert option data into database.
@@ -2002,22 +2047,30 @@ public:
                 bind_[7].buffer_type = MYSQL_TYPE_NULL;
             }
 
+            // Client classes: TEXT NOT NULL
+            client_classes_ = opt_desc.client_classes_.toElement()->str();
+            client_classes_len_ = client_classes_.size();
+            bind_[8].buffer_type = MYSQL_TYPE_STRING;
+            bind_[8].buffer = const_cast<char*>(client_classes_.c_str());
+            bind_[8].buffer_length = client_classes_len_;
+            bind_[8].length = &client_classes_len_;
+
             // dhcp4_subnet_id: INT UNSIGNED NULL
             if (!subnet_id.unspecified()) {
                 subnet_id_ = subnet_id;
-                bind_[8].buffer_type = MYSQL_TYPE_LONG;
-                bind_[8].buffer = reinterpret_cast<char*>(subnet_id_);
-                bind_[8].is_unsigned = MLM_TRUE;
+                bind_[9].buffer_type = MYSQL_TYPE_LONG;
+                bind_[9].buffer = reinterpret_cast<char*>(subnet_id_);
+                bind_[9].is_unsigned = MLM_TRUE;
 
             } else {
-                bind_[8].buffer_type = MYSQL_TYPE_NULL;
+                bind_[9].buffer_type = MYSQL_TYPE_NULL;
             }
 
             // host_id: INT UNSIGNED NOT NULL
             host_id_ = host_id;
-            bind_[9].buffer_type = MYSQL_TYPE_LONG;
-            bind_[9].buffer = reinterpret_cast<char*>(&host_id_);
-            bind_[9].is_unsigned = MLM_TRUE;
+            bind_[10].buffer_type = MYSQL_TYPE_LONG;
+            bind_[10].buffer = reinterpret_cast<char*>(&host_id_);
+            bind_[10].is_unsigned = MLM_TRUE;
 
         } catch (const std::exception& ex) {
             isc_throw(DbOperationError,
@@ -2063,6 +2116,12 @@ private:
     /// @brief User context length.
     unsigned long user_context_len_;
 
+    /// @brief Client classes.
+    std::string client_classes_;
+
+    /// @brief Client classes length.
+    unsigned long client_classes_len_;
+
     /// @brief Subnet identifier.
     uint32_t subnet_id_;
 
@@ -2440,9 +2499,9 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
-                "o4.persistent, o4.cancelled, o4.user_context, "
+                "o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
                 "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
-                "o6.persistent, o6.cancelled, o6.user_context, "
+                "o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2465,7 +2524,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM hosts AS h "
             "LEFT JOIN dhcp4_options AS o "
                 "ON h.host_id = o.host_id "
@@ -2482,7 +2541,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM hosts AS h "
             "LEFT JOIN dhcp4_options AS o "
                 "ON h.host_id = o.host_id "
@@ -2501,7 +2560,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2524,7 +2583,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM hosts AS h "
             "LEFT JOIN dhcp4_options AS o "
                 "ON h.host_id = o.host_id "
@@ -2545,7 +2604,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context,"
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2572,7 +2631,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2600,7 +2659,7 @@ TaggedStatementArray tagged_statements = { {
      "h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "h.dhcp4_boot_file_name, h.auth_key, "
      "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "o.persistent, o.cancelled, o.user_context, "
+     "o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "r.reservation_id, r.address, r.prefix_len, r.type, "
      "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -2623,7 +2682,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM hosts AS h "
             "LEFT JOIN dhcp4_options AS o "
                 "ON h.host_id = o.host_id "
@@ -2642,7 +2701,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2665,9 +2724,9 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
-                "o4.persistent, o4.cancelled, o4.user_context, "
+                "o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
                 "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
-                "o6.persistent, o6.cancelled, o6.user_context, "
+                "o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2690,7 +2749,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM hosts AS h "
             "LEFT JOIN dhcp4_options AS o "
                 "ON h.host_id = o.host_id "
@@ -2708,7 +2767,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM hosts AS h "
@@ -2731,7 +2790,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM ( SELECT * FROM hosts AS h "
                     "WHERE h.dhcp4_subnet_id = ? AND h.host_id > ? "
                     "ORDER BY h.host_id "
@@ -2753,7 +2812,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM ( SELECT * FROM hosts AS h "
@@ -2778,7 +2837,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes "
             "FROM ( SELECT * FROM hosts AS h "
                     "WHERE h.host_id > ? "
                     "ORDER BY h.host_id "
@@ -2800,7 +2859,7 @@ TaggedStatementArray tagged_statements = { {
                 "h.dhcp4_next_server, h.dhcp4_server_hostname, "
                 "h.dhcp4_boot_file_name, h.auth_key, "
                 "o.option_id, o.code, o.value, o.formatted_value, o.space, "
-                "o.persistent, o.cancelled, o.user_context, "
+                "o.persistent, o.cancelled, o.user_context, o.client_classes, "
                 "r.reservation_id, r.address, r.prefix_len, r.type, "
                 "r.dhcp6_iaid, r.excluded_prefix, r.excluded_prefix_len "
             "FROM ( SELECT * FROM hosts AS h "
@@ -2870,15 +2929,17 @@ TaggedStatementArray tagged_statements = { {
     // Using fixed scope_id = 3, which associates an option with host.
     {MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
             "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
-                "persistent, cancelled, user_context, dhcp4_subnet_id, host_id, scope_id) "
-            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+                "persistent, cancelled, user_context, client_classes, "
+                "dhcp4_subnet_id, host_id, scope_id) "
+            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
 
     // Inserts a single DHCPv6 option into 'dhcp6_options' table.
     // Using fixed scope_id = 3, which associates an option with host.
     {MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
             "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
-                "persistent, cancelled, user_context, dhcp6_subnet_id, host_id, scope_id) "
-            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+                "persistent, cancelled, user_context, client_classes, "
+                "dhcp6_subnet_id, host_id, scope_id) "
+            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
 
     // Delete IPv4 reservations by subnet id and reserved address.
     {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
index 37cca250d07a9af07e245b5aa678ad69b727a519..12fc2d05a6e002054cd19542e189b17f8baca7c3 100644 (file)
@@ -477,7 +477,7 @@ class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
 private:
 
     /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
-    static const size_t OPTION_COLUMNS = 8;
+    static const size_t OPTION_COLUMNS = 9;
 
     /// @brief Receives DHCPv4 or DHCPv6 options information from the
     /// dhcp4_options or dhcp6_options tables respectively.
@@ -512,6 +512,7 @@ private:
           persistent_index_(start_column_ + 5),
           cancelled_index_(start_column_ + 6),
           user_context_index_(start_column_ + 7),
+          client_classes_index_(start_column_ + 8),
           most_recent_option_id_(0) {
         }
 
@@ -626,6 +627,13 @@ private:
                                               user_context);
             }
 
+            // client_classes: TEXT
+            std::string client_classes;
+            if (!isColumnNull(r, row, client_classes_index_)) {
+                PgSqlExchange::getColumnValue(r, row, client_classes_index_,
+                                              client_classes);
+            }
+
             // Options are held in a binary or textual format in the database.
             // This is similar to having an option specified in a server
             // configuration file. Such option is converted to appropriate C++
@@ -703,6 +711,18 @@ private:
                 }
             }
 
+            // Set option's class tag client classes. Should always be content
+            // per schema rules, even if its's an empty list '[  ]'.
+            if (!client_classes.empty()) {
+                try {
+                    auto cclist_element = Element::fromJSON(client_classes);
+                    desc.client_classes_.fromElement(cclist_element);
+                } catch (const std::exception& ex) {
+                    isc_throw(BadValue, "client classes '" << client_classes
+                              << "' is invalid JSON: " << ex.what());
+                }
+            }
+
             cfg->add(desc, space);
         }
 
@@ -719,6 +739,7 @@ private:
             columns[persistent_index_] = "persistent";
             columns[cancelled_index_] = "cancelled";
             columns[user_context_index_] = "user_context";
+            columns[client_classes_index_] = "client_classes";
         }
 
     private:
@@ -757,6 +778,9 @@ private:
         /// @brief User context
         size_t user_context_index_;
 
+        /// @brief Client classes
+        size_t client_classes_index_;
+
         /// @brief Option id for last processed row.
         uint64_t most_recent_option_id_;
     };
@@ -1220,10 +1244,11 @@ private:
     static const size_t PERSISTENT_COL = 5;
     static const size_t CANCELLED_COL = 6;
     static const size_t USER_CONTEXT_COL = 7;
-    static const size_t DHCP_SUBNET_ID_COL = 8;
-    static const size_t HOST_ID_COL = 9;
+    static const size_t CLIENT_CLASSES_COL = 8;
+    static const size_t DHCP_SUBNET_ID_COL = 9;
+    static const size_t HOST_ID_COL = 10;
     /// @brief Number of columns in the option tables holding bindable values.
-    static const size_t OPTION_COLUMNS = 10;
+    static const size_t OPTION_COLUMNS = 11;
 
 public:
 
@@ -1239,10 +1264,11 @@ public:
         columns_[PERSISTENT_COL] = "persistent";
         columns_[CANCELLED_COL] = "cancelled";
         columns_[USER_CONTEXT_COL] = "user_context";
+        columns_[CLIENT_CLASSES_COL] = "client_classes";
         columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
         columns_[HOST_ID_COL] = "host_id";
 
-        BOOST_STATIC_ASSERT(10 <= OPTION_COLUMNS);
+        BOOST_STATIC_ASSERT(11 <= OPTION_COLUMNS);
     }
 
     /// @brief Creates binding array to insert option data into database.
@@ -1317,6 +1343,10 @@ public:
                 bind_array->addNull();
             }
 
+            // Client classes: TEXT NOT NULL
+            auto client_classes = opt_desc.client_classes_.toElement()->str();
+            bind_array->addTempString(client_classes);
+
             // host_id: INT NULL
             if (!host_id) {
                 isc_throw(BadValue, "host_id cannot be null");
@@ -1702,9 +1732,9 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
-     "  o4.persistent, o4.cancelled, o4.user_context, "
+     "  o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
      "  o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
-     "  o6.persistent, o6.cancelled, o6.user_context, "
+     "  o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1728,7 +1758,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM hosts AS h "
      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
      "WHERE ipv4_address = $1 "
@@ -1748,7 +1778,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM hosts AS h "
      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
      "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
@@ -1770,7 +1800,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1795,7 +1825,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM hosts AS h "
      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
      "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
@@ -1819,7 +1849,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1848,7 +1878,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1878,7 +1908,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1905,7 +1935,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM hosts AS h "
      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
      "WHERE h.dhcp4_subnet_id = $1 "
@@ -1932,7 +1962,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1958,9 +1988,9 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
-     "  o4.persistent, o4.cancelled, o4.user_context, "
+     "  o4.persistent, o4.cancelled, o4.user_context, o4.client_classes, "
      "  o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
-     "  o6.persistent, o6.cancelled, o6.user_context, "
+     "  o6.persistent, o6.cancelled, o6.user_context, o6.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -1985,7 +2015,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM hosts AS h "
      "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
      "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
@@ -2009,7 +2039,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM hosts AS h "
@@ -2033,7 +2063,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM ( SELECT * FROM hosts AS h "
      "       WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
      "       ORDER BY h.host_id "
@@ -2059,7 +2089,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM ( SELECT * FROM hosts AS h "
@@ -2085,7 +2115,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes "
      "FROM ( SELECT * FROM hosts AS h "
      "       WHERE h.host_id > $1 "
      "       ORDER BY h.host_id "
@@ -2111,7 +2141,7 @@ TaggedStatementArray tagged_statements = { {
      "  h.dhcp4_next_server, h.dhcp4_server_hostname, "
      "  h.dhcp4_boot_file_name, h.auth_key, "
      "  o.option_id, o.code, o.value, o.formatted_value, o.space, "
-     "  o.persistent, o.cancelled, o.user_context, "
+     "  o.persistent, o.cancelled, o.user_context, o.client_classes, "
      "  r.reservation_id, host(r.address), r.prefix_len, r.type, "
      "  r.dhcp6_iaid, host(r.excluded_prefix), r.excluded_prefix_len "
      "FROM ( SELECT * FROM hosts AS h "
@@ -2201,25 +2231,25 @@ TaggedStatementArray tagged_statements = { {
     // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
     // Inserts a single DHCPv4 option into 'dhcp4_options' table.
     // Using fixed scope_id = 3, which associates an option with host.
-    {8,
+    {9,
      { OID_INT2, OID_BYTEA, OID_TEXT,
-       OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_INT8 },
+       OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_TEXT, OID_INT8 },
      "insert_v4_host_option",
      "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
-     "  persistent, cancelled, user_context, host_id, scope_id) "
-     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 3)"
+     "  persistent, cancelled, user_context, client_classes, host_id, scope_id) "
+     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 3)"
     },
 
     // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
     // Inserts a single DHCPv6 option into 'dhcp6_options' table.
     // Using fixed scope_id = 3, which associates an option with host.
-    {8,
+    {9,
      { OID_INT2, OID_BYTEA, OID_TEXT,
-       OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_INT8 },
+       OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_TEXT, OID_INT8 },
      "insert_v6_host_option",
      "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
-     "  persistent, cancelled, user_context, host_id, scope_id) "
-     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 3)"
+     "  persistent, cancelled, user_context, client_classes, host_id, scope_id) "
+     "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 3)"
     },
 
     // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
index 0e99bc8ce2c8b88232b077d67789209745482ece..74703258d1e7658e562c5b252b434cde7eb62301 100644 (file)
@@ -122,6 +122,8 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
             createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
                                        true, false, formatted, "my-boot-file");
         desc.setContext(user_context);
+        desc.addClientClass("class-one");
+        desc.addClientClass("class-two");
         opts->add(desc, DHCP4_OPTION_SPACE);
         opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
                                             false, false, formatted, 64),
@@ -162,6 +164,8 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
             createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
                                        true, false, formatted, "my-boot-file");
         desc.setContext(user_context);
+        desc.addClientClass("class-one");
+        desc.addClientClass("class-two");
         opts->add(desc, DHCP6_OPTION_SPACE);
         opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
                                              false, false, formatted, 3600),
index 6a012847911e67731681cce16f43152b3fa36b25..c3b1f45a21a360d1650b73921d852d6650938022 100644 (file)
@@ -427,6 +427,9 @@ HostDataSourceUtils::compareOptions(const ConstCfgOptionPtr& cfg1,
                 EXPECT_FALSE(ctx2);
             }
 
+            // Compare client classes.
+            EXPECT_EQ(desc1.client_classes_, desc2.client_classes_);
+
             // Retrieve options.
             Option* option1 = desc1.option_.get();
             Option* option2 = desc2.option_.get();
index 5076c0777ab2a0929746c1d8ae05ecd8b2887612..c16b4c11be3385c7415d0b4cfbc145e878ea3e1c 100644 (file)
@@ -6441,6 +6441,8 @@ UPDATE schema_version
 SET @disable_audit = 1;
 UPDATE dhcp4_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
 ALTER TABLE dhcp4_options MODIFY COLUMN client_classes LONGTEXT NOT NULL;
+UPDATE dhcp6_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
+ALTER TABLE dhcp6_options MODIFY COLUMN client_classes LONGTEXT NOT NULL;
 SET @disable_audit = 0;
 
 -- Update the schema version number.
index 860de914ecea01467d7dd9ba50971c7b6309225f..d2e8a991b9a38b70ccd1598dc310a1b621a3fb38 100755 (executable)
@@ -58,6 +58,8 @@ mysql "$@" <<EOF
 SET @disable_audit = 1;
 UPDATE dhcp4_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
 ALTER TABLE dhcp4_options MODIFY COLUMN client_classes LONGTEXT NOT NULL;
+UPDATE dhcp6_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
+ALTER TABLE dhcp6_options MODIFY COLUMN client_classes LONGTEXT NOT NULL;
 SET @disable_audit = 0;
 
 -- Update the schema version number.
index dc29038044ebe8455ff88036244c924d51513df2..11f471a1539d2e015b98848bc53a13b2b036247f 100644 (file)
@@ -6716,6 +6716,8 @@ UPDATE schema_version
 SELECT set_config('kea.disable_audit', 'true', false);
 UPDATE dhcp4_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
 ALTER TABLE dhcp4_options ALTER COLUMN client_classes SET NOT NULL;
+UPDATE dhcp6_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
+ALTER TABLE dhcp6_options ALTER COLUMN client_classes SET NOT NULL;
 SELECT set_config('kea.disable_audit', 'false', false);
 
 -- Update the schema version number.
index 46ac964df9fce652fb1d2266b0e0f33c39784629..106500779d38357826182bdd12c0f771a0a2e6b4 100755 (executable)
@@ -42,6 +42,8 @@ START TRANSACTION;
 SELECT set_config('kea.disable_audit', 'true', false);
 UPDATE dhcp4_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
 ALTER TABLE dhcp4_options ALTER COLUMN client_classes SET NOT NULL;
+UPDATE dhcp6_options SET client_classes = '[  ]' WHERE client_classes IS NULL;
+ALTER TABLE dhcp6_options ALTER COLUMN client_classes SET NOT NULL;
 SELECT set_config('kea.disable_audit', 'false', false);
 
 -- Update the schema version number.