[chmod +x src/share/database/scripts/mysql/upgrade_9.4_to_9.5.sh])
AC_CONFIG_FILES([src/share/database/scripts/mysql/upgrade_9.5_to_9.6.sh],
[chmod +x src/share/database/scripts/mysql/upgrade_9.5_to_9.6.sh])
+AC_CONFIG_FILES([src/share/database/scripts/mysql/upgrade_9.6_to_10.0.sh],
+ [chmod +x src/share/database/scripts/mysql/upgrade_9.6_to_10.0.sh])
AC_CONFIG_FILES([src/share/database/scripts/mysql/wipe_data.sh],
[chmod +x src/share/database/scripts/mysql/wipe_data.sh])
AC_CONFIG_FILES([src/share/database/scripts/pgsql/Makefile])
for script in "${db_scripts_dir}"/mysql/upgrade*.sh
do
+ version=$(mysql_version)
if [ "${version}" = "${target_version}" ]
then
break
echo "Processing $script file..."
"${script}" --user="${db_user}" --password="${db_password}" "${db_name}"
- version=$(mysql_version)
done
echo "Schema upgraded to $version"
version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
assert_str_eq "1.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
- # Ok, we have a 1.0 database. Let's upgrade it to 9.6
+ # Ok, we have a 1.0 database. Let's upgrade it to 10.0
run_command \
"${kea_admin}" db-upgrade mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
assert_eq 0 "${EXIT_CODE}" "kea-admin db-upgrade mysql failed, expected %d, returned non-zero status code %d\n"
mysql_execute "${qry}"
assert_eq 0 "${EXIT_CODE}" "${qry} failed: expected status code %d, returned %d"
- # Verify upgraded schema reports version 9.6
+ # Verify upgraded schema reports version 10.0
version=$("${kea_admin}" db-version mysql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}")
- assert_str_eq "9.6" "${version}" "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "10.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
# Let's wipe the whole database
mysql_wipe
test_finish 0
}
+# Verifies that several tables for holding client classes are created
+# and the triggers and stored procedures positioning the client classes
+# and validating their dependencies behave correctly.
+mysql_client_class_test() {
+ table_prefix="$1"
+
+ test_start "mysql_client_classes_test ${table_prefix}"
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ # We need to create an older database with lease data so we can
+ # verify the upgrade mechanisms which convert subnet id values
+ #
+ # Initialize database to schema 1.0.
+ mysql -u"${db_user}" -p"${db_password}" "${db_name}" < "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.mysql"
+
+ # Now upgrade to schema 10.0 that can contain client classes.
+ mysql_upgrade_schema_to_version 10.0
+
+ # Insert a new server.
+ sql=\
+"SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_server (tag, modification_ts) VALUES ('server1', NOW()); \
+ SET @disable_audit = 0;"
+
+ run_statement "insert servers" "$sql"
+
+ # Insert client class foo at the top of the hierarchy. It has no dependencies.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name, depend_on_known_directly) VALUES ('foo', NOW(), NULL, 1); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'all')); \
+ SET @disable_audit = 0; \
+ COMMIT;"
+ run_statement "insert client class foo" "$sql"
+
+ # Insert client class foobar after the foo class.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name) VALUES ('foobar', NOW(), NULL); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'server1')); \
+ SET @disable_audit = 0; \
+ COMMIT;"
+ run_statement "insert client class foobar" "$sql"
+
+ # Insert the client class bar at the end. This class depends on the client
+ # class foo.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ INSERT INTO ${table_prefix}_client_class (name, modification_ts, follow_class_name) VALUES ('bar', NOW(), 'foo'); \
+ SET @last_id = LAST_INSERT_ID(); \
+ INSERT INTO ${table_prefix}_client_class_server (class_id, server_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_server WHERE tag = 'server1')); \
+ INSERT INTO ${table_prefix}_client_class_dependency (class_id, dependency_id) \
+ VALUES (@last_id, (SELECT id FROM ${table_prefix}_client_class WHERE name = 'foo')); \
+ SET @disable_audit = 0; \
+ COMMIT;"
+ run_statement "insert client class bar" "$sql"
+
+ # Ensure that all thre classes have been added in the expected order.
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foo'";
+ run_statement "#get order index of class foo" "$sql" 1
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'bar'";
+ run_statement "#get order index of class bar" "$sql" 2
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foobar'";
+ run_statement "#get order index of class foobar" "$sql" 3
+
+ # Update the class bar moving behind the foobar class.
+ sql=\
+"START TRANSACTION; \
+ SET @disable_audit = 1; \
+ UPDATE ${table_prefix}_client_class SET follow_class_name = 'foobar' WHERE name = 'bar'; \
+ SET @disable_audit = 0; \
+ COMMIT;"
+ run_statement "update client class bar with re-positioning" "$sql"
+
+ # Check that the order of the last two classes was changed.
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'bar'";
+ run_statement "#get order index of class bar" "$sql" 4
+
+ sql="SELECT o.order_index FROM ${table_prefix}_client_class AS c \
+ INNER JOIN ${table_prefix}_client_class_order AS o \
+ ON c.id = o.class_id WHERE c.name = 'foobar'";
+ run_statement "#get order index of class foobar" "$sql" 3
+
+ # Let's wipe the whole database
+ mysql_wipe
+
+ test_finish 0
+}
+
# Run tests.
mysql_db_init_test
mysql_lease_stat_recount_test
mysql_unused_subnet_id_test
mysql_reservation_mode_upgrade_test
+mysql_client_class_test dhcp4
+mysql_client_class_test dhcp6
/// @name Current database schema version values.
//@{
-const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 9;
-const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 6;
+const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 10;
+const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
//@}
/upgrade_9.3_to_9.4.sh
/upgrade_9.4_to_9.5.sh
/upgrade_9.5_to_9.6.sh
+/upgrade_9.6_to_10.0.sh
/wipe_data.sh
mysql_SCRIPTS += upgrade_9.3_to_9.4.sh
mysql_SCRIPTS += upgrade_9.4_to_9.5.sh
mysql_SCRIPTS += upgrade_9.5_to_9.6.sh
+mysql_SCRIPTS += upgrade_9.6_to_10.0.sh
mysql_SCRIPTS += wipe_data.sh
DISTCLEANFILES = ${mysql_SCRIPTS}
# This line concludes database upgrade to version 9.6.
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ next_server INT UNSIGNED DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_directly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp4_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass4Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128))
+BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp4_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE id = class_id
+ );
+ REPLACE INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AINS AFTER INSERT ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AUPD AFTER UPDATE ON dhcp4_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp4_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_ADEL AFTER DELETE ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp4_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS BEFORE INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv4ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_dependency_AINS AFTER INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp4_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_directly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp6_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass6Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128))
+BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp6_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE id = class_id
+ );
+ REPLACE INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AINS AFTER INSERT ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AUPD AFTER UPDATE ON dhcp6_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp6_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_ADEL AFTER DELETE ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp6_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS BEFORE INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv6ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_dependency_AINS AFTER INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp6_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+# This line concludes database upgrade to version 10.
+
# Notes:
#
# Indexes
-# Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2016-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
DROP TABLE IF EXISTS dhcp4_shared_network_server;
DROP TABLE IF EXISTS dhcp4_subnet;
DROP TABLE IF EXISTS dhcp4_subnet_server;
+DROP TABLE IF EXISTS dhcp4_client_class;
+DROP TABLE IF EXISTS dhcp4_client_class_order;
+DROP TABLE IF EXISTS dhcp4_client_class_dependency;
+DROP TABLE IF EXISTS dhcp4_client_class_server;
DROP TABLE IF EXISTS dhcp6_audit;
DROP TABLE IF EXISTS dhcp6_global_parameter;
DROP TABLE IF EXISTS dhcp6_global_parameter_server;
DROP TABLE IF EXISTS dhcp6_shared_network_server;
DROP TABLE IF EXISTS dhcp6_subnet;
DROP TABLE IF EXISTS dhcp6_subnet_server;
+DROP TABLE IF EXISTS dhcp6_client_class;
+DROP TABLE IF EXISTS dhcp6_client_class_order;
+DROP TABLE IF EXISTS dhcp6_client_class_dependency;
+DROP TABLE IF EXISTS dhcp6_client_class_server;
DROP TABLE IF EXISTS modification;
DROP TABLE IF EXISTS parameter_data_type;
DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
DROP TRIGGER IF EXISTS dhcp4_global_parameter_AINS;
DROP TRIGGER IF EXISTS dhcp4_global_parameter_AUPD;
DROP TRIGGER IF EXISTS dhcp4_global_parameter_ADEL;
DROP TRIGGER IF EXISTS dhcp4_options_AINS;
DROP TRIGGER IF EXISTS dhcp4_options_AUPD;
DROP TRIGGER IF EXISTS dhcp4_options_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
DROP TABLE IF EXISTS dhcp6_audit_revision;
DROP PROCEDURE IF EXISTS createAuditRevisionDHCP6;
DROP PROCEDURE IF EXISTS createAuditEntryDHCP6;
DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
DROP TRIGGER IF EXISTS dhcp6_global_parameter_AINS;
DROP TRIGGER IF EXISTS dhcp6_global_parameter_AUPD;
DROP TRIGGER IF EXISTS dhcp6_global_parameter_ADEL;
DROP TRIGGER IF EXISTS dhcp6_options_AINS;
DROP TRIGGER IF EXISTS dhcp6_options_AUPD;
DROP TRIGGER IF EXISTS dhcp6_options_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
--- /dev/null
+#!/bin/sh
+
+# Copyright (C) 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "9.6"; then
+ printf 'This script upgrades 9.5 to 9.6. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ next_server INT UNSIGNED DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_directly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp4_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id) REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass4Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128))
+BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp4_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE id = class_id
+ );
+ REPLACE INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AINS AFTER INSERT ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AUPD AFTER UPDATE ON dhcp4_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp4_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_ADEL AFTER DELETE ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp4_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS BEFORE INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv4ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_dependency_AINS AFTER INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp4_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_directly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp6_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass6Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128))
+BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp6_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE id = class_id
+ );
+ REPLACE INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AINS AFTER INSERT ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AUPD AFTER UPDATE ON dhcp6_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp6_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_ADEL AFTER DELETE ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp6_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS BEFORE INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv6ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_dependency_AINS AFTER INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp6_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+# This line concludes database upgrade to version 10.
+EOF
DELETE FROM dhcp4_subnet_server;
DELETE FROM dhcp4_audit_revision;
DELETE FROM dhcp4_audit;
+DELETE FROM dhcp4_client_class;
+DELETE FROM dhcp4_client_class_order;
+DELETE FROM dhcp4_client_class_dependency;
+DELETE FROM dhcp4_client_class_server;
DELETE FROM dhcp6_global_parameter;
DELETE FROM dhcp6_global_parameter_server;
DELETE FROM dhcp6_option_def;
DELETE FROM dhcp6_subnet_server;
DELETE FROM dhcp6_audit;
DELETE FROM dhcp6_audit_revision;
+DELETE FROM dhcp6_client_class;
+DELETE FROM dhcp6_client_class_order;
+DELETE FROM dhcp6_client_class_dependency;
+DELETE FROM dhcp6_client_class_server;
DELETE FROM hosts;
DELETE FROM ipv6_reservations;
DELETE FROM lease4;