From 8b561ef8543dceb6e4564c01becf98cee382004c Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Sat, 22 Apr 2017 21:56:33 +0200 Subject: [PATCH] [5208a] delete methods implemented in host data sources --- src/lib/dhcpsrv/base_host_data_source.h | 38 +++++ src/lib/dhcpsrv/cfg_hosts.cc | 25 +++ src/lib/dhcpsrv/cfg_hosts.h | 39 +++++ src/lib/dhcpsrv/host_mgr.cc | 34 ++++ src/lib/dhcpsrv/host_mgr.h | 37 ++++ src/lib/dhcpsrv/mysql_host_data_source.cc | 153 ++++++++++++++++- src/lib/dhcpsrv/mysql_host_data_source.h | 31 ++++ src/lib/dhcpsrv/pgsql_host_data_source.cc | 159 ++++++++++++++++-- src/lib/dhcpsrv/pgsql_host_data_source.h | 31 ++++ .../generic_host_data_source_unittest.cc | 97 +++++++++++ .../tests/generic_host_data_source_unittest.h | 16 +- .../tests/mysql_host_data_source_unittest.cc | 17 +- .../tests/pgsql_host_data_source_unittest.cc | 17 +- 13 files changed, 675 insertions(+), 19 deletions(-) diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h index 405ae6e133..922c17aa95 100644 --- a/src/lib/dhcpsrv/base_host_data_source.h +++ b/src/lib/dhcpsrv/base_host_data_source.h @@ -247,6 +247,44 @@ public: /// @param host Pointer to the new @c Host object being added. virtual void add(const HostPtr& host) = 0; + /// @brief Attempts to delete a host by (subnet-id, address) + /// + /// This method supports both v4 and v6. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) = 0; + + /// @brief Attempts to delete a host by (subnet-id4, identifier, identifier-type) + /// + /// This method supports both v4 hosts only. + /// + /// @param subnet_id IPv4 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) = 0; + + /// @brief Attempts to delete a host by (subnet-id6, identifier, identifier-type) + /// + /// This method supports both v6 hosts only. + /// + /// @param subnet_id IPv6 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) = 0; + + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc index c493c77edb..e1dc43e983 100644 --- a/src/lib/dhcpsrv/cfg_hosts.cc +++ b/src/lib/dhcpsrv/cfg_hosts.cc @@ -688,6 +688,31 @@ CfgHosts::add6(const HostPtr& host) { } } +bool +CfgHosts::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) { + /// @todo: Implement host removal + isc_throw(NotImplemented, "sorry, not implemented"); + return (false); +} + +bool +CfgHosts::del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + /// @todo: Implement host removal + isc_throw(NotImplemented, "sorry, not implemented"); + return (false); +} + +bool +CfgHosts::del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + /// @todo: Implement host removal + isc_throw(NotImplemented, "sorry, not implemented"); + return (false); +} + ElementPtr CfgHosts::toElement() const { uint16_t family = CfgMgr::instance().getFamily(); diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index 4575c42d0b..21e0784a7e 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -322,6 +322,45 @@ public: /// has already been added to the IPv4 or IPv6 subnet. virtual void add(const HostPtr& host); + /// @brief Attempts to delete a host by address. + /// + /// This method supports both v4 and v6. + /// @todo: Not implemented. + /// + /// @param subnet_id subnet identifier. + /// @param addr specified address. + virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr); + + /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type) + /// + /// This method supports v4 only. + /// @todo: Not implemented. + /// + /// @param subnet_id IPv4 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + + /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type) + /// + /// This method supports v6 only. + /// @todo: Not implemented. + /// + /// @param subnet_id IPv6 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc index 090a5433ac..d0376a85de 100644 --- a/src/lib/dhcpsrv/host_mgr.cc +++ b/src/lib/dhcpsrv/host_mgr.cc @@ -222,5 +222,39 @@ HostMgr::add(const HostPtr& host) { alternate_source_->add(host); } +bool +HostMgr::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) { + if (!alternate_source_) { + isc_throw(NoHostDataSourceManager, "unable to delete a host because there is " + "no alternate host data source present"); + } + + return (alternate_source_->del(subnet_id, addr)); +} + +bool +HostMgr::del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + if (!alternate_source_) { + isc_throw(NoHostDataSourceManager, "unable to delete a host because there is " + "no alternate host data source present"); + } + + return (alternate_source_->del4(subnet_id, identifier_type, + identifier_begin, identifier_len)); +} + +bool +HostMgr::del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + if (!alternate_source_) { + isc_throw(NoHostDataSourceManager, "unable to delete a host because there is " + "no alternate host data source present"); + } + + return (alternate_source_->del6(subnet_id, identifier_type, + identifier_begin, identifier_len)); +} + } // end of isc::dhcp namespace } // end of isc namespace diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index 7da6aba471..34e9e7e7c8 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -285,6 +285,43 @@ public: alternate_source_ = source; } + /// @brief Attempts to delete a host by address. + /// + /// This method supports both v4 and v6. + /// + /// @param subnet_id subnet identifier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr); + + /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type) + /// + /// This method supports v4 only. + /// + /// @param subnet_id IPv4 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool + del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + + /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type) + /// + /// This method supports v6 only. + /// + /// @param subnet_id IPv6 Subnet identifier. + /// @param identifier_type Identifier type. + /// @param identifier_begin Pointer to a beginning of a buffer containing + /// an identifier. + /// @param identifier_len Identifier length. + /// @return true if deletion was successful, false otherwise. + virtual bool + del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + private: /// @brief Private default constructor. diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 1a38e2af3d..f571c24d2c 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -1763,6 +1763,9 @@ public: INSERT_V6_RESRV, // Insert v6 reservation INSERT_V4_OPTION, // Insert DHCPv4 option INSERT_V6_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 }; @@ -1792,6 +1795,15 @@ public: void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex, std::vector& bind); + /// @brief Executes statements that delete records. + /// + /// @param stindex Index of a statement being executed. + /// @param bind Vector of MYSQL_BIND objects to be used when making the + /// query. + /// @return true if any records were deleted, false otherwise + bool + delStatement(StatementIndex stindex, MYSQL_BIND* bind); + /// @brief Inserts IPv6 Reservation into ipv6_reservation table. /// /// @param resv IPv6 Reservation to be added @@ -2101,7 +2113,20 @@ TaggedStatementArray tagged_statements = { { {MySqlHostDataSourceImpl::INSERT_V6_OPTION, "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, " "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) " - " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}} + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}, + + {MySqlHostDataSourceImpl::DEL_HOST_ADDR4, + "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"}, + + {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, + "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? " + "AND dhcp_identifier = ?"}, + + {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, + "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? " + "AND dhcp_identifier = ?"} + + } }; MySqlHostDataSourceImpl:: @@ -2186,6 +2211,25 @@ MySqlHostDataSourceImpl::addStatement(StatementIndex stindex, } } +bool +MySqlHostDataSourceImpl::delStatement(StatementIndex stindex, + MYSQL_BIND* bind) { + // Bind the parameters to the statement + int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]); + checkError(status, stindex, "unable to bind parameters"); + + // Execute the statement + status = mysql_stmt_execute(conn_.statements_[stindex]); + + if (status != 0) { + checkError(status, stindex, "unable to execute"); + } + + // Let's check how many hosts were deleted. + my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]); + return (numrows != 0); +} + void MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv, const HostID& id) { @@ -2412,6 +2456,113 @@ MySqlHostDataSource::add(const HostPtr& host) { transaction.commit(); } +bool +MySqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + + if (addr.isV4()) { + // Set up the WHERE clause value + MYSQL_BIND inbind[2]; + + uint32_t subnet = subnet_id; + memset(inbind, 0, sizeof(inbind)); + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&subnet); + inbind[0].is_unsigned = MLM_TRUE; + + uint32_t addr4 = addr.toUint32(); + inbind[1].buffer_type = MYSQL_TYPE_LONG; + inbind[1].buffer = reinterpret_cast(&addr4); + inbind[1].is_unsigned = MLM_TRUE; + + ConstHostCollection collection; + return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind)); + } + + // v6 + ConstHostPtr host = get6(subnet_id, addr); + if (!host) { + return (false); + } + + // Ok, there is a host. Let's delete it. + return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0], + host->getIdentifier().size()); +} + +bool +MySqlHostDataSource::del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + + // Set up the WHERE clause value + MYSQL_BIND inbind[3]; + + // subnet-id + memset(inbind, 0, sizeof(inbind)); + uint32_t subnet = static_cast(subnet_id); + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&subnet); + inbind[0].is_unsigned = MLM_TRUE; + + // identifier type + char identifier_type_copy = static_cast(identifier_type); + inbind[1].buffer_type = MYSQL_TYPE_TINY; + inbind[1].buffer = reinterpret_cast(&identifier_type_copy); + inbind[1].is_unsigned = MLM_TRUE; + + // identifier value + std::vector identifier_vec(identifier_begin, + identifier_begin + identifier_len); + unsigned long length = identifier_vec.size(); + inbind[2].buffer_type = MYSQL_TYPE_BLOB; + inbind[2].buffer = &identifier_vec[0]; + inbind[2].buffer_length = length; + inbind[2].length = &length; + + ConstHostCollection collection; + return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind)); +} + +bool +MySqlHostDataSource::del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + + // Set up the WHERE clause value + MYSQL_BIND inbind[3]; + + // subnet-id + memset(inbind, 0, sizeof(inbind)); + uint32_t subnet = static_cast(subnet_id); + inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer = reinterpret_cast(&subnet); + inbind[0].is_unsigned = MLM_TRUE; + + // identifier type + char identifier_type_copy = static_cast(identifier_type); + inbind[1].buffer_type = MYSQL_TYPE_TINY; + inbind[1].buffer = reinterpret_cast(&identifier_type_copy); + inbind[1].is_unsigned = MLM_TRUE; + + // identifier value + std::vector identifier_vec(identifier_begin, + identifier_begin + identifier_len); + unsigned long length = identifier_vec.size(); + inbind[2].buffer_type = MYSQL_TYPE_BLOB; + inbind[2].buffer = &identifier_vec[0]; + inbind[2].buffer_length = length; + inbind[2].length = &length; + + ConstHostCollection collection; + return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind)); +} + ConstHostCollection MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const { diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h index 950ea25ecb..f053827d98 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.h +++ b/src/lib/dhcpsrv/mysql_host_data_source.h @@ -220,6 +220,37 @@ public: /// @param host Pointer to the new @c Host object being added. virtual void add(const HostPtr& host); + /// @brief Attempts to delete a host by (subnet-id, address) + /// + /// This method supports both v4 and v6. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr); + + /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier) + /// + /// This method supports v4 hosts only. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + + /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier) + /// + /// This method supports v6 hosts only. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + /// @brief Return backend type /// /// Returns the type of the backend (e.g. "mysql", "memfile" etc.) diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index 9403709859..ac41591d84 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -392,7 +392,7 @@ private: /// DHCPv6 options. /// /// The following are the basic functions of this class: - /// - bind class members to specific columns in MySQL binding tables, + /// - bind class members to specific columns in PgSQL binding tables, /// - set DHCP options specific column names, /// - create instances of options retrieved from the database. /// @@ -826,8 +826,6 @@ public: /// @brief Creates IPv6 reservation from the data contained in the /// currently processed row. /// - /// Called after the MYSQL_BIND array created by createBindForReceive(). - /// /// @return IPv6Resrv object (containing IPv6 address or prefix reservation) IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) { @@ -936,7 +934,7 @@ private: }; -/// @brief This class is used for storing IPv6 reservations in a MySQL database. +/// @brief This class is used for storing IPv6 reservations in a PgSQL database. /// /// This class is only used to insert IPv6 reservations into the /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To @@ -1180,6 +1178,9 @@ public: 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 }; @@ -1202,8 +1203,7 @@ public: /// @brief Executes statements which insert a row into one of the tables. /// /// @param stindex Index of a statement being executed. - /// @param bind Vector of MYSQL_BIND objects to be used when making the - /// query. + /// @param bind Vector of PgsqlBindArray objects to be used for the query /// @param return_last_id flag indicating whether or not the insert /// returns the primary key of from the row inserted via " RETURNING /// as pid" clause on the INSERT statement. The RETURNING @@ -1219,6 +1219,14 @@ public: PsqlBindArrayPtr& bind, const bool return_last_id = false); + /// @brief Executes statements that delete records. + /// + /// @param stindex Index of a statement being executed. + /// @param bind pointer to PsqlBindArray objects to be used for the query + /// @return true if any records were deleted, false otherwise + bool delStatement(PgSqlHostDataSourceImpl::StatementIndex stindex, + PsqlBindArrayPtr& bind); + /// @brief Inserts IPv6 Reservation into ipv6_reservation table. /// /// @param resv IPv6 Reservation to be added @@ -1244,7 +1252,8 @@ public: /// @param stindex Index of a statement being executed. /// @param options_cfg An object holding a collection of options to be /// inserted into the database. - /// @param host_id Host identifier retrieved using @c mysql_insert_id. + /// @param host_id Host identifier retrieved using getColumnValue + /// in addStatement method void addOptions(const StatementIndex& stindex, const ConstCfgOptionPtr& options_cfg, const uint64_t host_id); @@ -1260,7 +1269,7 @@ public: /// @ref Host objects depends on the type of the exchange object. /// /// @param stindex Statement index. - /// @param bind Pointer to an array of MySQL bindings. + /// @param bind Pointer to an array of PgSQL bindings. /// @param exchange Pointer to the exchange object used for the /// particular query. /// @param [out] result Reference to the collection of hosts returned. @@ -1334,7 +1343,7 @@ public: /// or dhcp6_options table. boost::shared_ptr host_option_exchange_; - /// @brief MySQL connection + /// @brief PgSQL connection PgSqlConnection conn_; /// @brief Indicates if the database is opened in read only mode. @@ -1508,7 +1517,7 @@ TaggedStatementArray tagged_statements = { { }, // PgSqlHostDataSourceImpl::GET_VERSION - // Retrieves MySQL schema version. + // Retrieves PgSQL schema version. {0, { OID_NONE }, "get_version", @@ -1561,6 +1570,34 @@ TaggedStatementArray tagged_statements = { { "INSERT INTO dhcp6_options(code, value, formatted_value, space, " " persistent, host_id, scope_id) " "VALUES ($1, $2, $3, $4, $5, $6, 3)" + }, + + // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4 + // Deletes a v4 host that matches (subnet-id, addr4) + {2, + { OID_INT4, OID_INT8 }, + "del_host_addr4", + "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2" + }, + + // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID + // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier) + {3, + { OID_INT4, OID_INT2, OID_BYTEA }, + "del_host_subid4_id", + "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 " + "AND dhcp_identifier_type = $2 " + "AND dhcp_identifier = $3" + }, + + // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID + // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier) + {3, + { OID_INT4, OID_INT2, OID_BYTEA }, + "del_host_subid6_id", + "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 " + "AND dhcp_identifier_type = $2 " + "AND dhcp_identifier = $3" } } }; @@ -1634,6 +1671,33 @@ PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex, } +bool +PgSqlHostDataSourceImpl::delStatement(StatementIndex stindex, + PsqlBindArrayPtr& bind_array) { + PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name, + tagged_statements[stindex].nbparams, + &bind_array->values_[0], + &bind_array->lengths_[0], + &bind_array->formats_[0], 0)); + + int s = PQresultStatus(r); + + if (s != PGRES_COMMAND_OK) { + // Connection determines if the error is fatal or not, and + // throws the appropriate exception + conn_.checkStatementError(r, tagged_statements[stindex]); + } + + // Now check how many rows (hosts) were deleted. This should be either + // "0" or "1". + char* rows_deleted = PQcmdTuples(r); + if (!rows_deleted) { + isc_throw(DbOperationError, + "Could not retrieve the number of deleted rows."); + } + return (rows_deleted[0] != '0'); +} + void PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv, const HostID& id) { @@ -1787,7 +1851,7 @@ PgSqlHostDataSource::add(const HostPtr& host) { // the PgSqlTransaction class. PgSqlTransaction transaction(impl_->conn_); - // Create the MYSQL_BIND array for the host + // Create the PgSQL Bind array for the host PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host); // ... and insert the host. @@ -1821,6 +1885,71 @@ PgSqlHostDataSource::add(const HostPtr& host) { transaction.commit(); } +bool +PgSqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) { + // If operating in read-only mode, throw exception. + impl_->checkReadOnly(); + + if (addr.isV4()) { + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + bind_array->add(subnet_id); + bind_array->add(addr); + return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_ADDR4, + bind_array)); + } + + ConstHostPtr host = get6(subnet_id, addr); + if (!host) { + return (false); + } + + return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0], + host->getIdentifier().size()); +} + +bool +PgSqlHostDataSource::del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, + const size_t identifier_len) { + + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Subnet-id + bind_array->add(subnet_id); + + // identifier-type + bind_array->add(static_cast(identifier_type)); + + // identifier + bind_array->add(identifier_begin, identifier_len); + + return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, + bind_array)); + +} + +bool +PgSqlHostDataSource::del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, + const size_t identifier_len) { + PsqlBindArrayPtr bind_array(new PsqlBindArray()); + + // Subnet-id + bind_array->add(subnet_id); + + // identifier-type + bind_array->add(static_cast(identifier_type)); + + // identifier + bind_array->add(identifier_begin, identifier_len); + + return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, + bind_array)); + +} + ConstHostCollection PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const { @@ -1880,11 +2009,11 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr, /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid) if (hwaddr && duid) { - isc_throw(BadValue, "MySQL host data source get4() called with both" + isc_throw(BadValue, "PgSQL host data source get4() called with both" " hwaddr and duid, only one of them is allowed"); } if (!hwaddr && !duid) { - isc_throw(BadValue, "MySQL host data source get4() called with " + isc_throw(BadValue, "PgSQL host data source get4() called with " "neither hwaddr or duid specified, one of them is required"); } @@ -1949,11 +2078,11 @@ PgSqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid, /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid) if (hwaddr && duid) { - isc_throw(BadValue, "MySQL host data source get6() called with both" + isc_throw(BadValue, "PgSQL host data source get6() called with both" " hwaddr and duid, only one of them is allowed"); } if (!hwaddr && !duid) { - isc_throw(BadValue, "MySQL host data source get6() called with " + isc_throw(BadValue, "PgSQL host data source get6() called with " "neither hwaddr or duid specified, one of them is required"); } diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h index 8c5371dd11..cfed1ca5f5 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.h +++ b/src/lib/dhcpsrv/pgsql_host_data_source.h @@ -251,6 +251,37 @@ public: /// violation virtual void add(const HostPtr& host); + /// @brief Attempts to delete a host by (subnet-id, address) + /// + /// This method supports both v4 and v6. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr); + + /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier) + /// + /// This method supports v4 hosts only. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del4(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + + /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier) + /// + /// This method supports v6 hosts only. + /// + /// @param subnet_id subnet identfier. + /// @param addr specified address. + /// @return true if deletion was successful, false otherwise. + virtual bool del6(const SubnetID& subnet_id, + const Host::IdentifierType& identifier_type, + const uint8_t* identifier_begin, const size_t identifier_len); + /// @brief Return backend type /// /// Returns the type of database as the string "postgresql". This is diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc index 747b5a5c1d..85b930a9bf 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc @@ -1463,6 +1463,103 @@ GenericHostDataSourceTest::testMessageFields4() { ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds)); } +void GenericHostDataSourceTest::testDeleteByAddr4() { + // Make sure we have a pointer to the host data source. + ASSERT_TRUE(hdsptr_); + + // Let's create a v4 host... + HostPtr host1 = initializeHost4("192.0.2.1", Host::IDENT_HWADDR); + SubnetID subnet1 = host1->getIPv4SubnetID(); + + // ... and add it to the data source. + ASSERT_NO_THROW(hdsptr_->add(host1)); + + // And then try to retrieve it back. + ConstHostPtr before = hdsptr_->get4(subnet1, IOAddress("192.0.2.1")); + + // Now try to delete it: del(subnet-id, addr4) + EXPECT_TRUE(hdsptr_->del(subnet1, IOAddress("192.0.2.1"))); + + // Check if it's still there. + ConstHostPtr after = hdsptr_->get4(subnet1, IOAddress("192.0.2.1")); + + // Make sure the host was there before... + EXPECT_TRUE(before); + + // ... and that it's gone after deletion. + EXPECT_FALSE(after); +} + +void GenericHostDataSourceTest::testDeleteById4() { + // Make sure we have a pointer to the host data source. + ASSERT_TRUE(hdsptr_); + + // Let's create a v4 host... + HostPtr host1 = initializeHost4("192.0.2.1", Host::IDENT_HWADDR); + SubnetID subnet1 = host1->getIPv4SubnetID(); + + // ... and add it to the data source. + ASSERT_NO_THROW(hdsptr_->add(host1)); + + // And then try to retrieve it back. + ConstHostPtr before = hdsptr_->get4(subnet1, + host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size()); + + // Now try to delete it: del4(subnet4-id, identifier-type, identifier) + EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size())); + + // Check if it's still there. + ConstHostPtr after = hdsptr_->get4(subnet1, + host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size()); + + // Make sure the host was there before... + EXPECT_TRUE(before); + + // ... and that it's gone after deletion. + EXPECT_FALSE(after); +} + +void GenericHostDataSourceTest::testDeleteById6() { + // Make sure we have a pointer to the host data source. + ASSERT_TRUE(hdsptr_); + + // Let's create a v6 host... + HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false); + SubnetID subnet1 = host1->getIPv6SubnetID(); + + // ... and add it to the data source. + ASSERT_NO_THROW(hdsptr_->add(host1)); + + // And then try to retrieve it back. + ConstHostPtr before = hdsptr_->get6(subnet1, + host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size()); + + // Now try to delete it: del4(subnet4-id, identifier-type, identifier) + EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size())); + + // Check if it's still there. + ConstHostPtr after = hdsptr_->get6(subnet1, + host1->getIdentifierType(), + &host1->getIdentifier()[0], + host1->getIdentifier().size()); + + // Make sure the host was there before... + EXPECT_TRUE(before); + + // ... and that it's gone after deletion. + EXPECT_FALSE(after); +} + }; // namespace test }; // namespace dhcp }; // namespace isc diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h index db03e20ade..9566ee07bc 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h @@ -522,9 +522,23 @@ public: /// from a database for a host. /// /// Uses gtest macros to report failures. - /// void testMessageFields4(); + /// @brief Tests that delete(subnet-id, addr4) call works. + /// + /// Uses gtest macros to report failures. + void testDeleteByAddr4(); + + /// @brief Tests that delete(subnet4-id, identifier-type, identifier) works. + /// + /// Uses gtest macros to report failures. + void testDeleteById4(); + + /// @brief Tests that delete(subnet6-id, identifier-type, identifier) works. + /// + /// Uses gtest macros to report failures. + void testDeleteById6(); + /// @brief Returns DUID with identical content as specified HW address /// /// This method does not have any sense in real life and is only useful diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc index 845b9d915b..793a211336 100644 --- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2017 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 @@ -535,4 +535,19 @@ TEST_F(MySqlHostDataSourceTest, messageFields) { testMessageFields4(); } +// Check that delete(subnet-id, addr4) works. +TEST_F(MySqlHostDataSourceTest, deleteByAddr4) { + testDeleteByAddr4(); +} + +// Check that delete(subnet4-id, identifier-type, identifier) works. +TEST_F(MySqlHostDataSourceTest, deleteById4) { + testDeleteById4(); +} + +// Check that delete(subnet6-id, identifier-type, identifier) works. +TEST_F(MySqlHostDataSourceTest, deleteById6) { + testDeleteById6(); +} + }; // Of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc index 4c0f7c0a8e..ee5b9e0ea7 100644 --- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2017 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 @@ -493,4 +493,19 @@ TEST_F(PgSqlHostDataSourceTest, messageFields) { testMessageFields4(); } +// Check that delete(subnet-id, addr4) works. +TEST_F(PgSqlHostDataSourceTest, deleteByAddr4) { + testDeleteByAddr4(); +} + +// Check that delete(subnet4-id, identifier-type, identifier) works. +TEST_F(PgSqlHostDataSourceTest, deleteById4) { + testDeleteById4(); +} + +// Check that delete(subnet6-id, identifier-type, identifier) works. +TEST_F(PgSqlHostDataSourceTest, deleteById6) { + testDeleteById6(); +} + }; // Of anonymous namespace