Browse Source

[master] Merge branch 'trac5208a' (preparations for host_cmds hook)

Tomek Mrugalski 8 years ago
parent
commit
88555d8f23

+ 1 - 118
configure.ac

@@ -118,124 +118,7 @@ AC_CHECK_DECL([__clang__], [CLANGPP="yes"], [CLANGPP="no"])
 AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 
 
 # Check for C++11 features support
 # Check for C++11 features support
-CXX_SAVED=$CXX
-feature=
-for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
-	if test "$retry" = "fail"; then
-		AC_MSG_ERROR([$feature (a C++11 feature) is not supported])
-	fi
-	if test "$retry" != "none"; then
-		AC_MSG_WARN([unsupported C++11 feature])
-		AC_MSG_NOTICE([retrying by adding $retry to $CXX])
-		CXX="$CXX_SAVED $retry"
-		AC_MSG_CHECKING($retry support)
-		AC_COMPILE_IFELSE(
-			[AC_LANG_PROGRAM(
-				[],
-				[int myincr = 1;])],
-			[AC_MSG_RESULT([yes])],
-			[AC_MSG_RESULT([no])
-			 continue])
-	fi
-
-	AC_MSG_CHECKING(std::unique_ptr support)
-	feature="std::unique_ptr"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[#include <memory>],
-			[std::unique_ptr<int> a;])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(cbegin/cend support)
-	feature="cbegin/cend"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[#include <string>],
-			[const std::string& s = "abcd";
-			 unsigned count = 0;
-			 for (std::string::const_iterator i = s.cbegin();
-			      i != s.cend(); ++i)
-				if (*i == 'b')
-					++count;])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(final method support)
-	feature="final method"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[class Foo {
-			 public:
-			 	virtual ~Foo() {};
-				virtual void bar() final;
-			 };],[])],
-		 [AC_MSG_RESULT([yes])],
-		 [AC_MSG_RESULT([no])
-		  continue])
-
-	AC_MSG_CHECKING(aggregate initialization support)
-	feature="aggregate initialization"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[#include <vector>],
-			[std::vector<int> foo = { 1, 2, 3};])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(variadic template support)
-	feature="variadic template"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[template<typename ... Args>
-			 struct A {
-			 void foo(Args... myargs) { return; };
-			 };],
-			 [A<> a;
-			  a.foo();])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(static_assert support)
-	feature="static_assert"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[static_assert(1 + 1 == 2, "");],
-			[])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(template alias)
-	feature="template alias"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[template<int i>
-			 class I {
-			 public: int get() { return i; };
-			 };
-			 using Zero = I<0>;],
-			[Zero Z;
-			 return Z.get();])],
-		[AC_MSG_RESULT([yes])],
-		[AC_MSG_RESULT([no])
-		 continue])
-
-	AC_MSG_CHECKING(lambda support)
-	feature="lambda"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[],
-			[auto myincr = [[]](int x) { return x + 1; };])],
-		[AC_MSG_RESULT([yes])
-		 break],
-		[AC_MSG_RESULT([no])
-		 continue])
-done
+AX_ISC_CPP11
 
 
 # Check for std::is_base_of support
 # Check for std::is_base_of support
 AC_MSG_CHECKING([for std::is_base_of])
 AC_MSG_CHECKING([for std::is_base_of])

+ 385 - 65
doc/guide/hooks.xml

@@ -207,7 +207,7 @@
             <row>
             <row>
               <entry>Flexible Identifier</entry>
               <entry>Flexible Identifier</entry>
               <entry>Support customers</entry>
               <entry>Support customers</entry>
-              <entry>Kea 1.2.0 beta</entry>
+              <entry>Kea 1.2.0</entry>
               <entry>Kea software provides a way to handle host reservations
               <entry>Kea software provides a way to handle host reservations
               that include addresses, prefixes, options, client classes and
               that include addresses, prefixes, options, client classes and
               other features. The reservation can be based on hardware address,
               other features. The reservation can be based on hardware address,
@@ -219,11 +219,28 @@
               client. Those scenarios are addressed by the Flexible Identifiers
               client. Those scenarios are addressed by the Flexible Identifiers
               hook application. It allows defining an expression, similar to
               hook application. It allows defining an expression, similar to
               the one used in client classification,
               the one used in client classification,
-	      e.g. substring(relay6[0].option[37],0,6). Each incoming packet is
-	      evaluated against that expression and its value is then searched
-	      in the reservations database.
+              e.g. substring(relay6[0].option[37],0,6). Each incoming packet is
+              evaluated against that expression and its value is then searched
+              in the reservations database.
               </entry>
               </entry>
             </row>
             </row>
+            <row>
+              <entry>Host Commands</entry>
+              <entry>Support customers</entry>
+              <entry>Kea 1.2.0</entry>
+              <entry>Kea provides a way to store host reservations in a
+              database. In many larger deployments it is useful to be able to
+              manage that information while the server is running. This library
+              provides management commands for adding, querying and deleting
+              host reservations in a safe way without restarting the server.
+              In particular, it validates the parameters, so an attempt to
+              insert incorrect data, e.g. add a host with conflicting identifier
+              in the same subnet will be rejected. Those commands are
+              exposed via command channel (JSON over unix sockets) and Control
+              Agent (JSON over RESTful interface). Additional commands and
+              capabilities related to host reservations will be added in the
+              future.</entry>
+            </row>
           </tbody>
           </tbody>
           </tgroup>
           </tgroup>
           </table>
           </table>
@@ -547,7 +564,7 @@ link address: 3001::1, hop count: 1, identified by remote-id:
         </section>
         </section>
       </section>
       </section>
 
 
-      <section>
+      <section id="flex-id">
         <title>flex_id: Flexible Identifiers for Host Reservations</title>
         <title>flex_id: Flexible Identifiers for Host Reservations</title>
         <para>
         <para>
           This section describes a hook application dedicated to generate
           This section describes a hook application dedicated to generate
@@ -560,25 +577,28 @@ link address: 3001::1, hop count: 1, identified by remote-id:
           options that mentioned above, uses part of specific options or perhaps
           options that mentioned above, uses part of specific options or perhaps
           even a combination of several options and fields to uniquely identify
           even a combination of several options and fields to uniquely identify
           a client. Those scenarios are addressed by the Flexible Identifiers
           a client. Those scenarios are addressed by the Flexible Identifiers
-          hook application.</para>
-
-	<para>The library allows defining an expression, using notation
-	initially used for client classification only. See <xref
-	linkend="classification-using-expressions" /> for detailed description
-	of the syntax available. One notable difference is that for client
-	classification the expression currently has to evaluate to either true
-	or false, while the flexible identifier expression is expected to
-	evaluate to a string that will be used as identifier. It is a valid case
-	for the expression to evaluate to empty string (e.g. in cases where a
-	client does not sent specific options). This expression is then
-	evaluated for each incoming packet. This evaluation generates an
-	identifier that is used to identify the client. In particular, there may
-	be host reservations that are tied to specific values of the flexible
-	identifier.</para>
-
-	<para>
-	  The library can be loaded in similar way as other hook libraries. It
-	  takes one mandatory parameter identifier-expression:
+        hook application.</para>
+
+        <para>Currently this library is only available to ISC customers with a
+        support contract.</para>
+
+        <para>The library allows for defining an expression, using notation
+        initially used for client classification only. See <xref
+        linkend="classification-using-expressions" /> for detailed description
+        of the syntax available. One notable difference is that for client
+        classification the expression currently has to evaluate to either true
+        or false, while the flexible identifier expression is expected to
+        evaluate to a string that will be used as identifier. It is a valid case
+        for the expression to evaluate to empty string (e.g. in cases where a
+        client does not sent specific options). This expression is then
+        evaluated for each incoming packet. This evaluation generates an
+        identifier that is used to identify the client. In particular, there may
+        be host reservations that are tied to specific values of the flexible
+        identifier.</para>
+
+        <para>
+          The library can be loaded in similar way as other hook libraries. It
+          takes one mandatory parameter identifier-expression:
 <screen>
 <screen>
 "Dhcp6": { <userinput>
 "Dhcp6": { <userinput>
     "hooks-libraries": [
     "hooks-libraries": [
@@ -592,26 +612,26 @@ link address: 3001::1, hop count: 1, identified by remote-id:
     ] </userinput>
     ] </userinput>
 }
 }
 </screen>
 </screen>
-	</para>
-
-	<para>
-	  The flexible identifier library supports both DHCPv4 and DHCPv6.
-	</para>
-
-	<para>
-	  EXAMPLE: Let's consider a case of an IPv6 network that has an
-	  independent interface for each of the connected customers. Customers
-	  are able to plug in whatever device they want, so any type of
-	  identifier (e.g. a client-id) is unreliable. Therefore the operator
-	  may decide to use an option inserted by a relay agent to differentiate
-	  between clients. In this particular deployment, the operator verified
-	  that the interface-id is unique for each customer facing
-	  interface. Therefore it is suitable for usage as reservation. However,
-	  only the first 6 bytes of the interface-id are interesting, because
-	  remaining bytes are either randomly changed or not unique between
-	  devices. Therefore the customer decided to use first 6 bytes of the
-	  interface-id option inserted by the relay agent. This could be
-	  achieved by using the following configuration:
+        </para>
+
+        <para>
+          The flexible identifier library supports both DHCPv4 and DHCPv6.
+        </para>
+
+        <para>
+          EXAMPLE: Let's consider a case of an IPv6 network that has an
+          independent interface for each of the connected customers. Customers
+          are able to plug in whatever device they want, so any type of
+          identifier (e.g. a client-id) is unreliable. Therefore the operator
+          may decide to use an option inserted by a relay agent to differentiate
+          between clients. In this particular deployment, the operator verified
+          that the interface-id is unique for each customer facing
+          interface. Therefore it is suitable for usage as reservation. However,
+          only the first 6 bytes of the interface-id are interesting, because
+          remaining bytes are either randomly changed or not unique between
+          devices. Therefore the customer decided to use first 6 bytes of the
+          interface-id option inserted by the relay agent. This could be
+          achieved by using the following configuration:
 <screen>
 <screen>
 "Dhcp6": {
 "Dhcp6": {
     "subnet6": [{ ..., // subnet definition starts here
     "subnet6": [{ ..., // subnet definition starts here
@@ -631,27 +651,327 @@ link address: 3001::1, hop count: 1, identified by remote-id:
     ]
     ]
 }
 }
 </screen>
 </screen>
-	</para>
-
-	<para>
-	  NOTE: Care should be taken when adjusting the expression. If the
-	  expression changes, then all the flex-id values may change, possibly
-	  rendering all reservations based on flex-id unusable until they're
-	  manually updated. Therefore it is strongly recommended to start with
-	  the expression and a handful reservations, adjust the expression as
-	  needed and only after it was confirmed the expression does exactly
-	  what is expected out of it go forward with host reservations on any
-	  broader scale.
-	</para>
-
-	<para>
-	  flex-id values in host reservations can be specified in two
-	  ways. First, they can be expressed as hex string, e.g. bar string
-	  can be represented as 626174. Alternatively, it can be expressed
-	  as quoted value (using double and single quotes), e.g. "'bar'".
-	  The former is more convenient for printable characters, while hex
-	  string values are more convenient for non-printable characters.
-	</para>
+        </para>
+
+        <para>
+          NOTE: Care should be taken when adjusting the expression. If the
+          expression changes, then all the flex-id values may change, possibly
+          rendering all reservations based on flex-id unusable until they're
+          manually updated. Therefore it is strongly recommended to start with
+          the expression and a handful reservations, adjust the expression as
+          needed and only after it was confirmed the expression does exactly
+          what is expected out of it go forward with host reservations on any
+          broader scale.
+        </para>
+
+        <para>
+          flex-id values in host reservations can be specified in two
+          ways. First, they can be expressed as hex string, e.g. bar string
+          can be represented as 626174. Alternatively, it can be expressed
+          as quoted value (using double and single quotes), e.g. "'bar'".
+          The former is more convenient for printable characters, while hex
+          string values are more convenient for non-printable characters.
+        </para>
+      </section>
+
+      <section id="host-cmds">
+        <title>host_cmds: Host Commands</title>
+        <para>
+          This section describes a hook application that offers a number of new
+          commands used to query and manipulate host reservations. Kea provides
+          a way to store host reservations in a database. In many larger
+          deployments it is useful to be able to manage that information while
+          the server is running. This library provides management commands for
+          adding, querying and deleting host reservations in a safe way without
+          restarting the server.  In particular, it validates the parameters, so
+          an attempt to insert incorrect data e.g. add a host with conflicting
+          identifier in the same subnet will be rejected. Those commands are
+          exposed via command channel (JSON over unix sockets) and Control Agent
+          (JSON over RESTful interface). Additional commands and capabilities
+          related to host reservations will be added in the future.
+        </para>
+
+        <para>Currently this library is only available to ISC customers with a
+        support contract.</para>
+        
+        <para>
+          Currently three commands are supported: reservation-add (which adds
+          new host reservation), reservation-get (which returns existing
+          reservation if specified criteria are matched) and reservation-del
+          (which attempts to delete a reservation matching specified
+          criteria). To use commands that change the reservation information
+          (currently these are reservation-add and reservation-del, but this
+          rule applies to other commands that may be implemented in the future),
+          hosts database must be specified (see hosts-database description in
+          <xref linkend="hosts-database-configuration4"/> and <xref
+          linkend="hosts-database-configuration6"/>) and it must not operate in
+          read-only mode. If the hosts-database is not specified or is running
+          in read-only mode, the host_cmds library will load, but any attempts
+          to use reservation-add or reservation-del will fail.
+        </para>
+
+        <para>
+          Additional host reservation commands are planned in the future. For
+          a description of envisaged commands, see
+<ulink url="http://kea.isc.org/wiki/ControlAPIRequirements">Control API
+Requirements </ulink> document.</para>
+
+        <para>
+          All commands are using JSON syntax. They can be issued either using
+          control channel (see <xref linkend="ctrl-channel"/>) or via Control
+          Agent (see <xref linkend="kea-ctrl-agent"/>). 
+        </para>
+
+        <para>
+          The library can be loaded in similar way as other hook libraries. It
+          does not take any parameters. It supports both DHCPv4 and DHCPv6
+          servers.
+<screen>
+"Dhcp6": { <userinput>
+    "hooks-libraries": [
+        {
+            "library": "/path/libdhcp_host_cmds.so"
+        }
+        ...
+    ] </userinput>
+}
+</screen>
+        </para>
+
+        <section>
+          <title>reservation-add command</title>
+        <para>
+          <command>reservation-add</command> allows insertion of a new host.  It
+          takes a set of arguments that vary depending on the nature of the host
+          reservation. Any parameters allowed in the configuration file that
+          pertain to host reservation are permitted here. For details regarding
+          IPv4 reservations, see <xref linkend="host-reservation-v4"/> and <xref
+          linkend="host-reservation-v6"/>. There is one notable addition. A
+          <command>subnet-id</command> must be specified.  This parameter is
+          mandatory, because reservations specified in the configuration file
+          are always defined within a subnet, so the subnet they belong to is
+          clear. This is not the case with reservation-add, therefore the
+          subnet-id must be specified explicitly. An example command can be as
+          simple as:
+<screen>{
+    "command": "reservation-add",
+    "arguments": {
+        <userinput>"reservation": {
+            "subnet-id": 1,
+            "hw-address": "1a:1b:1c:1d:1e:1f",
+            "ip-address": "192.0.2.202"
+        }</userinput>
+    }
+}</screen> but can also take many more parameters, for example:
+
+<screen>
+{
+    "command": "reservation-add",
+    "arguments": {
+        <userinput>"reservation": {
+            {
+                "client-id": "01:0a:0b:0c:0d:0e:0f",
+                "ip-address": "192.0.2.205",
+                "next-server": "192.0.2.1",
+                "server-hostname": "hal9000",
+                "boot-file-name": "/dev/null",
+                "option-data": [
+                    {
+                        "name": "domain-name-servers",
+                        "data": "10.1.1.202,10.1.1.203"
+                    }
+                ],
+                "client-classes": [ "special_snowflake", "office" ]
+            }
+        }</userinput>
+    }
+}</screen>
+    
+Here is an example of complex IPv6 reservation:
+<screen>
+{
+    "command": "reservation-add",
+    "arguments": {
+        <userinput>"reservation": {
+            {
+                "duid": "01:02:03:04:05:06:07:08:09:0A",
+                "ip-addresses": [ "2001:db8:1:cafe::1" ],
+                "prefixes": [ "2001:db8:2:abcd::/64" ],
+                "hostname": "foo.example.com",
+                "option-data": [
+                    {
+                        "name": "vendor-opts",
+                        "data": "4491"
+                    },
+                    {
+                        "name": "tftp-servers",
+                        "space": "vendor-4491",
+                        "data": "3000:1::234"
+                    }
+                ]
+            }
+        }</userinput>
+    }
+}</screen>
+        </para>          
+
+        <para>
+          The command returns a status that indicates either a success (result
+          0) or a failure (result 1). Failed command always includes text
+          parameter that explains the cause of failure. Example results:
+          <screen>{ "result": 0, "text": "Host added." }</screen> Example failure:
+          <screen>{ "result": 1, "text": "Mandatory 'subnet-id' parameter missing." }</screen>
+        </para>
+
+        <para>
+          As <command>reservation-add</command> is expected to store the host,
+          hosts-database parameter must be specified in your configuration and
+          the database must not run in read-only mode. In the future versions
+          it will be possible to modify the reservations read from a
+          configuration file. Please contact ISC if you are interested in this
+          functionality.
+        </para>
+        </section>
+
+        <section>
+          <title>reservation-get command</title>
+          <para><command>reservation-get</command> can be used to query the host
+          database and retrieve existing reservations. There are two types of
+          parameters this command supports: (subnet-id, address) or (subnet-id,
+          identifier-type, identifier). The first type of query is used when the
+          address (either IPv4 or IPv6) is known, but the details of the
+          reservation aren't. One common use case of this type of query is to
+          find out whether a given address is reserved or not. The second query
+          uses identifiers. For maximum flexibility, Kea stores the host
+          identifying information as a pair of values: type and the actual
+          identifier. Currently supported identifiers are "hw-address", "duid",
+          "circuit-id", "client-id" and "flex-id", but additional types may be
+          added in the future. If any new identifier types are defined in the
+          future, reservation-get command will support them
+          automatically.</para>
+
+          <para>
+            An example command for getting a host reservation by (subnet-id,
+            address) pair looks as follows:
+<screen>
+{
+    "command": "reservation-get",
+    "arguments": {
+        "subnet-id": 1,
+        "ip-address": "192.0.2.202"
+    }
+}</screen>
+
+An example query by (subnet-id, identifier-type, identifier) looks as follows:
+<screen>
+{
+    "command": "reservation-get",
+    "arguments":
+        "subnet-id": 4,
+        "identifier-type": "hw-address",
+        "identifier": "01:02:03:04:05:06"
+    }
+}</screen>
+
+          </para>
+          <para><command>reservation-get</command> typically returns result 0
+          when the query was conducted properly. In particular, 0 is returned
+          when the host was not found. If the query was successful a number
+          of host parameters will be returned. An example of a query that
+          did not find the host looks as follows:
+<screen>{ "result": 0, "text": "Host not found." }</screen>
+
+An example result returned when the host was found:
+<screen>{
+  "arguments": {
+    "boot-file-name": "bootfile.efi",
+    "client-classes": [
+      
+    ],
+    "hostname": "somehost.example.org",
+    "hw-address": "01:02:03:04:05:06",
+    "ip-address": "192.0.2.100",
+    "next-server": "192.0.0.2",
+    "option-data": [
+      
+    ],
+    "server-hostname": "server-hostname.example.org"
+  },
+  "result": 0,
+  "text": "Host found."
+}</screen>
+
+An example result returned when the query was malformed:<screen>
+{ "result": 1, "text": "No 'ip-address' provided and 'identifier-type'
+                        is either missing or not a string." }</screen>
+</para>
+            
+        </section>
+
+        <section>
+          <title>reservation-del command</title>
+          <para><command>reservation-del</command> can be used to delete a
+          reservation from the host database. There are two types of parameters
+          this command supports: (subnet-id, address) or (subnet-id,
+          identifier-type, identifier). The first type of query is used when the
+          address (either IPv4 or IPv6) is known, but the details of the
+          reservation aren't. One common use case of this type of query is to
+          remove a reservation (e.g. you want a specific address to no longer be
+          reserved). The second query uses identifiers. For maximum flexibility,
+          Kea stores the host identifying information as a pair of values: type
+          and the actual identifier. Currently supported identifiers are
+          "hw-address", "duid", "circuit-id", "client-id" and "flex-id", but
+          additional types may be added in the future. If any new identifier
+          types are defined in the future, reservation-get command will support
+          them automatically.</para>
+
+          <para>
+            An example command for deleting a host reservation by (subnet-id,
+            address) pair looks as follows:
+<screen>
+{
+    "command": "reservation-del",
+    "arguments": {
+        "subnet-id": 1,
+        "ip-address": "192.0.2.202"
+    }
+}</screen>
+
+An example deletion by (subnet-id, identifier-type, identifier) looks as follows:
+<screen>
+{
+    "command": "reservation-del",
+    "arguments":
+        "subnet-id": 4,
+        "identifier-type": "hw-address",
+        "identifier": "01:02:03:04:05:06"
+    }
+}</screen>
+          </para>
+          <para>
+            <command>reservation-del</command> returns result 0 when the host
+            deletion was successul or 1 if it was not. A descriptive text is
+            provided in case of error. Example results look as follows:
+<screen>
+{
+    "result": 1,
+    "text": "Host not deleted (not found)."
+}</screen>
+
+<screen>          
+{
+    "result": 0,
+    "text": "Host deleted."
+}</screen>
+
+<screen>
+{
+    "result": 1,
+    "text": "Unable to delete a host because there is no hosts-database
+             configured."
+}</screen>
+          </para>
+        </section>
       </section>
       </section>
 
 
     </section>
     </section>

+ 1 - 1
m4macros/Makefile.am

@@ -1 +1 @@
-EXTRA_DIST  = ax_boost_for_kea.m4 ax_isc_rpath.m4
+EXTRA_DIST  = ax_boost_for_kea.m4 ax_isc_rpath.m4 ax_cpp11.m4

+ 122 - 0
m4macros/ax_cpp11.m4

@@ -0,0 +1,122 @@
+AC_DEFUN([AX_ISC_CPP11], [
+
+CXX_SAVED=$CXX
+feature=
+for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
+	if test "$retry" = "fail"; then
+		AC_MSG_ERROR([$feature (a C++11 feature) is not supported])
+	fi
+	if test "$retry" != "none"; then
+		AC_MSG_WARN([unsupported C++11 feature])
+		AC_MSG_NOTICE([retrying by adding $retry to $CXX])
+		CXX="$CXX_SAVED $retry"
+		AC_MSG_CHECKING($retry support)
+		AC_COMPILE_IFELSE(
+			[AC_LANG_PROGRAM(
+				[],
+				[int myincr = 1;])],
+			[AC_MSG_RESULT([yes])],
+			[AC_MSG_RESULT([no])
+			 continue])
+	fi
+
+	AC_MSG_CHECKING(std::unique_ptr support)
+	feature="std::unique_ptr"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[#include <memory>],
+			[std::unique_ptr<int> a;])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(cbegin/cend support)
+	feature="cbegin/cend"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[#include <string>],
+			[const std::string& s = "abcd";
+			 unsigned count = 0;
+			 for (std::string::const_iterator i = s.cbegin();
+			      i != s.cend(); ++i)
+				if (*i == 'b')
+					++count;])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(final method support)
+	feature="final method"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[class Foo {
+			 public:
+			 	virtual ~Foo() {};
+				virtual void bar() final;
+			 };],[])],
+		 [AC_MSG_RESULT([yes])],
+		 [AC_MSG_RESULT([no])
+		  continue])
+
+	AC_MSG_CHECKING(aggregate initialization support)
+	feature="aggregate initialization"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[#include <vector>],
+			[std::vector<int> foo = { 1, 2, 3};])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(variadic template support)
+	feature="variadic template"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[template<typename ... Args>
+			 struct A {
+			 void foo(Args... myargs) { return; };
+			 };],
+			 [A<> a;
+			  a.foo();])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(static_assert support)
+	feature="static_assert"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[static_assert(1 + 1 == 2, "");],
+			[])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(template alias)
+	feature="template alias"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[template<int i>
+			 class I {
+			 public: int get() { return i; };
+			 };
+			 using Zero = I<0>;],
+			[Zero Z;
+			 return Z.get();])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(lambda support)
+	feature="lambda"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[],
+			[auto myincr = [[]](int x) { return x + 1; };])],
+		[AC_MSG_RESULT([yes])
+		 break],
+		[AC_MSG_RESULT([no])
+		 continue])
+done
+
+])dnl AX_ISC_RPATH

+ 40 - 0
src/lib/dhcpsrv/base_host_data_source.h

@@ -247,6 +247,46 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host) = 0;
     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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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
     /// @brief Return backend type
     ///
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 37 - 108
src/lib/dhcpsrv/cfg_hosts.cc

@@ -688,6 +688,33 @@ 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
 ElementPtr
 CfgHosts::toElement() const {
 CfgHosts::toElement() const {
     uint16_t family = CfgMgr::instance().getFamily();
     uint16_t family = CfgMgr::instance().getFamily();
@@ -708,60 +735,12 @@ CfgHosts::toElement4() const {
     const HostContainerIndex0& idx = hosts_.get<0>();
     const HostContainerIndex0& idx = hosts_.get<0>();
     for (HostContainerIndex0::const_iterator host = idx.begin();
     for (HostContainerIndex0::const_iterator host = idx.begin();
          host != idx.end(); ++host) {
          host != idx.end(); ++host) {
-        // Get the subnet ID
+
+        // Convert host to element representation
+        ElementPtr map = (*host)->toElement4();
+
+        // Push it on the list
         SubnetID subnet_id = (*host)->getIPv4SubnetID();
         SubnetID subnet_id = (*host)->getIPv4SubnetID();
-        // Prepare the map
-        ElementPtr map = Element::createMap();
-        // Set the identifier
-        Host::IdentifierType id_type = (*host)->getIdentifierType();
-        if (id_type == Host::IDENT_HWADDR) {
-            HWAddrPtr hwaddr = (*host)->getHWAddress();
-            map->set("hw-address", Element::create(hwaddr->toText(false)));
-        } else if (id_type == Host::IDENT_DUID) {
-            DuidPtr duid = (*host)->getDuid();
-            map->set("duid", Element::create(duid->toText()));
-        } else if (id_type == Host::IDENT_CIRCUIT_ID) {
-            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
-            std::string circuit_id = util::encode::encodeHex(bin);
-            map->set("circuit-id", Element::create(circuit_id));
-        } else if (id_type == Host::IDENT_CLIENT_ID) {
-            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
-            std::string client_id = util::encode::encodeHex(bin);
-            map->set("client-id", Element::create(client_id));
-        } else if (id_type == Host::IDENT_FLEX) {
-            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
-            std::string flex = util::encode::encodeHex(bin);
-            map->set("flex-id", Element::create(flex));
-        } else {
-            isc_throw(ToElementError, "invalid identifier type: " << id_type);
-        }
-        // Set the reservation
-        const IOAddress& address = (*host)->getIPv4Reservation();
-        map->set("ip-address", Element::create(address.toText()));
-        // Set the hostname
-        const std::string& hostname = (*host)->getHostname();
-        map->set("hostname", Element::create(hostname));
-        // Set next-server
-        const IOAddress& next_server = (*host)->getNextServer();
-        map->set("next-server", Element::create(next_server.toText()));
-        // Set server-hostname
-        const std::string& server_hostname = (*host)->getServerHostname();
-        map->set("server-hostname", Element::create(server_hostname));
-        // Set boot-file-name
-        const std::string& boot_file_name = (*host)->getBootFileName();
-        map->set("boot-file-name", Element::create(boot_file_name));
-        // Set client-classes
-        const ClientClasses& cclasses = (*host)->getClientClasses4();
-        ElementPtr classes = Element::createList();
-        for (ClientClasses::const_iterator cclass = cclasses.cbegin();
-             cclass != cclasses.end(); ++cclass) {
-            classes->add(Element::create(*cclass));
-        }
-        map->set("client-classes", classes);
-        // Set option-data
-        ConstCfgOptionPtr opts = (*host)->getCfgOption4();
-        map->set("option-data", opts->toElement());
-        // Push the map on the list
         result.add(subnet_id, map);
         result.add(subnet_id, map);
     }
     }
     return (result.externalize());
     return (result.externalize());
@@ -774,62 +753,12 @@ CfgHosts::toElement6() const {
     const HostContainerIndex0& idx = hosts_.get<0>();
     const HostContainerIndex0& idx = hosts_.get<0>();
     for (HostContainerIndex0::const_iterator host = idx.begin();
     for (HostContainerIndex0::const_iterator host = idx.begin();
          host != idx.end(); ++host) {
          host != idx.end(); ++host) {
-        // Get the subnet ID
+
+        // Convert host to Element representation
+        ElementPtr map = (*host)->toElement6();
+
+        // Push it on the list
         SubnetID subnet_id = (*host)->getIPv6SubnetID();
         SubnetID subnet_id = (*host)->getIPv6SubnetID();
-        // Prepare the map
-        ElementPtr map = Element::createMap();
-        // Set the identifier
-        Host::IdentifierType id_type = (*host)->getIdentifierType();
-        if (id_type == Host::IDENT_HWADDR) {
-            HWAddrPtr hwaddr = (*host)->getHWAddress();
-            map->set("hw-address", Element::create(hwaddr->toText(false)));
-        } else if (id_type == Host::IDENT_DUID) {
-            DuidPtr duid = (*host)->getDuid();
-            map->set("duid", Element::create(duid->toText()));
-        } else if (id_type == Host::IDENT_CIRCUIT_ID) {
-            isc_throw(ToElementError, "unexpected circuit-id DUID type");
-        } else if (id_type == Host::IDENT_CLIENT_ID) {
-            isc_throw(ToElementError, "unexpected client-id DUID type");
-        } else if (id_type == Host::IDENT_FLEX) {
-            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
-            std::string flex = util::encode::encodeHex(bin);
-            map->set("flex-id", Element::create(flex));
-        } else {
-            isc_throw(ToElementError, "invalid DUID type: " << id_type);
-        }
-        // Set reservations (ip-addresses)
-        IPv6ResrvRange na_resv =
-            (*host)->getIPv6Reservations(IPv6Resrv::TYPE_NA);
-        ElementPtr resvs = Element::createList();
-        for (IPv6ResrvIterator resv = na_resv.first;
-             resv != na_resv.second; ++resv) {
-            resvs->add(Element::create(resv->second.toText()));
-        }
-        map->set("ip-addresses", resvs);
-        // Set reservations (prefixes)
-        IPv6ResrvRange pd_resv =
-                (*host)->getIPv6Reservations(IPv6Resrv::TYPE_PD);
-        resvs = Element::createList();
-        for (IPv6ResrvIterator resv = pd_resv.first;
-             resv != pd_resv.second; ++resv) {
-            resvs->add(Element::create(resv->second.toText()));
-        }
-        map->set("prefixes", resvs);
-        // Set the hostname
-        const std::string& hostname = (*host)->getHostname();
-        map->set("hostname", Element::create(hostname));
-        // Set client-classes
-        const ClientClasses& cclasses = (*host)->getClientClasses6();
-        ElementPtr classes = Element::createList();
-        for (ClientClasses::const_iterator cclass = cclasses.cbegin();
-             cclass != cclasses.end(); ++cclass) {
-            classes->add(Element::create(*cclass));
-        }
-        map->set("client-classes", classes);
-        // Set option-data
-        ConstCfgOptionPtr opts = (*host)->getCfgOption6();
-        map->set("option-data", opts->toElement());
-        // Push the map on the list
         result.add(subnet_id, map);
         result.add(subnet_id, map);
     }
     }
     return (result.externalize());
     return (result.externalize());

+ 39 - 0
src/lib/dhcpsrv/cfg_hosts.h

@@ -322,6 +322,45 @@ public:
     /// has already been added to the IPv4 or IPv6 subnet.
     /// has already been added to the IPv4 or IPv6 subnet.
     virtual void add(const HostPtr& host);
     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
     /// @brief Return backend type
     ///
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 120 - 0
src/lib/dhcpsrv/host.cc

@@ -9,9 +9,13 @@
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host.h>
 #include <util/encode/hex.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <util/strutil.h>
+#include <asiolink/io_address.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <sstream>
 #include <sstream>
 
 
+using namespace isc::data;
+using namespace isc::asiolink;
+
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
@@ -400,6 +404,122 @@ Host::setBootFileName(const std::string& boot_file_name) {
     boot_file_name_ = boot_file_name;
     boot_file_name_ = boot_file_name;
 }
 }
 
 
+ElementPtr
+Host::toElement4() const {
+
+    // Prepare the map
+    ElementPtr map = Element::createMap();
+    // Set the identifier
+    Host::IdentifierType id_type = getIdentifierType();
+    if (id_type == Host::IDENT_HWADDR) {
+        HWAddrPtr hwaddr = getHWAddress();
+        map->set("hw-address", Element::create(hwaddr->toText(false)));
+    } else if (id_type == Host::IDENT_DUID) {
+        DuidPtr duid = getDuid();
+        map->set("duid", Element::create(duid->toText()));
+    } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+        const std::vector<uint8_t>& bin = getIdentifier();
+        std::string circuit_id = util::encode::encodeHex(bin);
+        map->set("circuit-id", Element::create(circuit_id));
+    } else if (id_type == Host::IDENT_CLIENT_ID) {
+        const std::vector<uint8_t>& bin = getIdentifier();
+        std::string client_id = util::encode::encodeHex(bin);
+        map->set("client-id", Element::create(client_id));
+    } else if (id_type == Host::IDENT_FLEX) {
+        const std::vector<uint8_t>& bin = getIdentifier();
+        std::string flex = util::encode::encodeHex(bin);
+        map->set("flex-id", Element::create(flex));
+    } else {
+        isc_throw(ToElementError, "invalid identifier type: " << id_type);
+    }
+    // Set the reservation
+    const IOAddress& address = getIPv4Reservation();
+    map->set("ip-address", Element::create(address.toText()));
+    // Set the hostname
+    const std::string& hostname = getHostname();
+    map->set("hostname", Element::create(hostname));
+    // Set next-server
+    const IOAddress& next_server = getNextServer();
+    map->set("next-server", Element::create(next_server.toText()));
+    // Set server-hostname
+    const std::string& server_hostname = getServerHostname();
+    map->set("server-hostname", Element::create(server_hostname));
+    // Set boot-file-name
+    const std::string& boot_file_name = getBootFileName();
+    map->set("boot-file-name", Element::create(boot_file_name));
+    // Set client-classes
+    const ClientClasses& cclasses = getClientClasses4();
+    ElementPtr classes = Element::createList();
+    for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+         cclass != cclasses.end(); ++cclass) {
+        classes->add(Element::create(*cclass));
+    }
+    map->set("client-classes", classes);
+    // Set option-data
+    ConstCfgOptionPtr opts = getCfgOption4();
+    map->set("option-data", opts->toElement());
+
+    return (map);
+}
+
+ElementPtr
+Host::toElement6() const {
+    // Prepare the map
+    ElementPtr map = Element::createMap();
+    // Set the identifier
+    Host::IdentifierType id_type = getIdentifierType();
+    if (id_type == Host::IDENT_HWADDR) {
+        HWAddrPtr hwaddr = getHWAddress();
+        map->set("hw-address", Element::create(hwaddr->toText(false)));
+    } else if (id_type == Host::IDENT_DUID) {
+        DuidPtr duid = getDuid();
+        map->set("duid", Element::create(duid->toText()));
+    } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+        isc_throw(ToElementError, "unexpected circuit-id DUID type");
+    } else if (id_type == Host::IDENT_CLIENT_ID) {
+        isc_throw(ToElementError, "unexpected client-id DUID type");
+    } else if (id_type == Host::IDENT_FLEX) {
+        const std::vector<uint8_t>& bin = getIdentifier();
+        std::string flex = util::encode::encodeHex(bin);
+        map->set("flex-id", Element::create(flex));
+    } else {
+        isc_throw(ToElementError, "invalid DUID type: " << id_type);
+    }
+    // Set reservations (ip-addresses)
+    IPv6ResrvRange na_resv = getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ElementPtr resvs = Element::createList();
+    for (IPv6ResrvIterator resv = na_resv.first;
+         resv != na_resv.second; ++resv) {
+        resvs->add(Element::create(resv->second.toText()));
+    }
+    map->set("ip-addresses", resvs);
+    // Set reservations (prefixes)
+    IPv6ResrvRange pd_resv = getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    resvs = Element::createList();
+    for (IPv6ResrvIterator resv = pd_resv.first;
+         resv != pd_resv.second; ++resv) {
+        resvs->add(Element::create(resv->second.toText()));
+    }
+    map->set("prefixes", resvs);
+    // Set the hostname
+    const std::string& hostname = getHostname();
+    map->set("hostname", Element::create(hostname));
+    // Set client-classes
+    const ClientClasses& cclasses = getClientClasses6();
+    ElementPtr classes = Element::createList();
+    for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+         cclass != cclasses.end(); ++cclass) {
+        classes->add(Element::create(*cclass));
+    }
+    map->set("client-classes", classes);
+
+    // Set option-data
+    ConstCfgOptionPtr opts = getCfgOption6();
+    map->set("option-data", opts->toElement());
+
+    return (map);
+}
+
 std::string
 std::string
 Host::toText() const {
 Host::toText() const {
     std::ostringstream s;
     std::ostringstream s;

+ 11 - 0
src/lib/dhcpsrv/host.h

@@ -8,6 +8,7 @@
 #define HOST_H
 #define HOST_H
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <dhcp/classify.h>
 #include <dhcp/classify.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
@@ -544,6 +545,16 @@ public:
         return (host_id_);
         return (host_id_);
     }
     }
 
 
+    /// @brief Unparses (converts to Element representation) IPv4 host
+    ///
+    /// @return Element representation of the host
+    isc::data::ElementPtr toElement4() const;
+
+    /// @brief Unparses (converts to Element representation) IPv4 host
+    ///
+    /// @return Element representation of the host
+    isc::data::ElementPtr toElement6() const;
+
 private:
 private:
 
 
     /// @brief Adds new client class for DHCPv4 or DHCPv6.
     /// @brief Adds new client class for DHCPv4 or DHCPv6.

+ 36 - 2
src/lib/dhcpsrv/host_mgr.cc

@@ -216,11 +216,45 @@ HostMgr::get6(const SubnetID& subnet_id,
 void
 void
 HostMgr::add(const HostPtr& host) {
 HostMgr::add(const HostPtr& host) {
     if (!alternate_source_) {
     if (!alternate_source_) {
-        isc_throw(NoHostDataSourceManager, "unable to add new host because there is "
-                  "no alternate host data source present");
+        isc_throw(NoHostDataSourceManager, "Unable to add new host because there is "
+                  "no hosts-database configured.");
     }
     }
     alternate_source_->add(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 hosts-database configured.");
+    }
+
+    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 hosts-database configured.");
+    }
+
+    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::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 53 - 0
src/lib/dhcpsrv/host_mgr.h

@@ -269,6 +269,59 @@ public:
         return (alternate_source_);
         return (alternate_source_);
     }
     }
 
 
+    /// @brief Sets the alternate host data source.
+    ///
+    /// Note: This should be used only for testing. Do not use
+    /// in production. Normal control flow assumes that
+    /// HostMgr::create(...) is called and it instnatiates
+    /// appropriate host data source. However, some tests
+    /// (e.g. host_cmds) implement their own very simple
+    /// data source. It's not production ready by any means,
+    /// so it does not belong in host_data_source_factory.cc.
+    /// The testing nature of this method is reflected in its name.
+    ///
+    /// @param source new source to be set (may be NULL)
+    void setTestHostDataSource(const HostDataSourcePtr& source) {
+        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:
 private:
 
 
     /// @brief Private default constructor.
     /// @brief Private default constructor.

+ 154 - 3
src/lib/dhcpsrv/mysql_host_data_source.cc

@@ -295,11 +295,11 @@ public:
             // The address in the Host structure is an IOAddress object.  Convert
             // The address in the Host structure is an IOAddress object.  Convert
             // this to an integer for storage.
             // this to an integer for storage.
             ipv4_address_ = host->getIPv4Reservation().toUint32();
             ipv4_address_ = host->getIPv4Reservation().toUint32();
+            ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
             bind_[5].buffer_type = MYSQL_TYPE_LONG;
             bind_[5].buffer_type = MYSQL_TYPE_LONG;
             bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
             bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
             bind_[5].is_unsigned = MLM_TRUE;
             bind_[5].is_unsigned = MLM_TRUE;
-            // bind_[5].is_null = &MLM_FALSE; // commented out for performance
-                                                      // reasons, see memset() above
+            bind_[5].is_null = &ipv4_address_null_;
 
 
             // hostname : VARCHAR(255) NULL
             // hostname : VARCHAR(255) NULL
             strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
             strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
@@ -1763,6 +1763,9 @@ public:
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V4_OPTION,       // Insert DHCPv4 option
         INSERT_V4_OPTION,       // Insert DHCPv4 option
         INSERT_V6_OPTION,       // Insert DHCPv6 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
         NUM_STATEMENTS          // Number of statements
     };
     };
 
 
@@ -1792,6 +1795,15 @@ public:
     void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
     void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
                       std::vector<MYSQL_BIND>& bind);
                       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.
     /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
     ///
     ///
     /// @param resv IPv6 Reservation to be added
     /// @param resv IPv6 Reservation to be added
@@ -2101,7 +2113,20 @@ TaggedStatementArray tagged_statements = { {
     {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
     {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
          "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
          "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
             "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
             "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::
 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
 void
 MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
 MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
                                  const HostID& id) {
                                  const HostID& id) {
@@ -2412,6 +2456,113 @@ MySqlHostDataSource::add(const HostPtr& host) {
     transaction.commit();
     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
 ConstHostCollection
 MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
 MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const {
                             const DuidPtr& duid) const {

+ 34 - 0
src/lib/dhcpsrv/mysql_host_data_source.h

@@ -220,6 +220,40 @@ public:
     /// @param host Pointer to the new @c Host object being added.
     /// @param host Pointer to the new @c Host object being added.
     virtual void add(const HostPtr& host);
     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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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
     /// @brief Return backend type
     ///
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
     /// 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.
     /// DHCPv6 options.
     ///
     ///
     /// The following are the basic functions of this class:
     /// 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,
     /// - set DHCP options specific column names,
     /// - create instances of options retrieved from the database.
     /// - create instances of options retrieved from the database.
     ///
     ///
@@ -826,8 +826,6 @@ public:
     /// @brief Creates IPv6 reservation from the data contained in the
     /// @brief Creates IPv6 reservation from the data contained in the
     /// currently processed row.
     /// currently processed row.
     ///
     ///
-    /// Called after the MYSQL_BIND array created by createBindForReceive().
-    ///
     /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
     /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
     IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
     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
 /// This class is only used to insert IPv6 reservations into the
 /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
 /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
@@ -1180,6 +1178,9 @@ public:
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V4_HOST_OPTION,  // Insert DHCPv4 option
         INSERT_V4_HOST_OPTION,  // Insert DHCPv4 option
         INSERT_V6_HOST_OPTION,  // Insert DHCPv6 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
         NUM_STATEMENTS          // Number of statements
     };
     };
 
 
@@ -1202,8 +1203,7 @@ public:
     /// @brief Executes statements which insert a row into one of the tables.
     /// @brief Executes statements which insert a row into one of the tables.
     ///
     ///
     /// @param stindex Index of a statement being executed.
     /// @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
     /// @param return_last_id flag indicating whether or not the insert
     /// returns the primary key of from the row inserted via " RETURNING
     /// returns the primary key of from the row inserted via " RETURNING
     /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
     /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
@@ -1219,6 +1219,14 @@ public:
                           PsqlBindArrayPtr& bind,
                           PsqlBindArrayPtr& bind,
                           const bool return_last_id = false);
                           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.
     /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
     ///
     ///
     /// @param resv IPv6 Reservation to be added
     /// @param resv IPv6 Reservation to be added
@@ -1244,7 +1252,8 @@ public:
     /// @param stindex Index of a statement being executed.
     /// @param stindex Index of a statement being executed.
     /// @param options_cfg An object holding a collection of options to be
     /// @param options_cfg An object holding a collection of options to be
     /// inserted into the database.
     /// 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,
     void addOptions(const StatementIndex& stindex,
                     const ConstCfgOptionPtr& options_cfg,
                     const ConstCfgOptionPtr& options_cfg,
                     const uint64_t host_id);
                     const uint64_t host_id);
@@ -1260,7 +1269,7 @@ public:
     /// @ref Host objects depends on the type of the exchange object.
     /// @ref Host objects depends on the type of the exchange object.
     ///
     ///
     /// @param stindex Statement index.
     /// @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
     /// @param exchange Pointer to the exchange object used for the
     /// particular query.
     /// particular query.
     /// @param [out] result Reference to the collection of hosts returned.
     /// @param [out] result Reference to the collection of hosts returned.
@@ -1334,7 +1343,7 @@ public:
     /// or dhcp6_options table.
     /// or dhcp6_options table.
     boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
     boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
 
 
-    /// @brief MySQL connection
+    /// @brief PgSQL connection
     PgSqlConnection conn_;
     PgSqlConnection conn_;
 
 
     /// @brief Indicates if the database is opened in read only mode.
     /// @brief Indicates if the database is opened in read only mode.
@@ -1508,7 +1517,7 @@ TaggedStatementArray tagged_statements = { {
     },
     },
 
 
     // PgSqlHostDataSourceImpl::GET_VERSION
     // PgSqlHostDataSourceImpl::GET_VERSION
-    // Retrieves MySQL schema version.
+    // Retrieves PgSQL schema version.
     {0,
     {0,
      { OID_NONE },
      { OID_NONE },
      "get_version",
      "get_version",
@@ -1561,6 +1570,34 @@ TaggedStatementArray tagged_statements = { {
      "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
      "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
      "  persistent, host_id, scope_id) "
      "  persistent, host_id, scope_id) "
      "VALUES ($1, $2, $3, $4, $5, $6, 3)"
      "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
 void
 PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
 PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
                                  const HostID& id) {
                                  const HostID& id) {
@@ -1787,7 +1851,7 @@ PgSqlHostDataSource::add(const HostPtr& host) {
     // the PgSqlTransaction class.
     // the PgSqlTransaction class.
     PgSqlTransaction transaction(impl_->conn_);
     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);
     PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host);
 
 
     // ... and insert the host.
     // ... and insert the host.
@@ -1821,6 +1885,71 @@ PgSqlHostDataSource::add(const HostPtr& host) {
     transaction.commit();
     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
 ConstHostCollection
 PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
 PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const {
                             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)
     /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
     if (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");
                   " hwaddr and duid, only one of them is allowed");
     }
     }
     if (!hwaddr && !duid) {
     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");
                   "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)
     /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
     if (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");
                   " hwaddr and duid, only one of them is allowed");
     }
     }
     if (!hwaddr && !duid) {
     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");
                   "neither hwaddr or duid specified, one of them is required");
     }
     }
 
 

+ 34 - 0
src/lib/dhcpsrv/pgsql_host_data_source.h

@@ -251,6 +251,40 @@ public:
     /// violation
     /// violation
     virtual void add(const HostPtr& host);
     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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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 if the host was not there.
+    /// @throw various exceptions in case of errors
+    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
     /// @brief Return backend type
     ///
     ///
     /// Returns the type of database as the string "postgresql".  This is
     /// Returns the type of database as the string "postgresql".  This is

+ 192 - 0
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -1463,6 +1463,198 @@ GenericHostDataSourceTest::testMessageFields4() {
     ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
     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);
+}
+
+// Test checks when a IPv4 host with options is deleted that the options are
+// deleted as well.
+void GenericHostDataSourceTest::testDeleteById4Options() {
+    // 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);
+    // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+    ASSERT_NO_THROW(addTestOptions(host1, true, DHCP4_ONLY));
+    // Insert host and the options into respective tables.
+
+    SubnetID subnet1 = host1->getIPv4SubnetID();
+
+    // ... and add it to the data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+
+    // There must be some options
+    EXPECT_NE(0, countDBOptions4());
+
+    // 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);
+
+    // Check the options are indeed gone.
+    EXPECT_EQ(0, countDBOptions4());
+}
+
+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);
+}
+
+void GenericHostDataSourceTest::testDeleteById6Options() {
+    // 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();
+    ASSERT_NO_THROW(addTestOptions(host1, true, DHCP6_ONLY));
+
+    // ... and add it to the data source.
+    ASSERT_NO_THROW(hdsptr_->add(host1));
+
+    // Check that the options are stored...
+    EXPECT_NE(0, countDBOptions6());
+
+    // ... and so are v6 reservations.
+    EXPECT_NE(0, countDBReservations6());
+
+    // 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);
+
+    // Check the options are indeed gone.
+    EXPECT_EQ(0, countDBOptions6());
+
+    // Check the options are indeed gone.
+    EXPECT_EQ(0, countDBReservations6());
+}
+
 }; // namespace test
 }; // namespace test
 }; // namespace dhcp
 }; // namespace dhcp
 }; // namespace isc
 }; // namespace isc

+ 56 - 1
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h

@@ -292,6 +292,39 @@ public:
         return (desc);
         return (desc);
     }
     }
 
 
+    /// @brief Returns number of entries in the v4 options table.
+    ///
+    /// This utility method is expected to be implemented by specific backends.
+    /// The code here is just a boilerplate for backends that do not store
+    /// host options in a table.
+    ///
+    /// @param number of existing entries in options table
+    virtual int countDBOptions4() {
+        return (-1);
+    }
+
+    /// @brief Returns number of entries in the v6 options table.
+    ///
+    /// This utility method is expected to be implemented by specific backends.
+    /// The code here is just a boilerplate for backends that do not store
+    /// host options in a table.
+    ///
+    /// @param number of existing entries in options table
+    virtual int countDBOptions6() {
+        return (-1);
+    }
+
+    /// @brief Returns number of entries in the v6 reservations table.
+    ///
+    /// This utility method is expected to be implemented by specific backends.
+    /// The code here is just a boilerplate for backends that do not store
+    /// v6 reservations in a table.
+    ///
+    /// @param number of existing entries in v6_reservations table
+    virtual int countDBReservations6() {
+        return (-1);
+    }
+
     /// @brief Creates an instance of the vendor option.
     /// @brief Creates an instance of the vendor option.
     ///
     ///
     /// @param universe V4 or V6.
     /// @param universe V4 or V6.
@@ -522,9 +555,31 @@ public:
     /// from a database for a host.
     /// from a database for a host.
     ///
     ///
     /// Uses gtest macros to report failures.
     /// Uses gtest macros to report failures.
-    ///
     void testMessageFields4();
     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(subnet4-id, id-type, id) also deletes options.
+    void testDeleteById4Options();
+
+    /// @brief Tests that delete(subnet6-id, identifier-type, identifier) works.
+    ///
+    /// Uses gtest macros to report failures.
+    void testDeleteById6();
+
+    /// @brief Tests that delete(subnet6-id, id-type, id) also deletes options.
+    ///
+    /// Uses gtest macros to report failures.
+    void testDeleteById6Options();
+
     /// @brief Returns DUID with identical content as specified HW address
     /// @brief Returns DUID with identical content as specified HW address
     ///
     ///
     /// This method does not have any sense in real life and is only useful
     /// This method does not have any sense in real life and is only useful

+ 71 - 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
 // 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,6 +86,49 @@ public:
         hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
         hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
     }
     }
 
 
+    /// @brief returns number of rows in a table
+    ///
+    /// Note: This method uses its own connection. It will not work if your test
+    /// uses transactions.
+    ///
+    /// @param name of the table
+    /// @return number of rows currently present in the table
+    int countRowsInTable(const std::string& table) {
+        string query = "SELECT * FROM " + table;
+
+        MySqlConnection::ParameterMap params;
+        params["name"] = "keatest";
+        params["user"] = "keatest";
+        params["password"] = "keatest";
+        MySqlConnection conn(params);
+        conn.openDatabase();
+        int status = mysql_query(conn.mysql_, query.c_str());
+        if (status !=0) {
+            isc_throw(DbOperationError, "Query failed: " << mysql_error(conn.mysql_));
+        }
+
+        MYSQL_RES * res = mysql_store_result(conn.mysql_);
+        int numrows = static_cast<int>(mysql_num_rows(res));
+        mysql_free_result(res);
+
+        return (numrows);
+    }
+
+    /// @brief Returns number of IPv4 options currently stored in DB.
+    virtual int countDBOptions4() {
+        return (countRowsInTable("dhcp4_options"));
+    }
+
+    /// @brief Returns number of IPv4 options currently stored in DB.
+    virtual int countDBOptions6() {
+        return (countRowsInTable("dhcp6_options"));
+    }
+
+    /// @brief Returns number of IPv6 reservations currently stored in DB.
+    virtual int countDBReservations6() {
+        return (countRowsInTable("ipv6_reservations"));
+    }
+
 };
 };
 
 
 /// @brief Check that database can be opened
 /// @brief Check that database can be opened
@@ -535,4 +578,31 @@ TEST_F(MySqlHostDataSourceTest, messageFields) {
     testMessageFields4();
     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(subnet4-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById4Options) {
+    testDeleteById4Options();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById6) {
+    testDeleteById6();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById6Options) {
+    testDeleteById6Options();
+}
+
 }; // Of anonymous namespace
 }; // Of anonymous namespace

+ 70 - 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
 // 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -87,6 +87,48 @@ public:
         hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
         hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
     }
     }
 
 
+    /// @brief returns number of rows in a table
+    ///
+    /// Note: This method uses its own connection. It will not work if your test
+    /// uses transactions.
+    ///
+    /// @param name of the table
+    /// @return number of rows currently present in the table
+    int countRowsInTable(const std::string& table) {
+        string query = "SELECT * FROM " + table;
+
+        PgSqlConnection::ParameterMap params;
+        params["name"] = "keatest";
+        params["user"] = "keatest";
+        params["password"] = "keatest";
+
+        PgSqlConnection conn(params);
+        conn.openDatabase();
+
+        PgSqlResult r(PQexec(conn, query.c_str()));
+        if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+            isc_throw(DbOperationError, "Query failed:" << PQerrorMessage(conn));
+        }
+
+        int numrows = PQntuples(r);
+        return (numrows);
+    }
+
+    /// @brief Returns number of IPv4 options in the DB table.
+    virtual int countDBOptions4() {
+        return (countRowsInTable("dhcp4_options"));
+    }
+
+    /// @brief Returns number of IPv4 options in the DB table.
+    virtual int countDBOptions6() {
+        return (countRowsInTable("dhcp6_options"));
+    }
+
+    /// @brief Returns number of IPv6 reservations in the DB table.
+    virtual int countDBReservations6() {
+        return (countRowsInTable("ipv6_reservations"));
+    }
+
 };
 };
 
 
 /// @brief Check that database can be opened
 /// @brief Check that database can be opened
@@ -493,4 +535,31 @@ TEST_F(PgSqlHostDataSourceTest, messageFields) {
     testMessageFields4();
     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(subnet4-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById4Options) {
+    testDeleteById4Options();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById6) {
+    testDeleteById6();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById6Options) {
+    testDeleteById6Options();
+}
+
 }; // Of anonymous namespace
 }; // Of anonymous namespace

+ 6 - 0
src/lib/hooks/hooks_log.cc

@@ -7,6 +7,7 @@
 /// Defines the logger used by the Hooks
 /// Defines the logger used by the Hooks
 
 
 #include <hooks/hooks_log.h>
 #include <hooks/hooks_log.h>
+#include <log/macros.h>
 
 
 namespace isc {
 namespace isc {
 namespace hooks {
 namespace hooks {
@@ -15,6 +16,11 @@ isc::log::Logger hooks_logger("hooks");
 
 
 isc::log::Logger callouts_logger("callouts");
 isc::log::Logger callouts_logger("callouts");
 
 
+const int HOOKS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+const int HOOKS_DBG_CALLS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+const int HOOKS_DBG_EXTENDED_CALLS = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+
 } // namespace hooks
 } // namespace hooks
 } // namespace isc
 } // namespace isc
 
 

+ 3 - 3
src/lib/hooks/hooks_log.h

@@ -19,14 +19,14 @@ namespace hooks {
 /// Note that higher numbers equate to more verbose (and detailed) output.
 /// Note that higher numbers equate to more verbose (and detailed) output.
 
 
 // The first level traces normal operations,
 // The first level traces normal operations,
-const int HOOKS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int HOOKS_DBG_TRACE;
 
 
 // The next level traces each call to hook code.
 // The next level traces each call to hook code.
-const int HOOKS_DBG_CALLS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int HOOKS_DBG_CALLS;
 
 
 // Additional information on the calls.  Report each call to a callout (even
 // Additional information on the calls.  Report each call to a callout (even
 // if there are multiple callouts on a hook) and each status return.
 // if there are multiple callouts on a hook) and each status return.
-const int HOOKS_DBG_EXTENDED_CALLS = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+extern const int HOOKS_DBG_EXTENDED_CALLS;
 
 
 
 
 /// @brief Hooks Logger
 /// @brief Hooks Logger

+ 16 - 2
src/lib/hooks/server_hooks.cc

@@ -46,10 +46,24 @@ ServerHooks::registerHook(const string& name) {
         hooks_.insert(make_pair(name, index));
         hooks_.insert(make_pair(name, index));
 
 
     if (!result.second) {
     if (!result.second) {
+
+        // There's a problem with hook libraries that need to be linked with
+        // libdhcpsrv. For example host_cmds hook library requires host
+        // parser, so it needs to be linked with libdhcpsrv. However, when
+        // unit-tests are started, the hook points are not registered.
+        // When the library is loaded new hook points are registered.
+        // This causes issues in the hooks framework, especially when
+        // LibraryManager::unloadLibrary() iterates through all hooks
+        // and then calls deregisterAllCallouts. This method gets
+        // hook_index that is greater than number of elements in
+        // hook_vector_ and then we have a read past the array boundary.
+        /// @todo: See ticket 5251 and 5208 for details.
+        return (getIndex(name));
+
         // New element was not inserted because an element with the same name
         // New element was not inserted because an element with the same name
         // already existed.
         // already existed.
-        isc_throw(DuplicateHook, "hook with name " << name <<
-                  " is already registered");
+        //isc_throw(DuplicateHook, "hook with name " << name <<
+        //         " is already registered");
     }
     }
 
 
     // Element was inserted, so add to the inverse hooks collection.
     // Element was inserted, so add to the inverse hooks collection.

+ 8 - 3
src/lib/hooks/tests/hooks_manager_unittest.cc

@@ -431,7 +431,7 @@ TEST_F(HooksManagerTest, PrePostCalloutShared) {
 
 
     HooksManager::callCallouts(hookpt_two_index_, *handle);
     HooksManager::callCallouts(hookpt_two_index_, *handle);
 
 
-    // Expect same value i.e. 1027 * 2 
+    // Expect same value i.e. 1027 * 2
     result = 0;
     result = 0;
     handle->getArgument("result", result);
     handle->getArgument("result", result);
     EXPECT_EQ(2054, result);
     EXPECT_EQ(2054, result);
@@ -564,8 +564,13 @@ TEST_F(HooksManagerTest, RegisterHooks) {
     EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
     EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
     EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
     EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
     EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
     EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
-    EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
-                 DuplicateHook);
+
+
+    // The code used to throw, but it now allows to register the same
+    // hook several times. It simply returns existing index.
+    //EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+    //             DuplicateHook);
+    EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
 
 
     // ... an check the hooks are as we expect.
     // ... an check the hooks are as we expect.
     EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
     EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());

+ 26 - 2
src/lib/hooks/tests/server_hooks_unittest.cc

@@ -54,8 +54,9 @@ TEST(ServerHooksTest, RegisterHooks) {
 }
 }
 
 
 // Check that duplicate names cannot be registered.
 // Check that duplicate names cannot be registered.
-
-TEST(ServerHooksTest, DuplicateHooks) {
+// This test has been updated. See #5251 for details. The old
+// code is retained in case we decide to get back to it.
+TEST(ServerHooksTest, DISABLED_OldDuplicateHooks) {
     ServerHooks& hooks = ServerHooks::getServerHooks();
     ServerHooks& hooks = ServerHooks::getServerHooks();
     hooks.reset();
     hooks.reset();
 
 
@@ -68,6 +69,29 @@ TEST(ServerHooksTest, DuplicateHooks) {
     EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
     EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
 }
 }
 
 
+// Check that duplicate names are handled properly. The code used to throw,
+// but it now returns the existing index. See #5251 for details.
+TEST(ServerHooksTest, NewDuplicateHooks) {
+    ServerHooks& hooks = ServerHooks::getServerHooks();
+    hooks.reset();
+
+    int index = hooks.getIndex("context_create");
+
+    // Ensure we can duplicate one of the existing names.
+    // Instead of throwing, we just check that a resonable
+    // index has been returned.
+    EXPECT_EQ(index, hooks.registerHook("context_create"));
+
+    // Check that mutiple attempts to register the same hook will return
+    // existing index.
+    int gamma = hooks.registerHook("gamma");
+    EXPECT_EQ(2, gamma);
+    EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+    EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+    EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+    EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+}
+
 // Checks that we can get the name of the hooks.
 // Checks that we can get the name of the hooks.
 
 
 TEST(ServerHooksTest, GetHookNames) {
 TEST(ServerHooksTest, GetHookNames) {

+ 15 - 0
src/share/database/scripts/mysql/dhcpdb_create.mysql

@@ -484,6 +484,21 @@ SET version = '5', minor = '0';
 INSERT INTO host_identifier_type VALUES (3, 'client-id');
 INSERT INTO host_identifier_type VALUES (3, 'client-id');
 INSERT INTO host_identifier_type VALUES (4, 'flex-id');
 INSERT INTO host_identifier_type VALUES (4, 'flex-id');
 
 
+# Recreate the trigger removing dependent host entries.
+DROP TRIGGER host_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+DELETE FROM dhcp4_options WHERE dhcp4_options.host_id = OLD.host_id;
+DELETE FROM dhcp6_options WHERE dhcp6_options.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
+
 # Update the schema version number
 # Update the schema version number
 UPDATE schema_version
 UPDATE schema_version
 SET version = '5', minor = '1';
 SET version = '5', minor = '1';

+ 14 - 0
src/share/database/scripts/mysql/upgrade_5.0_to_5.1.sh.in

@@ -21,6 +21,20 @@ mysql "$@" <<EOF
 INSERT INTO host_identifier_type VALUES (3, 'client-id');
 INSERT INTO host_identifier_type VALUES (3, 'client-id');
 INSERT INTO host_identifier_type VALUES (4, 'flex-id');
 INSERT INTO host_identifier_type VALUES (4, 'flex-id');
 
 
+# Recreate the trigger removing dependent host entries.
+DROP TRIGGER host_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+DELETE FROM dhcp4_options WHERE dhcp4_options.host_id = OLD.host_id;
+DELETE FROM dhcp6_options WHERE dhcp6_options.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
 # Update the schema version number
 # Update the schema version number
 UPDATE schema_version
 UPDATE schema_version
 SET version = '5', minor = '1';
 SET version = '5', minor = '1';