From: Thomas Markwalder Date: Wed, 16 Jul 2025 17:01:33 +0000 (-0400) Subject: [#3770] Add client-classes to host back end X-Git-Tag: Kea-3.1.0~28 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=49073d4af1425737977cd6108b702e5696c07c3b;p=thirdparty%2Fkea.git [#3770] Add client-classes to host back end /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 --- diff --git a/src/bin/admin/tests/mysql_tests.sh.in b/src/bin/admin/tests/mysql_tests.sh.in index d2058125ce..ad4f19deb5 100755 --- a/src/bin/admin/tests/mysql_tests.sh.in +++ b/src/bin/admin/tests/mysql_tests.sh.in @@ -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" diff --git a/src/hooks/dhcp/mysql/mysql_host_data_source.cc b/src/hooks/dhcp/mysql/mysql_host_data_source.cc index 900bb5a9b6..d4bfe00002 100644 --- a/src/hooks/dhcp/mysql/mysql_host_data_source.cc +++ b/src/hooks/dhcp/mysql/mysql_host_data_source.cc @@ -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(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(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(subnet_id_); - bind_[8].is_unsigned = MLM_TRUE; + bind_[9].buffer_type = MYSQL_TYPE_LONG; + bind_[9].buffer = reinterpret_cast(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(&host_id_); - bind_[9].is_unsigned = MLM_TRUE; + bind_[10].buffer_type = MYSQL_TYPE_LONG; + bind_[10].buffer = reinterpret_cast(&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, diff --git a/src/hooks/dhcp/pgsql/pgsql_host_data_source.cc b/src/hooks/dhcp/pgsql/pgsql_host_data_source.cc index 37cca250d0..12fc2d05a6 100644 --- a/src/hooks/dhcp/pgsql/pgsql_host_data_source.cc +++ b/src/hooks/dhcp/pgsql/pgsql_host_data_source.cc @@ -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 diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index 0e99bc8ce2..74703258d1 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -122,6 +122,8 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host, createOption(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(Option::V4, DHO_DEFAULT_IP_TTL, false, false, formatted, 64), @@ -162,6 +164,8 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host, createOption(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(Option::V6, D6O_INFORMATION_REFRESH_TIME, false, false, formatted, 3600), diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc index 6a01284791..c3b1f45a21 100644 --- a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc +++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc @@ -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(); diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql index 5076c0777a..c16b4c11be 100644 --- a/src/share/database/scripts/mysql/dhcpdb_create.mysql +++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql @@ -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. diff --git a/src/share/database/scripts/mysql/upgrade_030_to_031.sh.in b/src/share/database/scripts/mysql/upgrade_030_to_031.sh.in index 860de914ec..d2e8a991b9 100755 --- a/src/share/database/scripts/mysql/upgrade_030_to_031.sh.in +++ b/src/share/database/scripts/mysql/upgrade_030_to_031.sh.in @@ -58,6 +58,8 @@ mysql "$@" <