]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5416] Allow null or 0 values in unique indexes within hosts table.
authorMarcin Siodelski <marcin@isc.org>
Tue, 7 Nov 2017 09:26:14 +0000 (10:26 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 7 Nov 2017 09:27:05 +0000 (10:27 +0100)
Also, updated MySQL backend to convert 0 values to null to make sure that
multiple reservations are possible when IPv4 address is unspecified.

13 files changed:
configure.ac
src/bin/admin/tests/pgsql_tests.sh.in
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/pgsql_connection.h
src/lib/dhcpsrv/pgsql_host_data_source.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
src/share/database/scripts/pgsql/.gitignore
src/share/database/scripts/pgsql/Makefile.am
src/share/database/scripts/pgsql/dhcpdb_create.pgsql
src/share/database/scripts/pgsql/upgrade_3.1_to_3.2.sh.in [new file with mode: 0644]

index 22045c6dce2e886471b34f714b2dc30556c86465..2fa156d5b5a42269af362eb8a47c3481c48fe6da 100644 (file)
@@ -1331,6 +1331,7 @@ AC_CONFIG_FILES([Makefile
                  src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh
                  src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh
                  src/share/database/scripts/pgsql/upgrade_3.0_to_3.1.sh
+                 src/share/database/scripts/pgsql/upgrade_3.1_to_3.2.sh
                  tools/Makefile
                  tools/path_replacer.sh
 ])
index da91db5fcfa218cd04f978a80ccb82c82782c58c..41b7437e025f7febc64e564f1ecbe260a5db4906 100644 (file)
@@ -89,7 +89,7 @@ pgsql_lease_version_test() {
 
     # Verify that kea-admin lease-version returns the correct version
     version=$(${keaadmin} lease-version pgsql -u $db_user -p $db_password -n $db_name)
-    assert_str_eq "3.1" ${version} "Expected kea-admin to return %s, returned value was %s"
+    assert_str_eq "3.2" ${version} "Expected kea-admin to return %s, returned value was %s"
 
     # Let's wipe the whole database
     pgsql_wipe
index 82fd38d6a553b3a790cfe0c8af799409818bede3..b8709bf5d420aad6b8ec9405eb596fd9042890e0 100644 (file)
@@ -83,6 +83,26 @@ const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_T
 /// options associated with a host to minimize impact on performance. Other
 /// classes derived from @ref MySqlHostExchange should be used to retrieve
 /// information about IPv6 reservations and options.
+///
+/// Database schema contains several unique indexes to guard against adding
+/// multiple hosts for the same client identifier in a single subnet and for
+/// adding multiple hosts with a reservation for the same IPv4 address in a
+/// single subnet. The exceptions that has to be taken into account are
+/// listed below:
+/// - zero or null IPv4 address indicates that there is no reservation for the
+///   IPv4 address for the host,
+/// - zero or null subnet identifier (either IPv4 or IPv6) indicates that
+///   this subnet identifier must be ignored. Specifically, this is the case
+///   when host reservation is created DHCPv4 server, the IPv6 subnet id should
+///   be ignored. Conversely, when host reservation is created for DHCPv6 server,
+///   the IPv4 subnet id should be ignored.
+///
+/// To exclude those special case values from the unique indexes, the MySQL
+/// backend relies on the property of the unique indexes in MySQL, i.e. null
+/// values are excluded from unique indexes. That means that there might be
+/// multiple null values in a given column on which unique index is applied.
+/// Therefore, the MySQL backend converts subnet identifiers and IPv4 addresses
+/// of 0 to null before inserting a host to the database.
 class MySqlHostExchange {
 private:
 
@@ -279,17 +299,21 @@ public:
             // Can't take an address of intermediate object, so let's store it
             // in dhcp4_subnet_id_
             dhcp4_subnet_id_ = host->getIPv4SubnetID();
+            dhcp4_subnet_id_null_ = host->getIPv4SubnetID() == 0 ? MLM_TRUE : MLM_FALSE;
             bind_[3].buffer_type = MYSQL_TYPE_LONG;
             bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
             bind_[3].is_unsigned = MLM_TRUE;
+            bind_[3].is_null = &dhcp4_subnet_id_null_;
 
             // dhcp6_subnet_id : INT UNSIGNED NULL
             // Can't take an address of intermediate object, so let's store it
             // in dhcp6_subnet_id_
             dhcp6_subnet_id_ = host->getIPv6SubnetID();
+            dhcp6_subnet_id_null_ = host->getIPv6SubnetID() == 0 ? MLM_TRUE : MLM_FALSE;
             bind_[4].buffer_type = MYSQL_TYPE_LONG;
             bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
             bind_[4].is_unsigned = MLM_TRUE;
+            bind_[4].is_null = &dhcp6_subnet_id_null_;
 
             // ipv4_address : INT UNSIGNED NULL
             // The address in the Host structure is an IOAddress object.  Convert
index 9788972535794e2f975a053da61166d58f87ef8d..9aab9bfa3559f8adc3baa05383fe7312a888aab2 100644 (file)
@@ -17,9 +17,9 @@
 namespace isc {
 namespace dhcp {
 
-/// @brief Define PostgreSQL backend version: 3.0
+/// @brief Define PostgreSQL backend version: 3.2
 const uint32_t PG_SCHEMA_VERSION_MAJOR = 3;
-const uint32_t PG_SCHEMA_VERSION_MINOR = 1;
+const uint32_t PG_SCHEMA_VERSION_MINOR = 2;
 
 // Maximum number of parameters that can be used a statement
 // @todo This allows us to use an initializer list (since we can't
index ef0703f5bc191e84fbfca198b706117228b65a3b..beee96c06f331f196c065e6cd1df03dabcae661d 100644 (file)
@@ -57,6 +57,23 @@ const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
 /// options associated with a host to minimize impact on performance. Other
 /// classes derived from @ref PgSqlHostExchange should be used to retrieve
 /// information about IPv6 reservations and options.
+///
+/// Database schema contains several unique indexes to guard against adding
+/// multiple hosts for the same client identifier in a single subnet and for
+/// adding multiple hosts with a reservation for the same IPv4 address in a
+/// single subnet. The exceptions that has to be taken into account are
+/// listed below:
+/// - zero or null IPv4 address indicates that there is no reservation for the
+///   IPv4 address for the host,
+/// - zero or null subnet identifier (either IPv4 or IPv6) indicates that
+///   this subnet identifier must be ignored. Specifically, this is the case
+///   when host reservation is created DHCPv4 server, the IPv6 subnet id should
+///   be ignored. Conversely, when host reservation is created for DHCPv6 server,
+///   the IPv4 subnet id should be ignored.
+///
+/// To exclude those special case values, the Postgres backend uses partial
+/// indexes, i.e. the only values that are included in the index are those that
+/// are non-zero and non-null.
 class PgSqlHostExchange : public PgSqlExchange {
 private:
 
index 75e46c14b4bf644b0cce4a6a18b9d15799bc8930..71f9f46f7e4665dfe7e03beec2c2e5d2f09aa7d2 100644 (file)
@@ -784,10 +784,10 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
     ASSERT_TRUE(hdsptr_);
 
     HostPtr host = initializeHost4("192.0.2.1", id);
+    host->setIPv6SubnetID(0);
 
     for (int i = 0; i < subnets; ++i) {
         host->setIPv4SubnetID(i + 1000);
-        host->setIPv6SubnetID(i + 1000);
 
         // Check that the same host can have reservations in multiple subnets.
         EXPECT_NO_THROW(hdsptr_->add(host));
@@ -1655,6 +1655,54 @@ void GenericHostDataSourceTest::testDeleteById6Options() {
     EXPECT_EQ(0, countDBReservations6());
 }
 
+void
+GenericHostDataSourceTest::testMultipleHostsNoAddress4() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create a host with zero IPv4 address.
+    HostPtr host1 = initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+    host1->setIPv4SubnetID(1);
+    host1->setIPv6SubnetID(0);
+    // Add the host to the database.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+
+    // An attempt to add this host again should fail due to client identifier
+    // duplication.
+    ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+    // Create another host with zero IPv4 address. Adding this host to the
+    // database should be successful because zero addresses are not counted
+    // in the unique index.
+    HostPtr host2 = initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+    host2->setIPv4SubnetID(1);
+    host2->setIPv6SubnetID(0);
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
+void
+GenericHostDataSourceTest::testMultipleHosts6() {
+    // Make sure we have a pointer to the host data source.
+    ASSERT_TRUE(hdsptr_);
+
+    // Create first host.
+    HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+    host1->setIPv4SubnetID(0);
+    host1->setIPv6SubnetID(1);
+    // Add the host to the database.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+
+    // An attempt to add this host again should fail due to client identifier
+    // duplication.
+    ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+    HostPtr host2 = initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+    host2->setIPv4SubnetID(0);
+    host2->setIPv6SubnetID(1);
+    // Add the host to the database.
+    ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc
index c5bef733460c105109092257fcbd39e665f53521..d41d539de1af10f426a810d03a33cbd536113622 100644 (file)
@@ -580,6 +580,17 @@ public:
     /// Uses gtest macros to report failures.
     void testDeleteById6Options();
 
+    /// @brief Tests that multiple reservations without IPv4 addresses can be
+    /// specified within a subnet.
+    ///
+    /// Uses gtest macros to report failures.
+    void testMultipleHostsNoAddress4();
+
+    /// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+    ///
+    /// Uses gtest macros to report failures.
+    void testMultipleHosts6();
+
     /// @brief Returns DUID with identical content as specified HW address
     ///
     /// This method does not have any sense in real life and is only useful
index 2ca2b0f67a3297d058bba88103d777a888249c48..da76ca04748e7038e03451d0db98248b80624216 100644 (file)
@@ -605,4 +605,15 @@ TEST_F(MySqlHostDataSourceTest, deleteById6Options) {
     testDeleteById6Options();
 }
 
+// Tests that multiple reservations without IPv4 addresses can be
+// specified within a subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHostsNoAddress4) {
+    testMultipleHostsNoAddress4();
+}
+
+// Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHosts6) {
+    testMultipleHosts6();
+}
+
 }; // Of anonymous namespace
index f9aae9d93d3daa0a0022cc054d33d1479339c771..d0dbba539451627ecc2da8cdd6a376c4fcbc337f 100644 (file)
@@ -562,4 +562,15 @@ TEST_F(PgSqlHostDataSourceTest, deleteById6Options) {
     testDeleteById6Options();
 }
 
+// Tests that multiple reservations without IPv4 addresses can be
+// specified within a subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHostsNoAddress4) {
+    testMultipleHostsNoAddress4();
+}
+
+// Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHosts6) {
+    testMultipleHosts6();
+}
+
 }; // Of anonymous namespace
index ba8b8e795853ae01f785c43684f72ac2f4d56896..2bf6d129480e456e51a86c0677a9c74fdb7acdc7 100644 (file)
@@ -1,3 +1,4 @@
 upgrade_1.0_to_2.0.sh
 upgrade_2.0_to_3.0.sh
 upgrade_3.0_to_3.1.sh
+upgrade_3.1_to_3.2.sh
index d8df955df21338349860fd8e7ce89c6e14015572..3c845dec77edfed4538fc09e119ba2a0bcefde90 100644 (file)
@@ -6,9 +6,11 @@ sqlscripts_DATA += dhcpdb_drop.pgsql
 sqlscripts_DATA += upgrade_1.0_to_2.0.sh
 sqlscripts_DATA += upgrade_2.0_to_3.0.sh
 sqlscripts_DATA += upgrade_3.0_to_3.1.sh
+sqlscripts_DATA += upgrade_3.1_to_3.2.sh
 
 DISTCLEANFILES = upgrade_1.0_to_2.0.sh
 DISTCLEANFILES += upgrade_2.0_to_3.0.sh
-DISTCLEANFILES += upgrade_3.0_to_3.1.s
+DISTCLEANFILES += upgrade_3.0_to_3.1.sh
+DISTCLEANFILES += upgrade_3.1_to_3.2.sh
 
 EXTRA_DIST = ${sqlscripts_DATA}
index 29bdcf608fcee14b12e6f2dde276d198a1bfe872..3597b7334051f1859709ebe8ab3344853df38054 100644 (file)
@@ -493,6 +493,41 @@ INSERT INTO host_identifier_type VALUES (4, 'flex-id');
 UPDATE schema_version
     SET version = '3', minor = '1';
 
+-- Set 3.2 schema version.
+
+-- Remove constraints which perform too restrictive checks on the inserted
+-- host reservations. We want to be able to insert host reservations which
+-- include no specific IPv4 address or those that have repeating subnet
+-- identifiers, e.g. IPv4 reservations would typically include 0 (or null)
+-- IPv6 subnet identifiers.
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_ipv4_address_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_identifier_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp6_identifier_subnet_id;
+
+-- Create partial indexes instead of the constraints that we have removed.
+
+-- IPv4 address/IPv4 subnet identifier pair is unique if subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id ON hosts
+       (ipv4_address ASC, dhcp4_subnet_id ASC)
+    WHERE ipv4_address IS NOT NULL AND ipv4_address <> 0;
+
+-- Client identifier is unique within an IPv4 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts
+        (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+    WHERE (dhcp4_subnet_id IS NOT NULL AND dhcp4_subnet_id <> 0);
+
+-- Client identifier is unique within an IPv6 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts
+        (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+    WHERE (dhcp6_subnet_id IS NOT NULL AND dhcp6_subnet_id <> 0);
+
+-- Set 3.2 schema version.
+UPDATE schema_version
+    SET version = '3', minor = '2';
+
 
 -- Commit the script transaction.
 COMMIT;
diff --git a/src/share/database/scripts/pgsql/upgrade_3.1_to_3.2.sh.in b/src/share/database/scripts/pgsql/upgrade_3.1_to_3.2.sh.in
new file mode 100644 (file)
index 0000000..3e4c44c
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+# 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
+
+VERSION=`pgsql_version "$@"`
+
+if [ "$VERSION" != "3.0" ]; then
+    printf "This script upgrades 3.0 to 3.1. Reported version is $VERSION. Skipping upgrade.\n"
+    exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Remove constraints which perform too restrictive checks on the inserted
+-- host reservations. We want to be able to insert host reservations which
+-- include no specific IPv4 address or those that have repeating subnet
+-- identifiers, e.g. IPv4 reservations would typically include 0 (or null)
+-- IPv6 subnet identifiers.
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_ipv4_address_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_identifier_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp6_identifier_subnet_id;
+
+-- Create partial indexes instead of the constraints that we have removed.
+
+-- IPv4 address/IPv4 subnet identifier pair is unique if subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp4_ipv4_address_subnet_id ON hosts
+       (ipv4_address ASC, dhcp4_subnet_id ASC)
+    WHERE ipv4_address IS NOT NULL AND ipv4_address <> 0;
+
+-- Client identifier is unique within an IPv4 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp4_identifier_subnet_id ON hosts
+        (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+    WHERE (dhcp4_subnet_id IS NOT NULL AND dhcp4_subnet_id <> 0);
+
+-- Client identifier is unique within an IPv6 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp6_identifier_subnet_id ON hosts
+        (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+    WHERE (dhcp6_subnet_id IS NOT NULL AND dhcp6_subnet_id <> 0);
+
+-- Set 3.2 schema version.
+UPDATE schema_version
+    SET version = '3', minor = '2';
+
+-- Schema 3.2 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
+
+exit $RESULT
+