Browse Source

[5208a] delete methods implemented in host data sources

Tomek Mrugalski 8 years ago
parent
commit
8b561ef854

+ 38 - 0
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.)

+ 25 - 0
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();

+ 39 - 0
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.)

+ 34 - 0
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

+ 37 - 0
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.

+ 152 - 1
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<MYSQL_BIND>& 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<char*>(&subnet);
+        inbind[0].is_unsigned = MLM_TRUE;
+
+        uint32_t addr4 = addr.toUint32();
+        inbind[1].buffer_type = MYSQL_TYPE_LONG;
+        inbind[1].buffer = reinterpret_cast<char*>(&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<uint32_t>(subnet_id);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+    inbind[0].is_unsigned = MLM_TRUE;
+
+    // identifier type
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    // identifier value
+    std::vector<char> 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<uint32_t>(subnet_id);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+    inbind[0].is_unsigned = MLM_TRUE;
+
+    // identifier type
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    // identifier value
+    std::vector<char> 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 {

+ 31 - 0
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.)

+ 144 - 15
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
     /// <primary key> 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<PgSqlOptionExchange> 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<uint8_t>(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<uint8_t>(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");
     }
 

+ 31 - 0
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

+ 97 - 0
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

+ 15 - 1
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

+ 16 - 1
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

+ 16 - 1
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