src/share/database/scripts/mysql/upgrade_9.0_to_9.1.sh
src/share/database/scripts/mysql/upgrade_9.1_to_9.2.sh
src/share/database/scripts/mysql/upgrade_9.2_to_9.3.sh
+ src/share/database/scripts/mysql/upgrade_9.3_to_9.4.sh
src/share/database/scripts/mysql/wipe_data.sh
src/share/database/scripts/pgsql/Makefile
src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh
src/share/database/scripts/pgsql/upgrade_5.0_to_5.1.sh
src/share/database/scripts/pgsql/upgrade_5.1_to_6.0.sh
src/share/database/scripts/pgsql/upgrade_6.0_to_6.1.sh
+ src/share/database/scripts/pgsql/upgrade_6.1_to_6.2.sh
src/share/database/scripts/pgsql/wipe_data.sh
src/share/yang/Makefile
src/share/yang/modules/Makefile
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.3
+ # Ok, we have a 1.0 database. Let's upgrade it to 9.4
${keaadmin} db-upgrade mysql -u $db_user -p $db_password -n $db_name -d $db_scripts_dir
ERRCODE=$?
qry="select ddns_send_updates, ddns_override_no_update, ddns_override_client_update, ddns_replace_client_name, ddns_generated_prefix, ddns_qualifying_suffix from dhcp6_subnet"
run_statement "dhcp6_subnet" "$qry"
- # Verify upgraded schema reports version 9.3
+ # Schema upgrade from 9.3 to 9.4.
+
+ # Non unique indexes on hosts allowing multiple reservation for the same IP.
+
+ insert_sql="\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (hex('010101010101'), 0, 1, inet_aton('192.0.2.0'));\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (hex('010101010102'), 0, 1, inet_aton('192.0.2.0'));"
+ mysql_execute "$insert_sql"
+ ERRCODE=$?
+ assert_eq 0 $ERRCODE "insert into hosts failed, expected exit code %d, actual %d"
+
+ # Verify upgraded schema reports version 9.4
version=$(${keaadmin} db-version mysql -u $db_user -p $db_password -n $db_name -d $db_scripts_dir)
- assert_str_eq "9.3" ${version} "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "9.4" ${version} "Expected kea-admin to return %s, returned value was %s"
# Let's wipe the whole database
mysql_wipe
# Verify that kea-admin db-version returns the correct version
version=$(${keaadmin} db-version pgsql -u $db_user -p $db_password -n $db_name)
- assert_str_eq "6.1" ${version} "Expected kea-admin to return %s, returned value was %s"
+ assert_str_eq "6.2" ${version} "Expected kea-admin to return %s, returned value was %s"
# Let's wipe the whole database
pgsql_wipe
}
pgsql_upgrade_3_0_to_6_1() {
- # Verify upgraded schema reports version 6.1.
- version=$(${keaadmin} db-version pgsql -u $db_user -p $db_password -n $db_name -d $db_scripts_dir)
- assert_str_eq "6.1" ${version} "Expected kea-admin to return %s, returned value was %s"
-
# Added user_context to lease4
output=`pgsql_execute "select user_context from lease4;"`
ERRCODE=$?
assert_eq 0 $ERRCODE "logs table is missing or broken. (expected status code %d, returned %d)"
}
+pgsql_upgrade_6_1_to_6_2() {
+ # Verify upgraded schema reports version 6.2.
+ version=$(${keaadmin} db-version pgsql -u $db_user -p $db_password -n $db_name -d $db_scripts_dir)
+ assert_str_eq "6.2" ${version} "Expected kea-admin to return %s, returned value was %s"
+
+ insert_sql="\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (decode('010101010101', 'hex'), 0, 1, x'FFAF0002'::int);\
+insert into hosts(dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id, ipv4_address) values (decode('010101010102', 'hex'), 0, 1, x'FFAF0002'::int);"
+ pgsql_execute "$insert_sql"
+ ERRCODE=$?
+ assert_eq 0 $ERRCODE "insert into hosts failed, expected exit code %d, actual %d"
+}
+
pgsql_upgrade_test() {
test_start "pgsql.upgrade-test"
# Check 3.0 to 6.1 upgrade
pgsql_upgrade_3_0_to_6_1
+ # Check 6.1 to 6.2 upgrade
+ pgsql_upgrade_6_1_to_6_2
+
# Let's wipe the whole database
pgsql_wipe
/// Rolls back all pending database operations. On databases that don't
/// support transactions, this is a no-op.
virtual void rollback() {};
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// All backends are required to support the case when unique setting is
+ /// @c true and they must use this setting by default.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationUnique(const bool unique) = 0;
};
/// @brief HostDataSource pointer
}
// Check if the address is already reserved for the specified IPv4 subnet.
- if (!host->getIPv4Reservation().isV4Zero() &&
+ if (ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero() &&
(host->getIPv4SubnetID() != SUBNET_ID_UNUSED) &&
get4(host->getIPv4SubnetID(), host->getIPv4Reservation())) {
isc_throw(ReservedAddress, "failed to add new host using the HW"
for (IPv6ResrvIterator it = reservations.first; it != reservations.second;
++it) {
- // If there's an entry for this (subnet-id, address), reject it.
- if (get6(host->getIPv6SubnetID(), it->second.getPrefix())) {
- isc_throw(DuplicateHost, "failed to add address reservation for "
- << "host using the HW address '"
- << (hwaddr ? hwaddr->toText(false) : "(null)")
- << " and DUID '" << (duid ? duid->toText() : "(null)")
- << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
- << "' for address/prefix " << it->second.getPrefix()
- << ": There's already reservation for this address/prefix");
+ if (ip_reservations_unique_) {
+ // If there's an entry for this (subnet-id, address), reject it.
+ if (get6(host->getIPv6SubnetID(), it->second.getPrefix())) {
+ isc_throw(DuplicateHost, "failed to add address reservation for "
+ << "host using the HW address '"
+ << (hwaddr ? hwaddr->toText(false) : "(null)")
+ << " and DUID '" << (duid ? duid->toText() : "(null)")
+ << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+ << "' for address/prefix " << it->second.getPrefix()
+ << ": There's already reservation for this address/prefix");
+ }
}
hosts6_.insert(HostResrv6Tuple(it->second, host));
}
return (false);
}
+bool
+CfgHosts::setIPReservationUnique(const bool unique) {
+ ip_reservations_unique_ = unique;
+ return (true);
+}
+
+
ElementPtr
CfgHosts::toElement() const {
uint16_t family = CfgMgr::instance().getFamily();
return (std::string("configuration file"));
}
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return always true because this data source supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationUnique(const bool unique);
+
/// @brief Unparse a configuration object
///
/// host reservation lists are not autonomous so they are
/// - IPv6 prefix
HostContainer6 hosts6_;
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_ = true;
+
/// @brief Unparse a configuration object (DHCPv4 reservations)
///
/// @return a pointer to unparsed configuration
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_CQL_ROLLBACK);
}
+bool
+CqlHostDataSource::setIPReservationUnique(const bool unique) {
+ // This backend does not support the mode in which multiple reservations
+ // for the same IP address are created. If selecting this mode is
+ // attempted this function returns false to indicate that this is
+ // not allowed.
+ return (unique ? true : false);
+}
+
} // namespace dhcp
} // namespace isc
/// Rolls back all pending database operations (no-op for Cassandra)
virtual void rollback() override;
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address does not exist. In some cases it may be required
+ /// to allow non-unique IP reservations, e.g. in the case when a host has
+ /// several interfaces and independently of which interface is used by this
+ /// host to communicate with the DHCP server the same IP address should be
+ /// assigned. In this case the @c unique value should be set to false to
+ /// disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique within the subnet or can be non-unique.
+ /// @return true when addresses must be unique, false otherwise because
+ /// this backend does not support specifying the same IP address in multiple
+ /// host reservations.
+ virtual bool setIPReservationUnique(const bool unique) override;
+
private:
/// @brief Pointer to the implementation of the @ref CqlHostDataSource.
CqlHostDataSourceImpl* impl_;
}
}
+bool
+HostMgr::setIPReservationUnique(const bool unique) {
+ // Iterate over the alternate sources first, because they may include those
+ // for which the new setting is not supported.
+ for (auto source : alternate_sources_) {
+ if (!source->setIPReservationUnique(unique)) {
+ // One of the sources does not support this new mode of operation.
+ // Let's log a warning and back off the changes to the default
+ // setting which should always be supported.
+ LOG_WARN(hosts_logger, HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED)
+ .arg(source->getType());
+ for (auto source : alternate_sources_) {
+ source->setIPReservationUnique(true);
+ }
+ return (false);
+ }
+ }
+ return (true);
+}
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
disable_single_query_ = disable_single_query;
}
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// Calling this function on @c HostMgr causes the manager to attempt to
+ /// set this flag on all backends in use.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationUnique(const bool unique);
+
protected:
/// @brief The negative caching flag.
///
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER";
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST";
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL";
+extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED = "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED";
} // namespace dhcp
} // namespace isc
"HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER", "get one host with IPv6 reservation for subnet id %1, identified by %2",
"HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST", "using subnet id %1 and identifier %2, found in %3 host: %4",
"HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL", "host not found using subnet id %1 and identifier %2",
+ "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED", "host data source %1 does not support the mode in which IP reservations are non-unique",
NULL
};
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER;
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST;
extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL;
+extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED;
} // namespace dhcp
} // namespace isc
% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
This debug message is issued when no host was found using the specified
subnet id and host identifier.
+
+% HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED host data source %1 does not support the mode in which IP reservations are non-unique
+This warning message is issued when an administrator attempted to configure the
+server to allow multiple host reservations for the same IP address or prefix.
+Some host database backends may not support this mode of operation. In this
+case the administrator should stop using these backends or fall back to the
+default setting which requires that IP addresses are unique within a subnet.
+This setting is guaranteed to work for MySQL and Postgres host backends.
\ No newline at end of file
/// @param host Host object to be added to the database.
/// None of the fields in the host reservation are modified -
/// the host data is only read.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
///
/// @return Vector of MySQL BIND objects representing the data to be added.
- std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
+ std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host, const bool unique_ip) {
// Store host object to ensure it remains valid.
host_ = host;
}
// Add the data to the vector.
- return (std::vector<MYSQL_BIND>(bind_.begin(), bind_.begin() + columns_num_));
+ std::vector<MYSQL_BIND> vec(bind_.begin(), bind_.begin() + HOST_COLUMNS);
+
+ // When checking whether the IP is unique we need to bind the IPv4 address
+ // at the end of the query as it has additional binding for the IPv4
+ // address.
+ if (unique_ip) {
+ vec.push_back(bind_[5]);
+ vec.push_back(bind_[3]);
+ }
+ return (vec);
};
/// @brief Create BIND array to receive Host data.
/// None of the fields in the reservation are modified -
/// the reservation data is only read.
/// @param id ID of a host owning this reservation
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
///
/// @return Vector of MySQL BIND objects representing the data to be added.
std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
- const HostID& id) {
+ const HostID& id,
+ const bool unique_ip) {
// Store the values to ensure they remain valid.
resv_ = resv;
// Add the data to the vector. Note the end element is one after the
// end of the array.
// RESRV_COLUMNS -1 as we do not set reservation_id.
- return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
+ std::vector<MYSQL_BIND> vec(&bind_[0], &bind_[RESRV_COLUMNS-1]);
+
+ // When checking whether the IP is unique we need to bind the IPv6 address
+ // and prefix length at the end of the query as it has additional binding
+ // for the IPv6 address and prefix length.
+ if (unique_ip) {
+ vec.push_back(bind_[0]);
+ vec.push_back(bind_[1]);
+ }
+
+ return (vec);
}
private:
/// @note: please add new statements doing read only operations before
/// the WRITE_STMTS_BEGIN position.
enum StatementIndex {
- GET_HOST_DHCPID, // Gets hosts by host identifier
- GET_HOST_ADDR, // Gets hosts by IPv4 address
- GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
- GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
- GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
- GET_HOST_PREFIX, // Gets host by IPv6 prefix
- GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
- GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
- GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
- GET_HOST_HOSTNAME, // Gets hosts by hostname
- GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
- GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
- GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
- GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
- GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
- GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
- INSERT_HOST, // Insert new host to collection
- INSERT_V6_RESRV, // Insert v6 reservation
- INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
- INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
- DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
- DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
- DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
- NUM_STATEMENTS // Number of statements
+ GET_HOST_DHCPID, // Gets hosts by host identifier
+ GET_HOST_ADDR, // Gets hosts by IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+ GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+ GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Gets host by IPv6 prefix
+ GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
+ GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
+ GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
+ GET_HOST_HOSTNAME, // Gets hosts by hostname
+ GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
+ GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
+ GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
+ GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
+ GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
+ GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
+ INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
+ INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
+ INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
+ INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
+ INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
+ INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
+ NUM_STATEMENTS // Number of statements
};
/// @brief Index of first statement performing write to the database.
/// This value is used to mark border line between queries and other
/// statements and statements performing write operation on the database,
/// such as INSERT, DELETE, UPDATE.
- static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_UNIQUE_IP;
/// @brief Constructor.
///
/// @brief The parameters
DatabaseConnection::ParameterMap parameters_;
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_;
+
/// @brief The pool of contexts
MySqlHostContextPoolPtr pool_;
};
"ON h.host_id = r.host_id "
"ORDER BY h.host_id, o.option_id, r.reservation_id"},
- // Inserts a host into the 'hosts' table.
- {MySqlHostDataSourceImpl::INSERT_HOST,
+ // Inserts a host into the 'hosts' table without checking that there is
+ // a reservation for the IP address.
+ {MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
"INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
"dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
"dhcp4_client_classes, dhcp6_client_classes, "
"dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
- // Inserts a single IPv6 reservation into 'reservations' table.
- {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
+ // Inserts a host into the 'hosts' table with checking that reserved IP
+ // address is unique. The innermost query checks if there is at least
+ // one host for the given IP/subnet combination. If it not exists the
+ // new host is inserted. DUAL is a special MySQL table from which we
+ // can select the values to be inserted. If the host with the given
+ // IP address already exists the new host won't be inserted. The caller
+ // can check the number of affected rows to detect that there was
+ // a duplicate host in the database.
+ {MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes, "
+ "user_context, dhcp4_next_server, "
+ "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
+ "SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM DUAL "
+ "WHERE NOT EXISTS ("
+ "SELECT ipv4_address FROM hosts "
+ "WHERE ipv4_address = ? AND dhcp4_subnet_id = ? "
+ "LIMIT 1"
+ ")"},
+
+ // Inserts a single IPv6 reservation into 'reservations' table without
+ // checking that the inserted reservation is unique.
+ {MySqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE,
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
"dhcp6_iaid, host_id) "
"VALUES (?, ?, ?, ?, ?)"},
+ // Inserts a single IPv6 reservation into 'reservations' table with
+ // checking that the inserted reservation is unique.
+ {MySqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE,
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ "dhcp6_iaid, host_id) "
+ "SELECT ?, ?, ?, ?, ? FROM DUAL "
+ "WHERE NOT EXISTS ("
+ "SELECT 1 FROM ipv6_reservations "
+ "WHERE address = ? AND prefix_len = ? "
+ "LIMIT 1"
+ ")"},
+
// Inserts a single DHCPv4 option into 'dhcp4_options' table.
// Using fixed scope_id = 3, which associates an option with host.
{MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
}
MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
- : parameters_(parameters) {
+ : parameters_(parameters), ip_reservations_unique_(true) {
// Validate the schema version first.
std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
}
checkError(ctx, status, stindex, "unable to execute");
}
+
+ // If the number of rows inserted is 0 it means that the query detected
+ // an attempt to insert duplicated data for which there is no unique
+ // index in the database. Unique indexes are not created in the database
+ // when it may be sometimes allowed to insert duplicated records per
+ // server's configuration.
+ my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.statements_[stindex]);
+ if (numrows == 0) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
}
bool
MySqlHostDataSourceImpl::addResv(MySqlHostContextPtr& ctx,
const IPv6Resrv& resv,
const HostID& id) {
- std::vector<MYSQL_BIND> bind = ctx->host_ipv6_reservation_exchange_->createBindForSend(resv, id);
+ std::vector<MYSQL_BIND> bind = ctx->host_ipv6_reservation_exchange_->
+ createBindForSend(resv, id, ip_reservations_unique_);
- addStatement(ctx, INSERT_V6_RESRV, bind);
+ addStatement(ctx, ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE, bind);
}
void
// the MySqlTransaction class.
MySqlTransaction transaction(ctx->conn_);
+ // If we're configured to check that an IP reservation within a given subnet
+ // is unique, the IP reservation exists and the subnet is actually set
+ // we will be using a special query that checks for uniqueness. Otherwise,
+ // we will use a regular insert statement.
+ bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
+ && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
+
// Create the MYSQL_BIND array for the host
- std::vector<MYSQL_BIND> bind = ctx->host_ipv4_exchange_->createBindForSend(host);
+ std::vector<MYSQL_BIND> bind = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
// ... and insert the host.
- impl_->addStatement(ctx, MySqlHostDataSourceImpl::INSERT_HOST, bind);
+ impl_->addStatement(ctx, unique_ip ? MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
+ MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP, bind);
// Gets the last inserted hosts id
uint64_t host_id = mysql_insert_id(ctx->conn_.mysql_);
ctx->conn_.rollback();
}
+bool
+MySqlHostDataSource::setIPReservationUnique(const bool unique) {
+ impl_->ip_reservations_unique_ = unique;
+ return (true);
+}
+
+
} // namespace dhcp
} // namespace isc
/// Rolls back all pending database operations.
virtual void rollback();
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique within the subnet or can be non-unique.
+ /// @return always true because this backend supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationUnique(const bool unique);
+
/// @brief Context RAII Allocator.
class MySqlHostContextAlloc {
public:
/// @param host Host object to be added to the database.
/// None of the fields in the host reservation are modified -
/// the host data is only read.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
///
/// @return pointer to newly constructed bind_array containing the
/// bound values extracted from host
///
/// @throw DbOperationError if bind_array cannot be populated.
- PsqlBindArrayPtr createBindForSend(const HostPtr& host) {
+ PsqlBindArrayPtr createBindForSend(const HostPtr& host, const bool unique_ip) {
if (!host) {
isc_throw(BadValue, "createBindForSend:: host object is NULL");
}
bind_array->add(key);
}
+ // When checking whether the IP is unique we need to bind the IPv4 address
+ // at the end of the query as it has additional binding for the IPv4
+ // address.
+ if (unique_ip) {
+ bind_array->add(host->getIPv4Reservation());
+ bind_array->add(host->getIPv4SubnetID());
+ }
+
+
} catch (const std::exception& ex) {
host_.reset();
isc_throw(DbOperationError,
/// @param resv The IPv6 reservation to be added to the database.
/// None of the fields in the reservation are modified -
/// @param host_id ID of the host to which this reservation belongs.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
///
/// @return pointer to newly constructed bind_array containing the
/// bound values extracted the IPv6 reservation
///
/// @throw DbOperationError if bind_array cannot be populated.
PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
- const HostID& host_id) {
+ const HostID& host_id,
+ const bool unique_ip) {
// Store the values to ensure they remain valid.
// Technically we don't need this, as currently all the values
// are converted to strings and stored by the bind array.
// host_id: BIGINT NOT NULL
bind_array->add(host_id);
+
+ // When checking whether the IP is unique we need to bind the IPv6 address
+ // and prefix length at the end of the query as it has additional binding
+ // for the IPv6 address and prefix length.
+ if (unique_ip) {
+ bind_array->add(resv.getPrefix());
+ bind_array->add(resv.getPrefixLen());
+ }
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array from IPv6 Reservation: "
/// @note: please add new statements doing read only operations before
/// the WRITE_STMTS_BEGIN position.
enum StatementIndex {
- GET_HOST_DHCPID, // Gets hosts by host identifier
- GET_HOST_ADDR, // Gets hosts by IPv4 address
- GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
- GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
- GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
- GET_HOST_PREFIX, // Gets host by IPv6 prefix
- GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
- GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
- GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
- GET_HOST_HOSTNAME, // Gets hosts by hostname
- GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
- GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
- GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
- GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
- GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
- GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
- INSERT_HOST, // Insert new host to collection
- INSERT_V6_RESRV, // Insert v6 reservation
- INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
- INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
- DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
- DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
- DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
- NUM_STATEMENTS // Number of statements
+ GET_HOST_DHCPID, // Gets hosts by host identifier
+ GET_HOST_ADDR, // Gets hosts by IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+ GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+ GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Gets host by IPv6 prefix
+ GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
+ GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
+ GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
+ GET_HOST_HOSTNAME, // Gets hosts by hostname
+ GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
+ GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
+ GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
+ GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
+ GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
+ GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
+ INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
+ INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
+ INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
+ INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
+ INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
+ INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
+ NUM_STATEMENTS // Number of statements
};
/// @brief Index of first statement performing write to the database.
/// This value is used to mark border line between queries and other
/// statements and statements performing write operation on the database,
/// such as INSERT, DELETE, UPDATE.
- static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_UNIQUE_IP;
/// @brief Constructor.
///
/// @brief The parameters
PgSqlConnection::ParameterMap parameters_;
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_;
+
/// @brief The pool of contexts
PgSqlHostContextPoolPtr pool_;
};
"ORDER BY h.host_id, o.option_id, r.reservation_id"
},
- // PgSqlHostDataSourceImpl::INSERT_HOST
- // Inserts a host into the 'hosts' table. Returns the inserted host id.
+ // Inserts a host into the 'hosts' table without checking that there is
+ // a reservation for the IP address.
{13,
{ OID_BYTEA, OID_INT2,
OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
OID_VARCHAR, OID_VARCHAR, OID_TEXT,
- OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR },
- "insert_host",
+ OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR},
+ "insert_host_non_unique_ip",
"INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
" dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
" dhcp4_client_classes, dhcp6_client_classes, user_context, "
- " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
- "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) "
+ " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
+ "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
"RETURNING host_id"
},
- // PgSqlHostDataSourceImpl::INSERT_V6_RESRV
- // Inserts a single IPv6 reservation into 'reservations' table.
+ // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
+ // Inserts a host into the 'hosts' table with checking that reserved IP
+ // address is unique. The innermost query checks if there is at least
+ // one host for the given IP/subnet combination. If it not exists the
+ // new host is inserted. If the host with the given IP address already
+ // exists the new host won't be inserted. The caller can check the
+ // number of affected rows to detect that there was a duplicate host
+ // in the database. Returns the inserted host id.
+ {15,
+ { OID_BYTEA, OID_INT2,
+ OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
+ OID_VARCHAR, OID_VARCHAR, OID_TEXT,
+ OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR, OID_INT8,
+ OID_INT8},
+ "insert_host_unique_ip",
+ "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
+ " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ " dhcp4_client_classes, dhcp6_client_classes, user_context, "
+ " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
+ " SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
+ " WHERE NOT EXISTS ("
+ " SELECT 1 FROM HOSTS WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
+ " ) "
+ "RETURNING host_id"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
+ // Inserts a single IPv6 reservation into 'reservations' table without
+ // checking that the inserted reservation is unique.
{5,
{ OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
- "insert_v6_resrv",
+ "insert_v6_resrv_non_unique",
"INSERT INTO ipv6_reservations(address, prefix_len, type, "
" dhcp6_iaid, host_id) "
"VALUES ($1, $2, $3, $4, $5)"
},
+ // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
+ // Inserts a single IPv6 reservation into 'reservations' table with
+ // checking that the inserted reservation is unique.
+ {7,
+ { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4, OID_VARCHAR, OID_INT2 },
+ "insert_v6_resrv_unique",
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ " dhcp6_iaid, host_id) "
+ "SELECT $1, $2, $3, $4, $5 "
+ " WHERE NOT EXISTS ("
+ " SELECT 1 FROM ipv6_reservations"
+ " WHERE address = $6 AND prefix_len = $7"
+ " LIMIT 1"
+ " )"
+ },
+
// PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
// Inserts a single DHCPv4 option into 'dhcp4_options' table.
// Using fixed scope_id = 3, which associates an option with host.
}
PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
- : parameters_(parameters) {
+ : parameters_(parameters), ip_reservations_unique_(true) {
// Validate the schema version first.
std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
}
+ // Get the number of affected rows.
+ char* rows_affected = PQcmdTuples(r);
+ if (!rows_affected) {
+ isc_throw(DbOperationError,
+ "Could not retrieve the number of affected rows.");
+ }
+
+ // If the number of rows inserted is 0 it means that the query detected
+ // an attempt to insert duplicated data for which there is no unique
+ // index in the database. Unique indexes are not created in the database
+ // when it may be sometimes allowed to insert duplicated records per
+ // server's configuration.
+ if (rows_affected[0] == '0') {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+
if (return_last_id) {
PgSqlExchange::getColumnValue(r, 0, 0, last_id);
}
PgSqlHostDataSourceImpl::addResv(PgSqlHostContextPtr& ctx,
const IPv6Resrv& resv,
const HostID& id) {
- PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->createBindForSend(resv, id);
+ PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
+ createBindForSend(resv, id, ip_reservations_unique_);
- addStatement(ctx, INSERT_V6_RESRV, bind_array);
+ addStatement(ctx, ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE,
+ bind_array);
}
void
// the PgSqlTransaction class.
PgSqlTransaction transaction(ctx->conn_);
+ // If we're configured to check that an IP reservation within a given subnet
+ // is unique, the IP reservation exists and the subnet is actually set
+ // we will be using a special query that checks for uniqueness. Otherwise,
+ // we will use a regular insert statement.
+ bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
+ && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
+
// Create the PgSQL Bind array for the host
- PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host);
+ PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
// ... and insert the host.
- uint32_t host_id = impl_->addStatement(ctx, PgSqlHostDataSourceImpl::INSERT_HOST,
+ uint32_t host_id = impl_->addStatement(ctx, unique_ip ? PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
+ PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
bind_array, true);
// Insert DHCPv4 options.
ctx->conn_.rollback();
}
+bool
+PgSqlHostDataSource::setIPReservationUnique(const bool unique) {
+ impl_->ip_reservations_unique_ = unique;
+ return (true);
+}
+
} // namespace dhcp
} // namespace isc
/// Rolls back all pending database operations.
virtual void rollback();
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique within the subnet or can be non-unique.
+ /// @return always true because this backend supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationUnique(const bool unique);
+
/// @brief Context RAII Allocator.
class PgSqlHostContextAlloc {
public:
EXPECT_THROW(cfg.add(host2), isc::dhcp::ReservedAddress);
}
+// Test that it is possible to allow inserting multiple reservations for
+// the same IP address.
+TEST_F(CfgHostsTest, allow4AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationUnique(false));
+
+ // First host has a reservation for address 192.0.2.1
+ HostPtr host1 = HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(SUBNET_ID_UNUSED),
+ IOAddress("192.0.2.1")));
+ ASSERT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(hwaddrs_[1]->toText(false),
+ "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.1")));
+ // Adding this should work because the HW address is different.
+ EXPECT_NO_THROW(cfg.add(host2));
+}
+
// Checks that it's not possible for two hosts to have the same address
// reserved at the same time.
TEST_F(CfgHostsTest, add6Invalid2Hosts) {
EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
}
+// Test that it is possible to allow inserting multiple reservations for
+// the same IPv6 address.
+TEST_F(CfgHostsTest, allow6AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationUnique(false));
+
+ // First host has a reservation for address 2001:db8::1
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+
+ // Adding this should work because the DUID is different.
+ EXPECT_NO_THROW(cfg.add(host2));
+}
+
// Check that no error is reported when adding a host with subnet
// ids equal to global.
TEST_F(CfgHostsTest, globalSubnetIDs) {
testAddDuplicate4();
}
+/// @brief Test that the CQL backend does not support using non-unique
+/// IP addresses between multiple reservations.
+TEST_F(CqlHostDataSourceTest, disallowDuplicateIP) {
+ testDisallowDuplicateIP();
+}
+
// This test verifies that DHCPv4 options can be inserted in a binary format
/// and retrieved from the CQL host database.
TEST_F(CqlHostDataSourceTest, optionsReservations4) {
return ("one");
}
+ bool setIPReservationUnique(const bool) {
+ return (true);
+ }
+
/// Specific methods
/// @brief Set the entry
testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
}
+// This test verifies that it is possible to control whether the reserved
+// IP addresses are unique or non unique via the HostMgr.
+TEST_F(MySQLHostMgrTest, setIPReservationUnique) {
+ EXPECT_TRUE(HostMgr::instance().setIPReservationUnique(true));
+ EXPECT_TRUE(HostMgr::instance().setIPReservationUnique(false));
+}
+
// Verifies that loss of connectivity to MySQL is handled correctly.
TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostCallback) {
testDbLostCallback();
}
+
#endif
testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
}
+// This test verifies that it is possible to control whether the reserved
+// IP addresses are unique or non unique via the HostMgr.
+TEST_F(PostgreSQLHostMgrTest, setIPReservationUnique) {
+ EXPECT_TRUE(HostMgr::instance().setIPReservationUnique(true));
+ EXPECT_TRUE(HostMgr::instance().setIPReservationUnique(false));
+}
+
// Verifies that loss of connectivity to PostgreSQL is handled correctly.
TEST_F(PostgreSQLHostMgrDbLostCallbackTest, testDbLostCallback) {
testDbLostCallback();
testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
}
+// This test verifies that it is possible to control whether the reserved
+// IP addresses are unique or non unique via the HostMgr.
+TEST_F(CQLHostMgrTest, setIPReservationUnique) {
+ EXPECT_TRUE(HostMgr::instance().setIPReservationUnique(true));
+ // This is currently not supported for Cassandra.
+ EXPECT_FALSE(HostMgr::instance().setIPReservationUnique(false));
+}
+
#endif
} // namespace
testAddDuplicate6WithSameHWAddr();
}
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv6) {
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv6) {
+ testAllowDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv6();
+}
+
/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
/// follows: try to add multiple instances of the same host reservation and
/// verify that the second and following attempts will throw exceptions.
testAddDuplicate4();
}
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv4) {
+ testAllowDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv4();
+}
+
/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
/// and retrieved from the MySQL host database.
TEST_F(MySqlHostDataSourceTest, optionsReservations4) {
testAddDuplicate6WithSameHWAddr();
}
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv6) {
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv6();
+}
+
/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
/// follows: try to add multiple instances of the same host reservation and
/// verify that the second and following attempts will throw exceptions.
testAddDuplicate4();
}
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv4) {
+ testAllowDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv4();
+}
+
/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
/// and retrieved from the PostgreSQL host database.
TEST_F(PgSqlHostDataSourceTest, optionsReservations4) {
ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
}
+void
+GenericHostDataSourceTest::testAddDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Create a host with a different identifier but the same IPv6 address. An attempt
+ // to create the reservation for the same IPv6 address should fail.
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+ EXPECT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // HWADDR is the same.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address"));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+}
+
void
GenericHostDataSourceTest::testAddDuplicate4() {
// Make sure we have the pointer to the host data source.
EXPECT_NO_THROW(hdsptr_->add(host));
}
+void
+GenericHostDataSourceTest::testDisallowDuplicateIP() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ // The backend does not support switching to the mode in which multiple
+ // reservations for the same address can be created.
+ EXPECT_FALSE(hdsptr_->setIPReservationUnique(false));
+
+ // The default mode still can be used.
+ EXPECT_TRUE(hdsptr_->setIPReservationUnique(true));
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv4() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // DUID is the same.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address"));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+}
+
void
GenericHostDataSourceTest::testAddr6AndPrefix() {
// Make sure we have the pointer to the host data source.
/// Uses gtest macros to report failures.
void testAddDuplicate6WithSameHWAddr();
- /// @brief Test if the duplicate IPv4 host with can't be inserted.
+ /// @brief Test that duplicate IPv6 reservation can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicateIPv6();
+
+ /// @brief Test if the reservation for the same IPv6 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv6();
+
+ /// @brief Test that duplicate IPv4 reservation can't be inserted.
///
/// Uses gtest macros to report failures.
void testAddDuplicate4();
+ /// @brief Test that the backend does not support a mode in which multiple
+ /// host reservations for the same IP address can be created.
+ void testDisallowDuplicateIP();
+
+ /// @brief Test if the reservation for the same IPv4 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv4();
+
/// @brief Test that DHCPv4 options can be inserted and retrieved from
/// the database.
///
/// @return number of hosts in the store.
virtual size_t size() const;
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address does not exist. In some cases it may be required
+ /// to allow non-unique IP reservations, e.g. in the case when a host has
+ /// several interfaces and independently of which interface is used by this
+ /// host to communicate with the DHCP server the same IP address should be
+ /// assigned. In this case the @c unique value should be set to false to
+ /// disable the checks for uniqueness on the backend side.
+ ///
+ /// All backends are required to support the case when unique setting is
+ /// @c true and they must use this setting by default.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationUnique(const bool) {
+ return (true);
+ }
+
protected:
// This is very simple storage.
/// @name Current database schema version values.
//@{
const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 9;
-const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 3;
+const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 4;
//@}
namespace isc {
namespace db {
-/// @brief Define PostgreSQL backend version: 6.1
+/// @brief Define PostgreSQL backend version: 6.2
const uint32_t PG_SCHEMA_VERSION_MAJOR = 6;
-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
/upgrade_9.0_to_9.1.sh
/upgrade_9.1_to_9.2.sh
/upgrade_9.2_to_9.3.sh
+/upgrade_9.3_to_9.4.sh
/wipe_data.sh
# This line concludes database upgrade to version 9.3.
+# Starting from this version we allow specifying multiple IP reservations
+# for the same address in certain DHCP configurations. The server may check
+# uniqueness of the IP addresses on its own. This is no longer checked at
+# the database level to faciliate the use cases when a single host may
+# get the same reserved IP address via different interfaces.
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv4 address are still efficient.
+DROP INDEX key_dhcp4_ipv4_address_subnet_id ON hosts;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id_identifier
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv6 address are still efficient.
+DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '4';
+
+# This line concludes database upgrade to version 9.4.
# Notes:
#
--- /dev/null
+#!/bin/sh
+
+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
+
+VERSION=`mysql_version "$@"`
+
+if [ "$VERSION" != "9.3" ]; then
+ printf "This script upgrades 9.3 to 9.4. Reported version is $VERSION. Skipping upgrade.\n"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Starting from this version we allow specifying multiple IP reservations
+# for the same address in certain DHCP configurations. The server may check
+# uniqueness of the IP addresses on its own. This is no longer checked at
+# the database level to faciliate the use cases when a single host may
+# get the same reserved IP address via different interfaces.
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv4 address are still efficient.
+DROP INDEX key_dhcp4_ipv4_address_subnet_id ON hosts;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id_identifier
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv6 address are still efficient.
+DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '4';
+
+# This line concludes database upgrade to version 9.4.
+
+EOF
+
+RESULT=$?
+
+exit $?
/upgrade_5.0_to_5.1.sh
/upgrade_5.1_to_6.0.sh
/upgrade_6.0_to_6.1.sh
+/upgrade_6.1_to_6.2.sh
/wipe_data.sh
-- Commit the script transaction
COMMIT;
+-- Starting from this version we allow specifying multiple IP reservations
+-- for the same address in certain DHCP configurations. The server may check
+-- uniqueness of the IP addresses on its own. This is no longer checked at
+-- the database level to faciliate the use cases when a single host may
+-- get the same reserved IP address via different interfaces.
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv4 address are still efficient.
+DROP INDEX IF EXISTS key_dhcp4_ipv4_address_subnet_id;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv6 address are still efficient.
+ALTER TABLE ipv6_reservations DROP CONSTRAINT IF EXISTS key_dhcp6_address_prefix_len;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '6', minor = '2';
+
+-- Schema 6.2 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
-- Notes:
-- Indexes
--- /dev/null
+#!/bin/sh
+
+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
+
+VERSION=`pgsql_version "$@"`
+
+if [ "$VERSION" != "6.1" ]; then
+ printf "This script upgrades 6.1 to 6.2. Reported version is $VERSION. Skipping upgrade.\n"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+-- Starting from this version we allow specifying multiple IP reservations
+-- for the same address in certain DHCP configurations. The server may check
+-- uniqueness of the IP addresses on its own. This is no longer checked at
+-- the database level to faciliate the use cases when a single host may
+-- get the same reserved IP address via different interfaces.
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv4 address are still efficient.
+--
+-- Note we have introduced a bug a while ago which causes the index to have
+-- different names depending on whether the schema was created via the
+-- dhcpdb_create.pgsql script or via updates. Therefore, let's make sure
+-- that we drop the index regarless of its current name.
+DROP INDEX IF EXISTS key_dhcp4_ipv4_address_subnet_id;
+DROP INDEX IF EXISTS hosts_dhcp4_ipv4_address_subnet_id;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv6 address are still efficient.
+ALTER TABLE ipv6_reservations DROP CONSTRAINT IF EXISTS key_dhcp6_address_prefix_len;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '6', minor = '2';
+
+-- Schema 6.2 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
+
+exit $RESULT
+