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")
 
 # 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
 AC_MSG_CHECKING([for std::is_base_of])

+ 385 - 65
doc/guide/hooks.xml

@@ -207,7 +207,7 @@
             <row>
               <entry>Flexible Identifier</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
               that include addresses, prefixes, options, client classes and
               other features. The reservation can be based on hardware address,
@@ -219,11 +219,28 @@
               client. Those scenarios are addressed by the Flexible Identifiers
               hook application. It allows defining an expression, similar to
               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>
             </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>
           </tgroup>
           </table>
@@ -547,7 +564,7 @@ link address: 3001::1, hop count: 1, identified by remote-id:
         </section>
       </section>
 
-      <section>
+      <section id="flex-id">
         <title>flex_id: Flexible Identifiers for Host Reservations</title>
         <para>
           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
           even a combination of several options and fields to uniquely identify
           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>
 "Dhcp6": { <userinput>
     "hooks-libraries": [
@@ -592,26 +612,26 @@ link address: 3001::1, hop count: 1, identified by remote-id:
     ] </userinput>
 }
 </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>
 "Dhcp6": {
     "subnet6": [{ ..., // subnet definition starts here
@@ -631,27 +651,327 @@ link address: 3001::1, hop count: 1, identified by remote-id:
     ]
 }
 </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>

+ 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.
     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
     ///
     /// 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
 CfgHosts::toElement() const {
     uint16_t family = CfgMgr::instance().getFamily();
@@ -708,60 +735,12 @@ CfgHosts::toElement4() const {
     const HostContainerIndex0& idx = hosts_.get<0>();
     for (HostContainerIndex0::const_iterator host = idx.begin();
          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();
-        // 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);
     }
     return (result.externalize());
@@ -774,62 +753,12 @@ CfgHosts::toElement6() const {
     const HostContainerIndex0& idx = hosts_.get<0>();
     for (HostContainerIndex0::const_iterator host = idx.begin();
          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();
-        // 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);
     }
     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.
     virtual void add(const HostPtr& host);
 
+    /// @brief Attempts to delete a host by address.
+    ///
+    /// This method supports both v4 and v6.
+    /// @todo: Not implemented.
+    ///
+    /// @param subnet_id subnet identifier.
+    /// @param addr specified address.
+    virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+    /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type)
+    ///
+    /// This method supports v4 only.
+    /// @todo: Not implemented.
+    ///
+    /// @param subnet_id IPv4 Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a beginning of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    /// @return true if deletion was successful, false otherwise.
+    virtual bool del4(const SubnetID& subnet_id,
+                      const Host::IdentifierType& identifier_type,
+                      const uint8_t* identifier_begin, const size_t identifier_len);
+
+    /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type)
+    ///
+    /// This method supports v6 only.
+    /// @todo: Not implemented.
+    ///
+    /// @param subnet_id IPv6 Subnet identifier.
+    /// @param identifier_type Identifier type.
+    /// @param identifier_begin Pointer to a beginning of a buffer containing
+    /// an identifier.
+    /// @param identifier_len Identifier length.
+    /// @return true if deletion was successful, false otherwise.
+    virtual bool del6(const SubnetID& subnet_id,
+                      const Host::IdentifierType& identifier_type,
+                      const uint8_t* identifier_begin, const size_t identifier_len);
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

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

@@ -9,9 +9,13 @@
 #include <dhcpsrv/host.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
+#include <asiolink/io_address.h>
 #include <exceptions/exceptions.h>
 #include <sstream>
 
+using namespace isc::data;
+using namespace isc::asiolink;
+
 namespace isc {
 namespace dhcp {
 
@@ -400,6 +404,122 @@ Host::setBootFileName(const std::string& 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
 Host::toText() const {
     std::ostringstream s;

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

@@ -8,6 +8,7 @@
 #define HOST_H
 
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <dhcp/classify.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
@@ -544,6 +545,16 @@ public:
         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:
 
     /// @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
 HostMgr::add(const HostPtr& host) {
     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);
 }
 
+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 namespace

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

@@ -269,6 +269,59 @@ public:
         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:
 
     /// @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
             // this to an integer for storage.
             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 = reinterpret_cast<char*>(&ipv4_address_);
             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
             strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
@@ -1763,6 +1763,9 @@ public:
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V4_OPTION,       // Insert DHCPv4 option
         INSERT_V6_OPTION,       // Insert DHCPv6 option
+        DEL_HOST_ADDR4,         // Delete v4 host (subnet-id, addr4)
+        DEL_HOST_SUBID4_ID,     // Delete v4 host (subnet-id, ident.type, identifier)
+        DEL_HOST_SUBID6_ID,     // Delete v6 host (subnet-id, ident.type, identifier)
         NUM_STATEMENTS          // Number of statements
     };
 
@@ -1792,6 +1795,15 @@ public:
     void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
                       std::vector<MYSQL_BIND>& bind);
 
+    /// @brief Executes statements that delete records.
+    ///
+    /// @param stindex Index of a statement being executed.
+    /// @param bind Vector of MYSQL_BIND objects to be used when making the
+    /// query.
+    /// @return true if any records were deleted, false otherwise
+    bool
+    delStatement(StatementIndex stindex, MYSQL_BIND* bind);
+
     /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
     ///
     /// @param resv IPv6 Reservation to be added
@@ -2101,7 +2113,20 @@ TaggedStatementArray tagged_statements = { {
     {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
          "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
             "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
-         " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}}
+         " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+    {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
+     "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+
+    {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+     "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
+     "AND dhcp_identifier = ?"},
+
+    {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+     "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
+     "AND dhcp_identifier = ?"}
+
+    }
 };
 
 MySqlHostDataSourceImpl::
@@ -2186,6 +2211,25 @@ MySqlHostDataSourceImpl::addStatement(StatementIndex stindex,
     }
 }
 
+bool
+MySqlHostDataSourceImpl::delStatement(StatementIndex stindex,
+                                      MYSQL_BIND* bind) {
+    // Bind the parameters to the statement
+    int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
+    checkError(status, stindex, "unable to bind parameters");
+
+    // Execute the statement
+    status = mysql_stmt_execute(conn_.statements_[stindex]);
+
+    if (status != 0) {
+        checkError(status, stindex, "unable to execute");
+    }
+
+    // Let's check how many hosts were deleted.
+    my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
+    return (numrows != 0);
+}
+
 void
 MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
                                  const HostID& id) {
@@ -2412,6 +2456,113 @@ MySqlHostDataSource::add(const HostPtr& host) {
     transaction.commit();
 }
 
+bool
+MySqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+    // If operating in read-only mode, throw exception.
+    impl_->checkReadOnly();
+
+    if (addr.isV4()) {
+        // Set up the WHERE clause value
+        MYSQL_BIND inbind[2];
+
+        uint32_t subnet = subnet_id;
+        memset(inbind, 0, sizeof(inbind));
+        inbind[0].buffer_type = MYSQL_TYPE_LONG;
+        inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+        inbind[0].is_unsigned = MLM_TRUE;
+
+        uint32_t addr4 = addr.toUint32();
+        inbind[1].buffer_type = MYSQL_TYPE_LONG;
+        inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+        inbind[1].is_unsigned = MLM_TRUE;
+
+        ConstHostCollection collection;
+        return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
+    }
+
+    // v6
+    ConstHostPtr host = get6(subnet_id, addr);
+    if (!host) {
+        return (false);
+    }
+
+    // Ok, there is a host. Let's delete it.
+    return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
+                host->getIdentifier().size());
+}
+
+bool
+MySqlHostDataSource::del4(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin, const size_t identifier_len) {
+    // If operating in read-only mode, throw exception.
+    impl_->checkReadOnly();
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[3];
+
+    // subnet-id
+    memset(inbind, 0, sizeof(inbind));
+    uint32_t subnet = static_cast<uint32_t>(subnet_id);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+    inbind[0].is_unsigned = MLM_TRUE;
+
+    // identifier type
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    // identifier value
+    std::vector<char> identifier_vec(identifier_begin,
+                                     identifier_begin + identifier_len);
+    unsigned long length = identifier_vec.size();
+    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[2].buffer = &identifier_vec[0];
+    inbind[2].buffer_length = length;
+    inbind[2].length = &length;
+
+    ConstHostCollection collection;
+    return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
+}
+
+bool
+MySqlHostDataSource::del6(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin, const size_t identifier_len) {
+    // If operating in read-only mode, throw exception.
+    impl_->checkReadOnly();
+
+    // Set up the WHERE clause value
+    MYSQL_BIND inbind[3];
+
+    // subnet-id
+    memset(inbind, 0, sizeof(inbind));
+    uint32_t subnet = static_cast<uint32_t>(subnet_id);
+    inbind[0].buffer_type = MYSQL_TYPE_LONG;
+    inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+    inbind[0].is_unsigned = MLM_TRUE;
+
+    // identifier type
+    char identifier_type_copy = static_cast<char>(identifier_type);
+    inbind[1].buffer_type = MYSQL_TYPE_TINY;
+    inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+    inbind[1].is_unsigned = MLM_TRUE;
+
+    // identifier value
+    std::vector<char> identifier_vec(identifier_begin,
+                                     identifier_begin + identifier_len);
+    unsigned long length = identifier_vec.size();
+    inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+    inbind[2].buffer = &identifier_vec[0];
+    inbind[2].buffer_length = length;
+    inbind[2].length = &length;
+
+    ConstHostCollection collection;
+    return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
+}
+
 ConstHostCollection
 MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const {

+ 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.
     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
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 144 - 15
src/lib/dhcpsrv/pgsql_host_data_source.cc

@@ -392,7 +392,7 @@ private:
     /// DHCPv6 options.
     ///
     /// The following are the basic functions of this class:
-    /// - bind class members to specific columns in MySQL binding tables,
+    /// - bind class members to specific columns in PgSQL binding tables,
     /// - set DHCP options specific column names,
     /// - create instances of options retrieved from the database.
     ///
@@ -826,8 +826,6 @@ public:
     /// @brief Creates IPv6 reservation from the data contained in the
     /// currently processed row.
     ///
-    /// Called after the MYSQL_BIND array created by createBindForReceive().
-    ///
     /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
     IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
 
@@ -936,7 +934,7 @@ private:
 
 };
 
-/// @brief This class is used for storing IPv6 reservations in a MySQL database.
+/// @brief This class is used for storing IPv6 reservations in a PgSQL database.
 ///
 /// This class is only used to insert IPv6 reservations into the
 /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
@@ -1180,6 +1178,9 @@ public:
         INSERT_V6_RESRV,        // Insert v6 reservation
         INSERT_V4_HOST_OPTION,  // Insert DHCPv4 option
         INSERT_V6_HOST_OPTION,  // Insert DHCPv6 option
+        DEL_HOST_ADDR4,         // Delete v4 host (subnet-id, addr4)
+        DEL_HOST_SUBID4_ID,     // Delete v4 host (subnet-id, ident.type, identifier)
+        DEL_HOST_SUBID6_ID,     // Delete v6 host (subnet-id, ident.type, identifier)
         NUM_STATEMENTS          // Number of statements
     };
 
@@ -1202,8 +1203,7 @@ public:
     /// @brief Executes statements which insert a row into one of the tables.
     ///
     /// @param stindex Index of a statement being executed.
-    /// @param bind Vector of MYSQL_BIND objects to be used when making the
-    /// query.
+    /// @param bind Vector of PgsqlBindArray objects to be used for the query
     /// @param return_last_id flag indicating whether or not the insert
     /// returns the primary key of from the row inserted via " RETURNING
     /// <primary key> as pid" clause on the INSERT statement.  The RETURNING
@@ -1219,6 +1219,14 @@ public:
                           PsqlBindArrayPtr& bind,
                           const bool return_last_id = false);
 
+    /// @brief Executes statements that delete records.
+    ///
+    /// @param stindex Index of a statement being executed.
+    /// @param bind pointer to PsqlBindArray objects to be used for the query
+    /// @return true if any records were deleted, false otherwise
+    bool delStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
+                      PsqlBindArrayPtr& bind);
+
     /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
     ///
     /// @param resv IPv6 Reservation to be added
@@ -1244,7 +1252,8 @@ public:
     /// @param stindex Index of a statement being executed.
     /// @param options_cfg An object holding a collection of options to be
     /// inserted into the database.
-    /// @param host_id Host identifier retrieved using @c mysql_insert_id.
+    /// @param host_id Host identifier retrieved using getColumnValue
+    ///                in addStatement method
     void addOptions(const StatementIndex& stindex,
                     const ConstCfgOptionPtr& options_cfg,
                     const uint64_t host_id);
@@ -1260,7 +1269,7 @@ public:
     /// @ref Host objects depends on the type of the exchange object.
     ///
     /// @param stindex Statement index.
-    /// @param bind Pointer to an array of MySQL bindings.
+    /// @param bind Pointer to an array of PgSQL bindings.
     /// @param exchange Pointer to the exchange object used for the
     /// particular query.
     /// @param [out] result Reference to the collection of hosts returned.
@@ -1334,7 +1343,7 @@ public:
     /// or dhcp6_options table.
     boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
 
-    /// @brief MySQL connection
+    /// @brief PgSQL connection
     PgSqlConnection conn_;
 
     /// @brief Indicates if the database is opened in read only mode.
@@ -1508,7 +1517,7 @@ TaggedStatementArray tagged_statements = { {
     },
 
     // PgSqlHostDataSourceImpl::GET_VERSION
-    // Retrieves MySQL schema version.
+    // Retrieves PgSQL schema version.
     {0,
      { OID_NONE },
      "get_version",
@@ -1561,6 +1570,34 @@ TaggedStatementArray tagged_statements = { {
      "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
      "  persistent, host_id, scope_id) "
      "VALUES ($1, $2, $3, $4, $5, $6, 3)"
+    },
+
+    // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
+    // Deletes a v4 host that matches (subnet-id, addr4)
+    {2,
+     { OID_INT4, OID_INT8 },
+     "del_host_addr4",
+     "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
+    },
+
+    // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
+    // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
+    {3,
+     { OID_INT4, OID_INT2, OID_BYTEA },
+     "del_host_subid4_id",
+     "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
+     "AND dhcp_identifier_type = $2 "
+     "AND dhcp_identifier = $3"
+    },
+
+    // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
+    // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
+    {3,
+     { OID_INT4, OID_INT2, OID_BYTEA },
+     "del_host_subid6_id",
+     "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
+     "AND dhcp_identifier_type = $2 "
+     "AND dhcp_identifier = $3"
     }
 }
 };
@@ -1634,6 +1671,33 @@ PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex,
 
 }
 
+bool
+PgSqlHostDataSourceImpl::delStatement(StatementIndex stindex,
+                                      PsqlBindArrayPtr& bind_array) {
+    PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+                                 tagged_statements[stindex].nbparams,
+                                 &bind_array->values_[0],
+                                 &bind_array->lengths_[0],
+                                 &bind_array->formats_[0], 0));
+
+    int s = PQresultStatus(r);
+
+    if (s != PGRES_COMMAND_OK) {
+        // Connection determines if the error is fatal or not, and
+        // throws the appropriate exception
+        conn_.checkStatementError(r, tagged_statements[stindex]);
+    }
+
+    // Now check how many rows (hosts) were deleted. This should be either
+    // "0" or "1".
+    char* rows_deleted = PQcmdTuples(r);
+    if (!rows_deleted) {
+        isc_throw(DbOperationError,
+                  "Could not retrieve the number of deleted rows.");
+    }
+    return (rows_deleted[0] != '0');
+}
+
 void
 PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
                                  const HostID& id) {
@@ -1787,7 +1851,7 @@ PgSqlHostDataSource::add(const HostPtr& host) {
     // the PgSqlTransaction class.
     PgSqlTransaction transaction(impl_->conn_);
 
-    // Create the MYSQL_BIND array for the host
+    // Create the PgSQL Bind array for the host
     PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host);
 
     // ... and insert the host.
@@ -1821,6 +1885,71 @@ PgSqlHostDataSource::add(const HostPtr& host) {
     transaction.commit();
 }
 
+bool
+PgSqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+    // If operating in read-only mode, throw exception.
+    impl_->checkReadOnly();
+
+    if (addr.isV4()) {
+        PsqlBindArrayPtr bind_array(new PsqlBindArray());
+        bind_array->add(subnet_id);
+        bind_array->add(addr);
+        return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
+                                    bind_array));
+    }
+
+    ConstHostPtr host = get6(subnet_id, addr);
+    if (!host) {
+        return (false);
+    }
+
+    return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
+                host->getIdentifier().size());
+}
+
+bool
+PgSqlHostDataSource::del4(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) {
+
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    // Subnet-id
+    bind_array->add(subnet_id);
+
+    // identifier-type
+    bind_array->add(static_cast<uint8_t>(identifier_type));
+
+    // identifier
+    bind_array->add(identifier_begin, identifier_len);
+
+    return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+                                bind_array));
+
+}
+
+bool
+PgSqlHostDataSource::del6(const SubnetID& subnet_id,
+                          const Host::IdentifierType& identifier_type,
+                          const uint8_t* identifier_begin,
+                          const size_t identifier_len) {
+    PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+    // Subnet-id
+    bind_array->add(subnet_id);
+
+    // identifier-type
+    bind_array->add(static_cast<uint8_t>(identifier_type));
+
+    // identifier
+    bind_array->add(identifier_begin, identifier_len);
+
+    return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+                                bind_array));
+
+}
+
 ConstHostCollection
 PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const {
@@ -1880,11 +2009,11 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
 
     /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
     if (hwaddr && duid) {
-        isc_throw(BadValue, "MySQL host data source get4() called with both"
+        isc_throw(BadValue, "PgSQL host data source get4() called with both"
                   " hwaddr and duid, only one of them is allowed");
     }
     if (!hwaddr && !duid) {
-        isc_throw(BadValue, "MySQL host data source get4() called with "
+        isc_throw(BadValue, "PgSQL host data source get4() called with "
                   "neither hwaddr or duid specified, one of them is required");
     }
 
@@ -1949,11 +2078,11 @@ PgSqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
 
     /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
     if (hwaddr && duid) {
-        isc_throw(BadValue, "MySQL host data source get6() called with both"
+        isc_throw(BadValue, "PgSQL host data source get6() called with both"
                   " hwaddr and duid, only one of them is allowed");
     }
     if (!hwaddr && !duid) {
-        isc_throw(BadValue, "MySQL host data source get6() called with "
+        isc_throw(BadValue, "PgSQL host data source get6() called with "
                   "neither hwaddr or duid specified, one of them is required");
     }
 

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

@@ -251,6 +251,40 @@ public:
     /// violation
     virtual void add(const HostPtr& host);
 
+    /// @brief Attempts to delete a host by (subnet-id, address)
+    ///
+    /// This method supports both v4 and v6.
+    ///
+    /// @param subnet_id subnet identfier.
+    /// @param addr specified address.
+    /// @return true if deletion was successful, false 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
     ///
     /// 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));
 }
 
+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 dhcp
 }; // namespace isc

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

@@ -292,6 +292,39 @@ public:
         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.
     ///
     /// @param universe V4 or V6.
@@ -522,9 +555,31 @@ public:
     /// from a database for a host.
     ///
     /// Uses gtest macros to report failures.
-    ///
     void testMessageFields4();
 
+    /// @brief Tests that delete(subnet-id, addr4) call works.
+    ///
+    /// Uses gtest macros to report failures.
+    void testDeleteByAddr4();
+
+    /// @brief Tests that delete(subnet4-id, identifier-type, identifier) works.
+    ///
+    /// Uses gtest macros to report failures.
+    void testDeleteById4();
+
+    /// @brief Tests that delete(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
     ///
     /// 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,6 +86,49 @@ public:
         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
@@ -535,4 +578,31 @@ TEST_F(MySqlHostDataSourceTest, messageFields) {
     testMessageFields4();
 }
 
+// Check that delete(subnet-id, addr4) works.
+TEST_F(MySqlHostDataSourceTest, deleteByAddr4) {
+    testDeleteByAddr4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById4) {
+    testDeleteById4();
+}
+
+// Check that delete(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

+ 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
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -87,6 +87,48 @@ public:
         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
@@ -493,4 +535,31 @@ TEST_F(PgSqlHostDataSourceTest, messageFields) {
     testMessageFields4();
 }
 
+// Check that delete(subnet-id, addr4) works.
+TEST_F(PgSqlHostDataSourceTest, deleteByAddr4) {
+    testDeleteByAddr4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById4) {
+    testDeleteById4();
+}
+
+// Check that delete(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

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

@@ -7,6 +7,7 @@
 /// Defines the logger used by the Hooks
 
 #include <hooks/hooks_log.h>
+#include <log/macros.h>
 
 namespace isc {
 namespace hooks {
@@ -15,6 +16,11 @@ isc::log::Logger hooks_logger("hooks");
 
 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 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.
 
 // 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.
-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
 // 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

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

@@ -46,10 +46,24 @@ ServerHooks::registerHook(const string& name) {
         hooks_.insert(make_pair(name, index));
 
     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
         // 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.

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

@@ -431,7 +431,7 @@ TEST_F(HooksManagerTest, PrePostCalloutShared) {
 
     HooksManager::callCallouts(hookpt_two_index_, *handle);
 
-    // Expect same value i.e. 1027 * 2 
+    // Expect same value i.e. 1027 * 2
     result = 0;
     handle->getArgument("result", result);
     EXPECT_EQ(2054, result);
@@ -564,8 +564,13 @@ TEST_F(HooksManagerTest, RegisterHooks) {
     EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
     EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
     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.
     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.
-
-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();
     hooks.reset();
 
@@ -68,6 +69,29 @@ TEST(ServerHooksTest, DuplicateHooks) {
     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.
 
 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 (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 schema_version
 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 (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 schema_version
 SET version = '5', minor = '1';