Browse Source

Merge branch 'master' into trac2198_2

Mukund Sivaraman 12 years ago
parent
commit
f8181bb722
75 changed files with 4400 additions and 1288 deletions
  1. 74 1
      ChangeLog
  2. 103 91
      configure.ac
  3. 0 23
      doc/devel/02-dhcp.dox
  4. 5 0
      doc/devel/mainpage.dox
  5. 89 10
      doc/guide/bind10-guide.xml
  6. 1 1
      examples/README
  7. 1 1
      src/bin/auth/auth_config.h
  8. 26 43
      src/bin/auth/auth_srv.cc
  9. 45 24
      src/bin/auth/auth_srv.h
  10. 21 15
      src/bin/auth/benchmarks/query_bench.cc
  11. 3 2
      src/bin/auth/command.cc
  12. 4 5
      src/bin/auth/datasrc_config.cc
  13. 33 70
      src/bin/auth/datasrc_config.h
  14. 18 4
      src/bin/auth/main.cc
  15. 63 40
      src/bin/auth/tests/auth_srv_unittest.cc
  16. 45 22
      src/bin/auth/tests/command_unittest.cc
  17. 62 55
      src/bin/auth/tests/datasrc_config_unittest.cc
  18. 34 7
      src/bin/bind10/bind10_messages.mes
  19. 37 24
      src/bin/bind10/bind10_src.py.in
  20. 69 2
      src/bin/bind10/tests/bind10_test.py.in
  21. 2 0
      src/bin/dhcp6/Makefile.am
  22. 797 0
      src/bin/dhcp6/config_parser.cc
  23. 147 0
      src/bin/dhcp6/config_parser.h
  24. 25 5
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  25. 79 0
      src/bin/dhcp6/dhcp6.dox
  26. 90 2
      src/bin/dhcp6/dhcp6.spec
  27. 20 0
      src/bin/dhcp6/dhcp6_messages.mes
  28. 7 0
      src/bin/dhcp6/dhcp6_srv.cc
  29. 3 0
      src/bin/dhcp6/tests/Makefile.am
  30. 243 0
      src/bin/dhcp6/tests/config_parser_unittest.cc
  31. 8 8
      src/lib/cc/data.cc
  32. 22 22
      src/lib/cc/data.h
  33. 197 52
      src/lib/cc/tests/data_unittests.cc
  34. 9 63
      src/lib/datasrc/memory/domaintree.h
  35. 10 31
      src/lib/datasrc/memory/memory_client.cc
  36. 0 29
      src/lib/datasrc/memory/memory_client.h
  37. 1 1
      src/lib/datasrc/memory/zone_finder.cc
  38. 13 31
      src/lib/datasrc/memory/zone_table.cc
  39. 26 37
      src/lib/datasrc/memory/zone_table.h
  40. 0 1
      src/lib/datasrc/memory_datasrc.cc
  41. 1 1
      src/lib/datasrc/memory_datasrc_link.cc
  42. 16 15
      src/lib/datasrc/tests/memory/domaintree_unittest.cc
  43. 12 93
      src/lib/datasrc/tests/memory/memory_client_unittest.cc
  44. 1 1
      src/lib/datasrc/tests/memory/zone_data_unittest.cc
  45. 69 21
      src/lib/datasrc/tests/memory/zone_table_unittest.cc
  46. 0 1
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  47. 2 0
      src/lib/dhcp/Makefile.am
  48. 95 10
      src/lib/dhcp/addr_utilities.cc
  49. 33 0
      src/lib/dhcp/cfgmgr.cc
  50. 43 4
      src/lib/dhcp/cfgmgr.h
  51. 90 0
      src/lib/dhcp/duid.cc
  52. 98 0
      src/lib/dhcp/duid.h
  53. 68 0
      src/lib/dhcp/lease_mgr.cc
  54. 480 0
      src/lib/dhcp/lease_mgr.h
  55. 32 1
      src/lib/dhcp/pool.cc
  56. 27 0
      src/lib/dhcp/pool.h
  57. 44 0
      src/lib/dhcp/subnet.cc
  58. 51 0
      src/lib/dhcp/subnet.h
  59. 2 0
      src/lib/dhcp/tests/Makefile.am
  60. 63 2
      src/lib/dhcp/tests/addr_utilities_unittest.cc
  61. 36 0
      src/lib/dhcp/tests/cfgmgr_unittest.cc
  62. 169 0
      src/lib/dhcp/tests/duid_unittest.cc
  63. 296 0
      src/lib/dhcp/tests/lease_mgr_unittest.cc
  64. 73 0
      src/lib/dhcp/tests/pool_unittest.cc
  65. 80 2
      src/lib/dhcp/tests/subnet_unittest.cc
  66. 5 0
      src/lib/dhcp/triplet.h
  67. 0 1
      src/lib/dns/Makefile.am
  68. 0 60
      src/lib/dns/rrsetlist.cc
  69. 0 132
      src/lib/dns/rrsetlist.h
  70. 1 1
      src/lib/dns/tests/Makefile.am
  71. 0 188
      src/lib/dns/tests/rrsetlist_unittest.cc
  72. 27 9
      src/lib/python/isc/bind10/component.py
  73. 5 0
      src/lib/python/isc/bind10/sockcreator.py
  74. 43 22
      src/lib/python/isc/bind10/tests/component_test.py
  75. 6 2
      src/lib/util/locks.h

+ 74 - 1
ChangeLog

@@ -1,7 +1,80 @@
+494.	[bug]		jinmei
+	Fixed a problem that shutting down BIND 10 kept some of the
+	processes alive.  It was two-fold: when the main bind10 process
+	started as a root, started b10-sockcreator with the privilege, and
+	then dropped the privilege, the bind10 process cannot kill the
+	sockcreator via signal any more (when it has to), but it kept
+	sending the signal and didn't stop.  Also, when running on Python
+	3.1 (or older), the sockcreator had some additional file
+	descriptor open, which prevented it from exiting even after the
+	bind10 process terminated.  Now the bind10 process simply gives up
+	killing a subprocess if it fails due to lack of permission, and it
+	makes sure the socket creator is spawned without any unnecessary
+	FDs open.
+	(Trac #1858, git 405d85c8a0042ba807a3a123611ff383c4081ee1)
+
+493.	[build]		jinmei
+	Fixed build failure with newer versions of clang++.  These
+	versions are stricter regarding "unused variable" and "unused
+	(driver) arguments" warnings, and cause fatal build error
+	with -Werror.  The affected versions of clang++ include Apple's
+	customized version 4.1 included in Xcode 4.5.1.  So this fix
+	will solve build errors for Mac OS X that uses newer versions of
+	Xcode.
+	(Trac #2340, git 55be177fc4f7537143ab6ef5a728bd44bdf9d783,
+	3e2a372012e633d017a97029d13894e743199741 and commits before it
+	with [2340] in the commit log)
+
+492.	[func]		tomek
+	libdhcpsrv: The DHCP Configuration Manager is now able to store
+	information about IPv4 subnets and pools. It is still not possible
+	to configure that information. Such capability will be implemented
+	in a near future.
+	(Trac #2237, git a78e560343b41f0f692c7903c938b2b2b24bf56b)
+
+491.	[func]		tomek
+	b10-dhcp6: Configuration for DHCPv6 has been implemented.
+	Currently it is possible to configure IPv6 subnets and pools
+	within those subnets, global and per subnet values of renew,
+	rebind, preferred and valid lifetimes. Configured parameters
+	are accepted, but are not used yet by the allocation engine yet.
+	(Trac #2269, git 028bed9014b15facf1a29d3d4a822c9d14fc6411)
+
+490.	[func]		tomek
+	libdhcpsrv: An abstract API for lease database has been
+	implemented. It offers a common interface to all concrete
+	database backends.
+	(Trac #2140, git df196f7609757253c4f2f918cd91012bb3af1163)
+
+489.	[func]		muks
+	The isc::dns::RRsetList class has been removed. It was now unused
+	inside the BIND 10 codebase, and the interface was considered
+	prone to misuse.
+	(Trac #2266, git 532ac3d0054f6a11b91ee369964f3a84dabc6040)
+
+488.	[build]		jinmei
+	On configure, changed the search order for Python executable.
+	It first ties more specific file names such as "python3.2" before
+	more generic "python3".  This will prevent configure failure on
+	Mac OS X that installs Python3 via recent versions of Homebrew.
+	(Trac #2339, git 88db890d8d1c64de49be87f03c24a2021bcf63da)
+
+487.	[bug]		jinmei
+	The bind10 process now terminates a component (subprocess) by the
+	"config remove Boss/components" bindctl command even if the
+	process crashes immediately before the command is sent to bind10.
+	Previously this led to an inconsistent state between the
+	configuration and an internal component list of bind10, and bind10
+	kept trying to restart the component.  A known specific case of
+	this problem is that b10-ddns could keep failing (due to lack of
+	dependency modules) and the administrator couldn't stop the
+	restart via bindctl.
+	(Trac #2244, git 7565788d06f216ab254008ffdfae16678bcd00e5)
+
 486.	[bug]*		jinmei
 486.	[bug]*		jinmei
 	All public header files for libb10-dns++ are now installed.
 	All public header files for libb10-dns++ are now installed.
 	Template configure.ac and utility AC macros for external projects
 	Template configure.ac and utility AC macros for external projects
-	using the library is provided under the "examples" directory.
+	using the library are provided under the "examples" directory.
 	The src/bin/host was moved as part of the examples (and not
 	The src/bin/host was moved as part of the examples (and not
 	installed with other BIND 10 programs any more).
 	installed with other BIND 10 programs any more).
 	(Trac #1870, git 4973e638d354d8b56dcadf71123ef23c15662021)
 	(Trac #1870, git 4973e638d354d8b56dcadf71123ef23c15662021)

+ 103 - 91
configure.ac

@@ -70,6 +70,106 @@ AC_TRY_LINK([],[],
     ])
     ])
 LDFLAGS=$LDFLAGS_SAVED
 LDFLAGS=$LDFLAGS_SAVED
 
 
+# Compiler dependent settings: define some mandatory CXXFLAGS here.
+# We also use a separate variable B10_CXXFLAGS.  This will (and should) be
+# used as the default value for each specific AM_CXXFLAGS:
+# AM_CXXFLAGS = $(B10_CXXFLAGS)
+# AM_CXXFLAGS += ... # add module specific flags
+# We need this so that we can disable some specific compiler warnings per
+# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
+# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
+# specify the default warning flags in CXXFLAGS and let specific modules
+# "override" the default.
+
+# This may be used to try linker flags.
+AC_DEFUN([BIND10_CXX_TRY_FLAG], [
+  AC_MSG_CHECKING([whether $CXX supports $1])
+
+  bind10_save_CXXFLAGS="$CXXFLAGS"
+  CXXFLAGS="$CXXFLAGS $1"
+
+  AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
+                 [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
+  CXXFLAGS="$bind10_save_CXXFLAGS"
+
+  if test "x$bind10_cxx_flag" = "xyes"; then
+    ifelse([$2], , :, [$2])
+  else
+    ifelse([$3], , :, [$3])
+  fi
+
+  AC_MSG_RESULT([$bind10_cxx_flag])
+])
+
+# SunStudio compiler requires special compiler options for boost
+# (http://blogs.sun.com/sga/entry/boost_mini_howto)
+if test "$SUNCXX" = "yes"; then
+CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
+MULTITHREADING_FLAG="-mt"
+fi
+
+# Newer versions of clang++ promotes "unused driver arguments" warnings to
+# a fatal error with -Werror, causing build failure.  Since we use multiple
+# compilers on multiple systems, this can easily happen due to settings for
+# non clang++ environments that could be just ignored otherwise.  It can also
+# happen if clang++ is used via ccache.  So, although probably suboptimal,
+# we suppress this particular warning.  Note that it doesn't weaken checks
+# on the source code.
+if test "$CLANGPP" = "yes"; then
+	B10_CXXFLAGS="$B10_CXXFLAGS -Qunused-arguments"
+fi
+
+BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
+	[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
+AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+# gcc specific settings:
+if test "X$GXX" = "Xyes"; then
+B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
+case "$host" in
+*-solaris*)
+	MULTITHREADING_FLAG=-pthreads
+	# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
+	B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
+	;;
+*)
+	MULTITHREADING_FLAG=-pthread
+	;;
+esac
+
+# Don't use -Werror if configured not to
+AC_ARG_WITH(werror,
+    AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
+    [
+     case "${withval}" in
+         yes) with_werror=1 ;;
+         no)  with_werror=0 ;;
+         *)   AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
+     esac],
+     [with_werror=1])
+
+werror_ok=0
+
+# Certain versions of gcc (g++) have a bug that incorrectly warns about
+# the use of anonymous name spaces even if they're closed in a single
+# translation unit.  For these versions we have to disable -Werror.
+if test $with_werror = 1; then
+   CXXFLAGS_SAVED="$CXXFLAGS"
+   CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
+   AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
+   AC_TRY_COMPILE([namespace { class Foo {}; }
+   namespace isc {class Bar {Foo foo_;};} ],,
+	[AC_MSG_RESULT(no)
+	 werror_ok=1
+	 B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
+	[AC_MSG_RESULT(yes)])
+   CXXFLAGS="$CXXFLAGS_SAVED"
+fi
+
+fi				dnl GXX = yes
+
+AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
+
 # allow building programs with static link.  we need to make it selective
 # allow building programs with static link.  we need to make it selective
 # because loadable modules cannot be statically linked.
 # because loadable modules cannot be statically linked.
 AC_ARG_ENABLE([static-link],
 AC_ARG_ENABLE([static-link],
@@ -132,7 +232,7 @@ AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
 AC_SUBST(ENV_LIBRARY_PATH)
 AC_SUBST(ENV_LIBRARY_PATH)
 
 
-m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3 python3.1 python3.2])
+m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.2 python3.1 python3])
 AC_ARG_WITH([pythonpath],
 AC_ARG_WITH([pythonpath],
 AC_HELP_STRING([--with-pythonpath=PATH],
 AC_HELP_STRING([--with-pythonpath=PATH],
   [specify an absolute path to python executable when automatic version check (incorrectly) fails]),
   [specify an absolute path to python executable when automatic version check (incorrectly) fails]),
@@ -256,95 +356,11 @@ fi
 
 
 # TODO: check for _sqlite3.py module
 # TODO: check for _sqlite3.py module
 
 
-# Compiler dependent settings: define some mandatory CXXFLAGS here.
-# We also use a separate variable B10_CXXFLAGS.  This will (and should) be
-# used as the default value for each specific AM_CXXFLAGS:
-# AM_CXXFLAGS = $(B10_CXXFLAGS)
-# AM_CXXFLAGS += ... # add module specific flags
-# We need this so that we can disable some specific compiler warnings per
-# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
-# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
-# specify the default warning flags in CXXFLAGS and let specific modules
-# "override" the default.
-
-# This may be used to try linker flags.
-AC_DEFUN([BIND10_CXX_TRY_FLAG], [
-  AC_MSG_CHECKING([whether $CXX supports $1])
-
-  bind10_save_CXXFLAGS="$CXXFLAGS"
-  CXXFLAGS="$CXXFLAGS $1"
-
-  AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
-                 [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
-  CXXFLAGS="$bind10_save_CXXFLAGS"
-
-  if test "x$bind10_cxx_flag" = "xyes"; then
-    ifelse([$2], , :, [$2])
-  else
-    ifelse([$3], , :, [$3])
-  fi
-
-  AC_MSG_RESULT([$bind10_cxx_flag])
-])
-
-# SunStudio compiler requires special compiler options for boost
-# (http://blogs.sun.com/sga/entry/boost_mini_howto)
-if test "$SUNCXX" = "yes"; then
-CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
-MULTITHREADING_FLAG="-mt"
-fi
-
-BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
-	[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
-AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-
-# gcc specific settings:
-if test "X$GXX" = "Xyes"; then
-B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
-case "$host" in
-*-solaris*)
-	MULTITHREADING_FLAG=-pthreads
-	# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
-	B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
-	;;
-*)
-	MULTITHREADING_FLAG=-pthread
-	;;
-esac
-
-# Don't use -Werror if configured not to
-AC_ARG_WITH(werror,
-    AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
-    [
-     case "${withval}" in
-         yes) with_werror=1 ;;
-         no)  with_werror=0 ;;
-         *)   AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
-     esac],
-     [with_werror=1])
-
-werror_ok=0
-
-# Certain versions of gcc (g++) have a bug that incorrectly warns about
-# the use of anonymous name spaces even if they're closed in a single
-# translation unit.  For these versions we have to disable -Werror.
-if test $with_werror = 1; then
-   CXXFLAGS_SAVED="$CXXFLAGS"
-   CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
-   AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
-   AC_TRY_COMPILE([namespace { class Foo {}; }
-   namespace isc {class Bar {Foo foo_;};} ],,
-	[AC_MSG_RESULT(no)
-	 werror_ok=1
-	 B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
-	[AC_MSG_RESULT(yes)])
-   CXXFLAGS="$CXXFLAGS_SAVED"
-fi
-
+# (g++ only check)
 # Python 3.2 has an unused parameter in one of its headers. This
 # Python 3.2 has an unused parameter in one of its headers. This
 # has been reported, but not fixed as of yet, so we check if we need
 # has been reported, but not fixed as of yet, so we check if we need
 # to set -Wno-unused-parameter.
 # to set -Wno-unused-parameter.
-if test $werror_ok = 1; then
+if test "X$GXX" = "Xyes" -a $werror_ok = 1; then
 	CPPFLAGS_SAVED="$CPPFLAGS"
 	CPPFLAGS_SAVED="$CPPFLAGS"
 	CPPFLAGS=${PYTHON_INCLUDES}
 	CPPFLAGS=${PYTHON_INCLUDES}
 	CXXFLAGS_SAVED="$CXXFLAGS"
 	CXXFLAGS_SAVED="$CXXFLAGS"
@@ -370,10 +386,6 @@ if test $werror_ok = 1; then
 	CPPFLAGS="$CPPFLAGS_SAVED"
 	CPPFLAGS="$CPPFLAGS_SAVED"
 fi
 fi
 
 
-fi				dnl GXX = yes
-
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
 # produce PIC unless we disable shared libraries. need this for python bindings.
 # produce PIC unless we disable shared libraries. need this for python bindings.
 if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
 if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
    B10_CXXFLAGS="$B10_CXXFLAGS -fPIC"
    B10_CXXFLAGS="$B10_CXXFLAGS -fPIC"

+ 0 - 23
doc/devel/02-dhcp.dox

@@ -57,29 +57,6 @@
  * that does not support msgq. That is useful for embedded environments.
  * that does not support msgq. That is useful for embedded environments.
  * It may also be useful in validation.
  * It may also be useful in validation.
  *
  *
- * @page dhcpv6 DHCPv6 Server Component
- *
- * BIND10 offers DHCPv6 server implementation. It is implemented as
- * b10-dhcp6 component. Its primary code is located in
- * isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
- * especially lib::dhcp::Pkt6, isc::dhcp::Option and
- * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
- * functionality, i.e. it is able to receive and process incoming
- * requests and trasmit responses. However, it does not have database
- * management, so it returns only one, hardcoded lease to whoever asks
- * for it.
- *
- * DHCPv6 server component does not support relayed traffic yet, as
- * support for relay decapsulation is not implemented yet.
- *
- * DHCPv6 server component does not use BIND10 logging yet.
- *
- * @section dhcpv6Session BIND10 message queue integration
- *
- * DHCPv4 server component is now integrated with BIND10 message queue.
- * It follows the same principle as DHCPv4. See \ref dhcpv4Session for
- * details.
- *
  * @page libdhcp libdhcp++
  * @page libdhcp libdhcp++
  *
  *
  * @section libdhcpIntro Libdhcp++ Library Introduction
  * @section libdhcpIntro Libdhcp++ Library Introduction

+ 5 - 0
doc/devel/mainpage.dox

@@ -15,12 +15,17 @@
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
  *
  *
  * @section DNS
  * @section DNS
+ * - Authoritative DNS (todo)
+ * - Recursive resolver (todo)
  * - @subpage DataScrubbing
  * - @subpage DataScrubbing
  *
  *
  * @section DHCP
  * @section DHCP
  * - @subpage dhcpv4
  * - @subpage dhcpv4
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4Session
  * - @subpage dhcpv6
  * - @subpage dhcpv6
+ *   - @subpage dhcpv6-session
+ *   - @subpage dhcpv6-config-parser
+ *   - @subpage dhcpv6-config-inherit
  * - @subpage libdhcp
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIfaceMgr
  *   - @subpage libdhcpIfaceMgr

+ 89 - 10
doc/guide/bind10-guide.xml

@@ -2751,13 +2751,13 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
       <title>DHCPv4 Server Configuration</title>
       <title>DHCPv4 Server Configuration</title>
       <para>
       <para>
         The DHCPv4 server does not have a lease database implemented yet
         The DHCPv4 server does not have a lease database implemented yet
-        nor any support for configuration, so every time the same set
+        nor any support for configuration, so the same set
         of configuration options (including the same fixed address)
         of configuration options (including the same fixed address)
         will be assigned every time.
         will be assigned every time.
       </para>
       </para>
       <para>
       <para>
         At this stage of development, the only way to alter the server
         At this stage of development, the only way to alter the server
-        configuration is to tweak its source code. To do so, please
+        configuration is to modify its source code. To do so, please
         edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
         edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
         parameters and recompile:
         parameters and recompile:
         <screen>
         <screen>
@@ -2944,16 +2944,95 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
     <section id="dhcp6-config">
     <section id="dhcp6-config">
       <title>DHCPv6 Server Configuration</title>
       <title>DHCPv6 Server Configuration</title>
       <para>
       <para>
-        The DHCPv6 server does not have lease database implemented yet
-        or any support for configuration, so every time the same set
-        of configuration options (including the same fixed address)
-        will be assigned every time.
+        Once the server is started, it can be configured. To view the
+        current configuration, use the following command in <command>bindctl</command>:
+        <screen>
+          &gt; <userinput>config show Dhcp6</userinput></screen>
+        When starting Dhcp6 daemon for the first time, the default configuration
+        will be available. It will look similar to this:
+        <screen>
+&gt; <userinput>config show Dhcp6</userinput>
+Dhcp6/interface	         "eth0" string	(default)
+Dhcp6/renew-timer        1000   integer	(default)
+Dhcp6/rebind-timer       2000   integer	(default)
+Dhcp6/preferred-lifetime 3000   integer	(default)
+Dhcp6/valid-lifetime	 4000   integer	(default)
+Dhcp6/subnet6	         []     list    (default)</screen>
       </para>
       </para>
+
       <para>
       <para>
-        At this stage of development, the only way to alter server
-        configuration is to tweak its source code. To do so, please
-        edit src/bin/dhcp6/dhcp6_srv.cc file, modify the following
-        parameters and recompile:
+        To change one of the parameters, simply follow
+        the usual <command>bindctl</command> procedure. For example, to make the
+        leases longer, change their valid-lifetime parameter:
+        <screen>
+&gt; <userinput>config set Dhcp6/valid-lifetime 7200</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Please note that most Dhcp6 parameters are of global scope
+        and apply to all defined subnets, unless they are overridden on a
+        per-subnet basis.
+      </para>
+
+      <para>
+        The essential role of DHCPv6 server is address assignment. The server
+        has to be configured with at least one subnet and one pool of dynamic
+        addresses to be managed. For example, assume that the server
+        is connected to a network segment that uses the 2001:db8:1::/64
+        prefix. The Administrator of that network has decided that addresses from range
+        2001:db8:1::1 to 2001:db8:1::ffff are going to be managed by the Dhcp6
+        server. Such a configuration can be achieved in the following way:
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Note that subnet is defined as a simple string, but the pool parameter
+        is actually a list of pools: for this reason, the pool definition is
+        enclosed in square brackets, even though only one range of addresses
+        is specified.</para>
+        <para>It is possible to define more than one pool in a
+        subnet: continuing the previous example, further assume that 
+        2001:db8:1:0:5::/80 should be also be managed by the server. It could be written as
+        2001:db8:1:0:5:: to 2001:db8:1::5:ffff:ffff:ffff, but typing so many 'f's
+        is cumbersome. It can be expressed more simply as 2001:db8:1:0:5::/80. Both
+        formats are supported by Dhcp6 and can be mixed in the pool list.
+        For example, one could define the following pools:
+        <screen>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff", "2001:db8:1:0:5::/80" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        The number of pools is not limited, but for performance reasons it is recommended to
+        use as few as possible.
+      </para>
+      <para>
+         The server may be configured to serve more than one subnet. To add a second subnet,
+         use a command similar to the following:
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Arrays are counted from 0. subnet[0] refers to the subnet defined in the
+        previous example.  The <command>config add Dhcp6/subnet6</command> adds
+        another (second) subnet. It can be referred to as
+        <command>Dhcp6/subnet6[1]</command>. In this example, we allow server to
+        dynamically assign all addresses available in the whole subnet. Although
+        very wasteful, it is certainly a valid configuration to dedicate the
+        whole /48 subnet for that purpose.
+      </para>
+      <para>
+        When configuring a DHCPv6 server using prefix/length notation, please pay
+        attention to the boundary values. When specifying that the server should use
+        a given pool, it will be able to allocate also first (typically network
+        address) address from that pool. For example for pool 2001:db8::/64 the
+        2001:db8:: address may be assigned as well. If you want to avoid this,
+        please use min-max notation.
+      </para>
+
+      <para>
+        Note: Although configuration is now accepted, it is not internally used
+        by they server yet.  At this stage of development, the only way to alter
+        server configuration is to modify its source code. To do so, please edit
+        src/bin/dhcp6/dhcp6_srv.cc file, modify the following parameters and
+        recompile:
         <screen>
         <screen>
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const uint32_t HARDCODED_T1 = 1500; // in seconds
 const uint32_t HARDCODED_T1 = 1500; // in seconds

+ 1 - 1
examples/README

@@ -7,7 +7,7 @@ this directory.
 
 
 On the top (sub) directory (where this README file is stored), we
 On the top (sub) directory (where this README file is stored), we
 provide a sample configure.ac and Makefile.am files for GNU automake
 provide a sample configure.ac and Makefile.am files for GNU automake
-environments with helper autoconf macros to detect the available and
+environments with helper autoconf macros to detect the availability and
 location of BIND 10 header files and library objects.
 location of BIND 10 header files and library objects.
 
 
 You can use the configure.ac and Makefile.am files with macros under
 You can use the configure.ac and Makefile.am files with macros under

+ 1 - 1
src/bin/auth/auth_config.h

@@ -93,7 +93,7 @@ public:
     /// that corresponds to this derived class and prepares a new value to
     /// that corresponds to this derived class and prepares a new value to
     /// apply to the server.
     /// apply to the server.
     /// In the above example, the derived class for the identifier "param1"
     /// In the above example, the derived class for the identifier "param1"
-    /// would be passed an data \c Element storing an integer whose value
+    /// would be passed a data \c Element storing an integer whose value
     /// is 10, and would record that value internally;
     /// is 10, and would record that value internally;
     /// the derived class for the identifier "param2" would be passed a
     /// the derived class for the identifier "param2" would be passed a
     /// map element and (after parsing) convert it into some internal
     /// map element and (after parsing) convert it into some internal

+ 26 - 43
src/bin/auth/auth_srv.cc

@@ -69,6 +69,8 @@
 
 
 using namespace std;
 using namespace std;
 
 
+using boost::shared_ptr;
+
 using namespace isc;
 using namespace isc;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
@@ -264,23 +266,22 @@ public:
     AddressList listen_addresses_;
     AddressList listen_addresses_;
 
 
     /// The TSIG keyring
     /// The TSIG keyring
-    const boost::shared_ptr<TSIGKeyRing>* keyring_;
+    const shared_ptr<TSIGKeyRing>* keyring_;
 
 
-    /// The client list
-    std::map<RRClass, boost::shared_ptr<ConfigurableClientList> >
-        client_lists_;
+    /// The data source client list
+    AuthSrv::DataSrcClientListsPtr datasrc_client_lists_;
 
 
-    boost::shared_ptr<ConfigurableClientList> getClientList(const RRClass&
-                                                            rrclass)
+    shared_ptr<ConfigurableClientList> getDataSrcClientList(
+        const RRClass& rrclass)
     {
     {
         // TODO: Debug-build only check
         // TODO: Debug-build only check
         if (!mutex_.locked()) {
         if (!mutex_.locked()) {
             isc_throw(isc::Unexpected, "Not locked!");
             isc_throw(isc::Unexpected, "Not locked!");
         }
         }
-        const std::map<RRClass, boost::shared_ptr<ConfigurableClientList> >::
-            const_iterator it(client_lists_.find(rrclass));
-        if (it == client_lists_.end()) {
-            return (boost::shared_ptr<ConfigurableClientList>());
+        const std::map<RRClass, shared_ptr<ConfigurableClientList> >::
+            const_iterator it(datasrc_client_lists_->find(rrclass));
+        if (it == datasrc_client_lists_->end()) {
+            return (shared_ptr<ConfigurableClientList>());
         } else {
         } else {
             return (it->second);
             return (it->second);
         }
         }
@@ -335,6 +336,8 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
     xfrin_session_(NULL),
     xfrin_session_(NULL),
     counters_(),
     counters_(),
     keyring_(NULL),
     keyring_(NULL),
+    datasrc_client_lists_(new std::map<RRClass,
+                          shared_ptr<ConfigurableClientList> >()),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_forwarder_(NULL),
     ddns_forwarder_(NULL),
     xfrout_connected_(false),
     xfrout_connected_(false),
@@ -645,13 +648,13 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     }
     }
     // Lock the client lists and keep them under the lock until the processing
     // Lock the client lists and keep them under the lock until the processing
     // and rendering is done (this is the same mutex as from
     // and rendering is done (this is the same mutex as from
-    // AuthSrv::getClientListMutex()).
+    // AuthSrv::getDataSrcClientListMutex()).
     isc::util::thread::Mutex::Locker locker(mutex_);
     isc::util::thread::Mutex::Locker locker(mutex_);
 
 
     try {
     try {
         const ConstQuestionPtr question = *message.beginQuestion();
         const ConstQuestionPtr question = *message.beginQuestion();
-        const boost::shared_ptr<datasrc::ClientList>
-            list(getClientList(question->getClass()));
+        const shared_ptr<datasrc::ClientList>
+            list(getDataSrcClientList(question->getClass()));
         if (list) {
         if (list) {
             const RRType& qtype = question->getType();
             const RRType& qtype = question->getType();
             const Name& qname = question->getName();
             const Name& qname = question->getName();
@@ -911,7 +914,7 @@ AuthSrv::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
 }
 }
 
 
 void
 void
-AuthSrv::setTSIGKeyRing(const boost::shared_ptr<TSIGKeyRing>* keyring) {
+AuthSrv::setTSIGKeyRing(const shared_ptr<TSIGKeyRing>* keyring) {
     impl_->keyring_ = keyring;
     impl_->keyring_ = keyring;
 }
 }
 
 
@@ -930,43 +933,23 @@ AuthSrv::destroyDDNSForwarder() {
     }
     }
 }
 }
 
 
-void
-AuthSrv::setClientList(const RRClass& rrclass,
-                       const boost::shared_ptr<ConfigurableClientList>& list) {
+AuthSrv::DataSrcClientListsPtr
+AuthSrv::swapDataSrcClientLists(DataSrcClientListsPtr new_lists) {
     // TODO: Debug-build only check
     // TODO: Debug-build only check
     if (!impl_->mutex_.locked()) {
     if (!impl_->mutex_.locked()) {
-        isc_throw(isc::Unexpected, "Not locked");
-    }
-
-    if (list) {
-        impl_->client_lists_[rrclass] = list;
-    } else {
-        impl_->client_lists_.erase(rrclass);
+        isc_throw(isc::Unexpected, "Not locked!");
     }
     }
-}
-boost::shared_ptr<ConfigurableClientList>
-AuthSrv::getClientList(const RRClass& rrclass) {
-    return (impl_->getClientList(rrclass));
+    std::swap(new_lists, impl_->datasrc_client_lists_);
+    return (new_lists);
 }
 }
 
 
-vector<RRClass>
-AuthSrv::getClientListClasses() const {
-    // TODO: Debug-build only check
-    if (!impl_->mutex_.locked()) {
-        isc_throw(isc::Unexpected, "Not locked");
-    }
-
-    vector<RRClass> result;
-    for (std::map<RRClass, boost::shared_ptr<ConfigurableClientList> >::
-         const_iterator it(impl_->client_lists_.begin());
-         it != impl_->client_lists_.end(); ++it) {
-        result.push_back(it->first);
-    }
-    return (result);
+shared_ptr<ConfigurableClientList>
+AuthSrv::getDataSrcClientList(const RRClass& rrclass) {
+    return (impl_->getDataSrcClientList(rrclass));
 }
 }
 
 
 util::thread::Mutex&
 util::thread::Mutex&
-AuthSrv::getClientListMutex() const {
+AuthSrv::getDataSrcClientListMutex() const {
     return (impl_->mutex_);
     return (impl_->mutex_);
 }
 }
 
 

+ 45 - 24
src/bin/auth/auth_srv.h

@@ -15,10 +15,11 @@
 #ifndef __AUTH_SRV_H
 #ifndef __AUTH_SRV_H
 #define __AUTH_SRV_H 1
 #define __AUTH_SRV_H 1
 
 
-#include <string>
-
 #include <config/ccsession.h>
 #include <config/ccsession.h>
+
 #include <datasrc/factory.h>
 #include <datasrc/factory.h>
+#include <datasrc/client_list.h>
+
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/opcode.h>
 #include <dns/opcode.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
@@ -35,6 +36,11 @@
 #include <server_common/portconfig.h>
 #include <server_common/portconfig.h>
 #include <auth/statistics.h>
 #include <auth/statistics.h>
 
 
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+
 namespace isc {
 namespace isc {
 namespace util {
 namespace util {
 namespace io {
 namespace io {
@@ -296,31 +302,46 @@ public:
     /// If there was no forwarder yet, this method does nothing.
     /// If there was no forwarder yet, this method does nothing.
     void destroyDDNSForwarder();
     void destroyDDNSForwarder();
 
 
-    /// \brief Sets the currently used list for data sources of given
-    ///     class.
+    /// \brief Shortcut typedef used for swapDataSrcClientLists().
+    typedef boost::shared_ptr<std::map<
+        isc::dns::RRClass, boost::shared_ptr<
+                               isc::datasrc::ConfigurableClientList> > >
+    DataSrcClientListsPtr;
+
+    /// \brief Swap the currently used set of data source client lists with
+    /// given one.
+    ///
+    /// The "set" of lists is actually given in the form of map from
+    /// RRClasses to shared pointers to isc::datasrc::ConfigurableClientList.
+    ///
+    /// This method returns the swapped set of lists, which was previously
+    /// used by the server.
+    ///
+    /// This method is intended to be used by a separate method to update
+    /// the data source configuration "at once".  The caller must hold
+    /// a lock for the mutex object returned by  \c getDataSrcClientListMutex()
+    /// before calling this method.
     ///
     ///
-    /// Replaces the internally used client list with a new one. Other
-    /// classes are not changed.
+    /// The ownership of the returned pointer is transferred to the caller.
+    /// The caller is generally expected to release the resources used in
+    /// the old lists.  Note that it could take longer time if some of the
+    /// data source clients contain a large size of in-memory data.
     ///
     ///
-    /// \param rrclass The class to modify.
-    /// \param list Shared pointer to the client list. If it is NULL,
-    ///     the list is removed instead.
-    void setClientList(const isc::dns::RRClass& rrclass, const
-                       boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
-                       list);
+    /// The caller can pass a NULL pointer.  This effectively disables
+    /// any data source for the server.
+    ///
+    /// \param new_lists Shared pointer to a new set of data source client
+    /// lists.
+    /// \return The previous set of lists.  It can be NULL.
+    DataSrcClientListsPtr swapDataSrcClientLists(DataSrcClientListsPtr
+                                                 new_lists);
 
 
     /// \brief Returns the currently used client list for the class.
     /// \brief Returns the currently used client list for the class.
     ///
     ///
     /// \param rrclass The class for which to get the list.
     /// \param rrclass The class for which to get the list.
     /// \return The list, or NULL if no list is set for the class.
     /// \return The list, or NULL if no list is set for the class.
     boost::shared_ptr<isc::datasrc::ConfigurableClientList>
     boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        getClientList(const isc::dns::RRClass& rrclass);
-
-    /// \brief Returns a list of classes that have a client list.
-    ///
-    /// \return List of classes for which a non-NULL client list
-    ///     has been set by setClientList.
-    std::vector<isc::dns::RRClass> getClientListClasses() const;
+        getDataSrcClientList(const isc::dns::RRClass& rrclass);
 
 
     /// \brief Return a mutex for the client lists.
     /// \brief Return a mutex for the client lists.
     ///
     ///
@@ -331,9 +352,9 @@ public:
     /// is correct:
     /// is correct:
     /// \code
     /// \code
     /// {
     /// {
-    ///  Mutex::Locker locker(auth->getClientListMutex());
+    ///  Mutex::Locker locker(auth->getDataSrcClientListMutex());
     ///  boost::shared_ptr<isc::datasrc::ConfigurableClientList>
     ///  boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-    ///    list(auth->getClientList(RRClass::IN()));
+    ///    list(auth->getDataSrcClientList(RRClass::IN()));
     ///  // Do some processing here
     ///  // Do some processing here
     /// }
     /// }
     /// \endcode
     /// \endcode
@@ -342,8 +363,8 @@ public:
     /// \code
     /// \code
     /// boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
     /// boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
     /// {
     /// {
-    ///     Mutex::Locker locker(auth->getClientListMutex());
-    ///     list = auth->getClientList(RRClass::IN()));
+    ///     Mutex::Locker locker(auth->getDataSrcClientListMutex());
+    ///     list = auth->getDataSrcClientList(RRClass::IN()));
     /// }
     /// }
     /// // Do some processing here
     /// // Do some processing here
     /// \endcode
     /// \endcode
@@ -352,7 +373,7 @@ public:
     ///    (lock) the mutex. It's because locking of the mutex is not really
     ///    (lock) the mutex. It's because locking of the mutex is not really
     ///    a modification of the server object and it is needed to protect the
     ///    a modification of the server object and it is needed to protect the
     ///    lists even on read-only operations.
     ///    lists even on read-only operations.
-    isc::util::thread::Mutex& getClientListMutex() const;
+    isc::util::thread::Mutex& getDataSrcClientListMutex() const;
 
 
     /// \brief Sets the timeout for incoming TCP connections
     /// \brief Sets the timeout for incoming TCP connections
     ///
     ///

+ 21 - 15
src/bin/auth/benchmarks/query_bench.cc

@@ -18,6 +18,8 @@
 #include <bench/benchmark_util.h>
 #include <bench/benchmark_util.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
+#include <util/threads/lock.h>
+
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/question.h>
 #include <dns/question.h>
@@ -125,13 +127,15 @@ public:
                           OutputBuffer& buffer) :
                           OutputBuffer& buffer) :
         QueryBenchMark(queries, query_message, buffer)
         QueryBenchMark(queries, query_message, buffer)
     {
     {
-        configureDataSource(
-            *server_,
-            Element::fromJSON("{\"IN\":"
-                              "  [{\"type\": \"sqlite3\","
-                              "    \"params\": {"
-                              "      \"database_file\": \"" +
-                              string(datasrc_file) + "\"}}]}"));
+        isc::util::thread::Mutex::Locker locker(
+                server_->getDataSrcClientListMutex());
+        server_->swapDataSrcClientLists(
+            configureDataSource(
+                Element::fromJSON("{\"IN\":"
+                                  "  [{\"type\": \"sqlite3\","
+                                  "    \"params\": {"
+                                  "      \"database_file\": \"" +
+                                  string(datasrc_file) + "\"}}]}")));
     }
     }
 };
 };
 
 
@@ -144,14 +148,16 @@ public:
                          OutputBuffer& buffer) :
                          OutputBuffer& buffer) :
         QueryBenchMark(queries, query_message, buffer)
         QueryBenchMark(queries, query_message, buffer)
     {
     {
-        configureDataSource(
-            *server_,
-            Element::fromJSON("{\"IN\":"
-                              "  [{\"type\": \"MasterFiles\","
-                              "    \"cache-enable\": true, "
-                              "    \"params\": {\"" +
-                              string(zone_origin) + "\": \"" +
-                              string(zone_file) + "\"}}]}"));
+        isc::util::thread::Mutex::Locker locker(
+                server_->getDataSrcClientListMutex());
+        server_->swapDataSrcClientLists(
+            configureDataSource(
+                Element::fromJSON("{\"IN\":"
+                                  "  [{\"type\": \"MasterFiles\","
+                                  "    \"cache-enable\": true, "
+                                  "    \"params\": {\"" +
+                                  string(zone_origin) + "\": \"" +
+                                  string(zone_file) + "\"}}]}")));
     }
     }
 };
 };
 
 

+ 3 - 2
src/bin/auth/command.cc

@@ -192,9 +192,10 @@ public:
 
 
         // We're going to work with the client lists. They may be used
         // We're going to work with the client lists. They may be used
         // from a different thread too, protect them.
         // from a different thread too, protect them.
-        isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
+        isc::util::thread::Mutex::Locker locker(
+            server.getDataSrcClientListMutex());
         const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
         const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-            list(server.getClientList(zone_class));
+            list(server.getDataSrcClientList(zone_class));
 
 
         if (!list) {
         if (!list) {
             isc_throw(AuthCommandError, "There's no client list for "
             isc_throw(AuthCommandError, "There's no client list for "

+ 4 - 5
src/bin/auth/datasrc_config.cc

@@ -18,9 +18,8 @@
 
 
 // This is a trivial specialization for the commonly used version.
 // This is a trivial specialization for the commonly used version.
 // Defined in .cc to avoid accidental creation of multiple copies.
 // Defined in .cc to avoid accidental creation of multiple copies.
-void
-configureDataSource(AuthSrv& server, const isc::data::ConstElementPtr& config)
-{
-    return (configureDataSourceGeneric<AuthSrv,
-            isc::datasrc::ConfigurableClientList>(server, config));
+AuthSrv::DataSrcClientListsPtr
+configureDataSource(const isc::data::ConstElementPtr& config) {
+    return (configureDataSourceGeneric<
+            isc::datasrc::ConfigurableClientList>(config));
 }
 }

+ 33 - 70
src/bin/auth/datasrc_config.h

@@ -19,99 +19,62 @@
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 #include <datasrc/client_list.h>
 #include <datasrc/client_list.h>
-#include <util/threads/lock.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
+#include <utility>
 #include <set>
 #include <set>
 
 
-/// \brief Configure the authoritative server's data source lists
+/// \brief Configure data source client lists
 ///
 ///
 /// This will hook into the data_sources module configuration and it will
 /// This will hook into the data_sources module configuration and it will
-/// keep the local copy of data source clients in the list in the authoritative
-/// server.
+/// return a new set (in the form of a shared pointer to map) of data source
+/// client lists corresponding to the configuration.
 ///
 ///
 /// This function is templated. This is simply because of easier testing.
 /// This function is templated. This is simply because of easier testing.
 /// You don't need to pay attention to it, use the configureDataSource
 /// You don't need to pay attention to it, use the configureDataSource
 /// specialization instead.
 /// specialization instead.
 ///
 ///
-/// \param server It is the server to configure.
+/// \note In future we may want to make the reconfiguration more efficient
+/// by only creating newly configured data and just moving the rest from
+/// the running configuration if they are used in the new configuration
+/// without any parameter change.  We could probably do it by passing
+/// the old lists in addition to the new config, but further details are
+/// still to be defined yet.  It will surely require changes in the
+/// data source library, too.  So, right now, we don't introduce the
+/// possibility in the function interface.  If and when we decide to introduce
+/// the optimization, we'll extend the interface.
+///
 /// \param config The configuration value to parse. It is in the form
 /// \param config The configuration value to parse. It is in the form
 ///     as an update from the config manager.
 ///     as an update from the config manager.
-template<class Server, class List>
-void
-configureDataSourceGeneric(Server& server,
-                           const isc::data::ConstElementPtr& config)
-{
+/// \return A map from RR classes to configured lists.
+template<class List>
+boost::shared_ptr<std::map<isc::dns::RRClass,
+                           boost::shared_ptr<List> > > // = ListMap below
+configureDataSourceGeneric(const isc::data::ConstElementPtr& config) {
     typedef boost::shared_ptr<List> ListPtr;
     typedef boost::shared_ptr<List> ListPtr;
     typedef std::map<std::string, isc::data::ConstElementPtr> Map;
     typedef std::map<std::string, isc::data::ConstElementPtr> Map;
-    typedef std::pair<isc::dns::RRClass, ListPtr> RollbackPair;
-    typedef std::pair<isc::dns::RRClass, isc::data::ConstElementPtr>
-        RollbackConfiguration;
+    typedef std::map<isc::dns::RRClass, ListPtr> ListMap;
 
 
-    // Lock the client lists, we're going to manipulate them.
-    isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
+    boost::shared_ptr<ListMap> new_lists(new ListMap);
 
 
-    // Some structures to be able to perform a rollback
-    std::vector<RollbackPair> rollback_sets;
-    std::vector<RollbackConfiguration> rollback_configurations;
-    try {
-        // Get the configuration and current state.
-        const Map& map(config->mapValue());
-        const std::vector<isc::dns::RRClass>
-            activeVector(server.getClientListClasses());
-        std::set<isc::dns::RRClass> active(activeVector.begin(),
-                                           activeVector.end());
-        // Go through the configuration and change everything.
-        for (Map::const_iterator it(map.begin()); it != map.end(); ++it) {
-            const isc::dns::RRClass rrclass(it->first);
-            active.erase(rrclass);
-            ListPtr list(server.getClientList(rrclass));
-            bool need_set(false);
-            if (list) {
-                rollback_configurations.
-                    push_back(RollbackConfiguration(rrclass,
-                                                    list->getConfiguration()));
-            } else {
-                list.reset(new List(rrclass));
-                need_set = true;
-                rollback_sets.push_back(RollbackPair(rrclass, ListPtr()));
-            }
-            list->configure(it->second, true);
-            if (need_set) {
-                server.setClientList(rrclass, list);
-            }
-        }
-        // Remove the ones that are not in the configuration.
-        for (std::set<isc::dns::RRClass>::iterator it(active.begin());
-             it != active.end(); ++it) {
-            // There seems to be no way the setClientList could throw.
-            // But this is just to make sure in case it did to restore
-            // the original.
-            rollback_sets.push_back(
-                RollbackPair(*it, server.getClientList(*it)));
-            server.setClientList(*it, ListPtr());
-        }
-    } catch (...) {
-        // Perform a rollback of the changes. The old configuration should
-        // work.
-        for (typename std::vector<RollbackPair>::const_iterator
-                 it(rollback_sets.begin()); it != rollback_sets.end(); ++it) {
-            server.setClientList(it->first, it->second);
-        }
-        for (typename std::vector<RollbackConfiguration>::const_iterator
-                 it(rollback_configurations.begin());
-             it != rollback_configurations.end(); ++it) {
-            server.getClientList(it->first)->configure(it->second, true);
-        }
-        throw;
+    // Go through the configuration and create corresponding list.
+    const Map& map(config->mapValue());
+    for (Map::const_iterator it(map.begin()); it != map.end(); ++it) {
+        const isc::dns::RRClass rrclass(it->first);
+        ListPtr list(new List(rrclass));
+        list->configure(it->second, true);
+        new_lists->insert(std::pair<isc::dns::RRClass, ListPtr>(rrclass,
+                                                                list));
     }
     }
+
+    return (new_lists);
 }
 }
 
 
 /// \brief Concrete version of configureDataSource() for the
 /// \brief Concrete version of configureDataSource() for the
 ///     use with authoritative server implementation.
 ///     use with authoritative server implementation.
-void
-configureDataSource(AuthSrv& server, const isc::data::ConstElementPtr& config);
+AuthSrv::DataSrcClientListsPtr
+configureDataSource(const isc::data::ConstElementPtr& config);
 
 
 #endif  // DATASRC_CONFIG_H
 #endif  // DATASRC_CONFIG_H
 
 

+ 18 - 4
src/bin/auth/main.cc

@@ -18,6 +18,7 @@
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <util/io/socketsession.h>
 #include <util/io/socketsession.h>
+#include <util/threads/lock.h>
 
 
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
@@ -93,18 +94,31 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time,
 {
 {
     assert(server != NULL);
     assert(server != NULL);
     if (config->contains("classes")) {
     if (config->contains("classes")) {
+        AuthSrv::DataSrcClientListsPtr lists;
+
         if (*first_time) {
         if (*first_time) {
             // HACK: The default is not passed to the handler in the first
             // HACK: The default is not passed to the handler in the first
             // callback. This one will get the default (or, current value).
             // callback. This one will get the default (or, current value).
             // Further updates will work the usual way.
             // Further updates will work the usual way.
             assert(config_session != NULL);
             assert(config_session != NULL);
             *first_time = false;
             *first_time = false;
-            configureDataSource(*auth_server,
-                                config_session->getRemoteConfigValue(
-                                    "data_sources", "classes"));
+            lists = configureDataSource(
+                config_session->getRemoteConfigValue("data_sources",
+                                                     "classes"));
         } else {
         } else {
-            configureDataSource(*server, config->get("classes"));
+            lists = configureDataSource(config->get("classes"));
+        }
+
+        // Replace the server's lists.  The returned lists will be stored
+        // in a local variable 'lists', and will be destroyed outside of
+        // the temporary block for the lock scope.  That way we can minimize
+        // the range of the critical section.
+        {
+            isc::util::thread::Mutex::Locker locker(
+                server->getDataSrcClientListMutex());
+            lists = server->swapDataSrcClientLists(lists);
         }
         }
+        // The previous lists are destroyed here.
     }
     }
 }
 }
 
 

+ 63 - 40
src/bin/auth/tests/auth_srv_unittest.cc

@@ -63,6 +63,7 @@
 using namespace std;
 using namespace std;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::datasrc;
 using namespace isc::util;
 using namespace isc::util;
 using namespace isc::util::io::internal;
 using namespace isc::util::io::internal;
 using namespace isc::util::unittests;
 using namespace isc::util::unittests;
@@ -90,6 +91,9 @@ const char* const STATIC_DSRC_FILE = DSRC_DIR "/static.zone";
 // a signed example zone.
 // a signed example zone.
 const char* const CONFIG_INMEMORY_EXAMPLE = TEST_DATA_DIR "/rfc5155-example.zone.signed";
 const char* const CONFIG_INMEMORY_EXAMPLE = TEST_DATA_DIR "/rfc5155-example.zone.signed";
 
 
+// shortcut commonly used in tests
+typedef boost::shared_ptr<ConfigurableClientList> ListPtr;
+
 class AuthSrvTest : public SrvTestBase {
 class AuthSrvTest : public SrvTestBase {
 protected:
 protected:
     AuthSrvTest() :
     AuthSrvTest() :
@@ -722,13 +726,21 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
 }
 }
 
 
 void
 void
+installDataSrcClientLists(AuthSrv& server,
+                          AuthSrv::DataSrcClientListsPtr lists)
+{
+    thread::Mutex::Locker locker(server.getDataSrcClientListMutex());
+    server.swapDataSrcClientLists(lists);
+}
+
+void
 updateDatabase(AuthSrv& server, const char* params) {
 updateDatabase(AuthSrv& server, const char* params) {
     const ConstElementPtr config(Element::fromJSON("{"
     const ConstElementPtr config(Element::fromJSON("{"
         "\"IN\": [{"
         "\"IN\": [{"
         "    \"type\": \"sqlite3\","
         "    \"type\": \"sqlite3\","
         "    \"params\": " + string(params) +
         "    \"params\": " + string(params) +
         "}]}"));
         "}]}"));
-    configureDataSource(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 }
 }
 
 
 void
 void
@@ -745,7 +757,7 @@ updateInMemory(AuthSrv& server, const char* origin, const char* filename) {
         "   \"type\": \"static\","
         "   \"type\": \"static\","
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "}]}"));
         "}]}"));
-    configureDataSource(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 }
 }
 
 
 void
 void
@@ -755,7 +767,7 @@ updateBuiltin(AuthSrv& server) {
         "   \"type\": \"static\","
         "   \"type\": \"static\","
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "}]}"));
         "}]}"));
-    configureDataSource(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 }
 }
 
 
 // Try giving the server a TSIG signed request and see it can anwer signed as
 // Try giving the server a TSIG signed request and see it can anwer signed as
@@ -953,7 +965,7 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
         "   \"params\": {},"
         "   \"params\": {},"
         "   \"cache-enable\": true"
         "   \"cache-enable\": true"
         "}]}"));
         "}]}"));
-    configureDataSource(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
     // after successful configuration, we should have one (with empty zoneset).
     // after successful configuration, we should have one (with empty zoneset).
 
 
     // The memory data source is empty, should return REFUSED rcode.
     // The memory data source is empty, should return REFUSED rcode.
@@ -1427,11 +1439,14 @@ TEST_F(AuthSrvTest,
     // Set real inmem client to proxy
     // Set real inmem client to proxy
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
     {
     {
-        isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
+        isc::util::thread::Mutex::Locker locker(
+            server.getDataSrcClientListMutex());
         boost::shared_ptr<isc::datasrc::ConfigurableClientList>
         boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-            list(new FakeList(server.getClientList(RRClass::IN()), THROW_NEVER,
-                              false));
-        server.setClientList(RRClass::IN(), list);
+            list(new FakeList(server.getDataSrcClientList(RRClass::IN()),
+                              THROW_NEVER, false));
+        AuthSrv::DataSrcClientListsPtr lists(new std::map<RRClass, ListPtr>);
+        lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+        server.swapDataSrcClientLists(lists);
     }
     }
 
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
@@ -1455,11 +1470,14 @@ setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception,
 {
 {
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
 
-    isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
+    isc::util::thread::Mutex::Locker locker(
+        server.getDataSrcClientListMutex());
     boost::shared_ptr<isc::datasrc::ConfigurableClientList>
     boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        list(new FakeList(server.getClientList(RRClass::IN()), throw_when,
-                          isc_exception, rrset));
-    server.setClientList(RRClass::IN(), list);
+        list(new FakeList(server.getDataSrcClientList(RRClass::IN()),
+                          throw_when, isc_exception, rrset));
+    AuthSrv::DataSrcClientListsPtr lists(new std::map<RRClass, ListPtr>);
+    lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+    server.swapDataSrcClientLists(lists);
 }
 }
 
 
 TEST_F(AuthSrvTest,
 TEST_F(AuthSrvTest,
@@ -1771,46 +1789,51 @@ TEST_F(AuthSrvTest, clientList) {
     // We need to lock the mutex to make the (get|set)ClientList happy.
     // We need to lock the mutex to make the (get|set)ClientList happy.
     // There's a debug-build only check in them to make sure everything
     // There's a debug-build only check in them to make sure everything
     // locks them and we call them directly here.
     // locks them and we call them directly here.
-    isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
+    isc::util::thread::Mutex::Locker locker(
+        server.getDataSrcClientListMutex());
+
+    AuthSrv::DataSrcClientListsPtr lists; // initially empty
+
     // The lists don't exist. Therefore, the list of RRClasses is empty.
     // The lists don't exist. Therefore, the list of RRClasses is empty.
-    // We also have no IN list.
-    EXPECT_TRUE(server.getClientListClasses().empty());
-    EXPECT_EQ(boost::shared_ptr<const isc::datasrc::ClientList>(),
-              server.getClientList(RRClass::IN()));
+    EXPECT_TRUE(server.swapDataSrcClientLists(lists)->empty());
+
     // Put something in.
     // Put something in.
-    const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        list(new isc::datasrc::ConfigurableClientList(RRClass::IN()));
-    const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        list2(new isc::datasrc::ConfigurableClientList(RRClass::CH()));
-    server.setClientList(RRClass::IN(), list);
-    server.setClientList(RRClass::CH(), list2);
-    // There are two things in the list and they are IN and CH
-    vector<RRClass> classes(server.getClientListClasses());
-    ASSERT_EQ(2, classes.size());
-    EXPECT_EQ(RRClass::IN(), classes[0]);
-    EXPECT_EQ(RRClass::CH(), classes[1]);
+    const ListPtr list(new ConfigurableClientList(RRClass::IN()));
+    const ListPtr list2(new ConfigurableClientList(RRClass::CH()));
+
+    lists.reset(new std::map<RRClass, ListPtr>);
+    lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+    lists->insert(pair<RRClass, ListPtr>(RRClass::CH(), list2));
+    server.swapDataSrcClientLists(lists);
+
     // And the lists can be retrieved.
     // And the lists can be retrieved.
-    EXPECT_EQ(list, server.getClientList(RRClass::IN()));
-    EXPECT_EQ(list2, server.getClientList(RRClass::CH()));
-    // Remove one of them
-    server.setClientList(RRClass::CH(),
-        boost::shared_ptr<isc::datasrc::ConfigurableClientList>());
-    // This really got deleted, including the class.
-    classes = server.getClientListClasses();
-    ASSERT_EQ(1, classes.size());
-    EXPECT_EQ(RRClass::IN(), classes[0]);
-    EXPECT_EQ(list, server.getClientList(RRClass::IN()));
+    EXPECT_EQ(list, server.getDataSrcClientList(RRClass::IN()));
+    EXPECT_EQ(list2, server.getDataSrcClientList(RRClass::CH()));
+
+    // Replace the lists with new lists containing only one list.
+    lists.reset(new std::map<RRClass, ListPtr>);
+    lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+    lists = server.swapDataSrcClientLists(lists);
+
+    // Old one had two lists.  That confirms our swap for IN and CH classes
+    // (i.e., no other entries were there).
+    EXPECT_EQ(2, lists->size());
+
+    // The CH list really got deleted.
+    EXPECT_EQ(list, server.getDataSrcClientList(RRClass::IN()));
+    EXPECT_FALSE(server.getDataSrcClientList(RRClass::CH()));
 }
 }
 
 
 // We just test the mutex can be locked (exactly once).
 // We just test the mutex can be locked (exactly once).
 TEST_F(AuthSrvTest, mutex) {
 TEST_F(AuthSrvTest, mutex) {
-    isc::util::thread::Mutex::Locker l1(server.getClientListMutex());
+    isc::util::thread::Mutex::Locker l1(server.getDataSrcClientListMutex());
     // TODO: Once we have non-debug build, this one will not work, since
     // TODO: Once we have non-debug build, this one will not work, since
     // we currently use the fact that we can't lock twice from the same
     // we currently use the fact that we can't lock twice from the same
     // thread. In the non-debug mode, this would deadlock.
     // thread. In the non-debug mode, this would deadlock.
     // Skip then.
     // Skip then.
     EXPECT_THROW({
     EXPECT_THROW({
-        isc::util::thread::Mutex::Locker l2(server.getClientListMutex());
+        isc::util::thread::Mutex::Locker l2(
+            server.getDataSrcClientListMutex());
     }, isc::InvalidOperation);
     }, isc::InvalidOperation);
 }
 }
 
 

+ 45 - 22
src/bin/auth/tests/command_unittest.cc

@@ -16,6 +16,8 @@
 
 
 #include "datasrc_util.h"
 #include "datasrc_util.h"
 
 
+#include <util/threads/lock.h>
+
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/auth_config.h>
 #include <auth/command.h>
 #include <auth/command.h>
@@ -174,22 +176,32 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) {
 // zones, and checks the zones are correctly loaded.
 // zones, and checks the zones are correctly loaded.
 void
 void
 zoneChecks(AuthSrv& server) {
 zoneChecks(AuthSrv& server) {
-    isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    isc::util::thread::Mutex::Locker locker(
+        server.getDataSrcClientListMutex());
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::A())->code);
               find(Name("ns.test1.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::NXRRSET, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::AAAA())->code);
               find(Name("ns.test1.example"), RRType::AAAA())->code);
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::A())->code);
               find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::NXRRSET, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::AAAA())->code);
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 }
 
 
 void
 void
+installDataSrcClientLists(AuthSrv& server,
+                          AuthSrv::DataSrcClientListsPtr lists)
+{
+    isc::util::thread::Mutex::Locker locker(
+        server.getDataSrcClientListMutex());
+    server.swapDataSrcClientLists(lists);
+}
+
+void
 configureZones(AuthSrv& server) {
 configureZones(AuthSrv& server) {
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
     ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
@@ -208,27 +220,29 @@ configureZones(AuthSrv& server) {
         "   \"cache-enable\": true"
         "   \"cache-enable\": true"
         "}]}"));
         "}]}"));
 
 
-    configureDataSource(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 
 
     zoneChecks(server);
     zoneChecks(server);
 }
 }
 
 
 void
 void
 newZoneChecks(AuthSrv& server) {
 newZoneChecks(AuthSrv& server) {
-    isc::util::thread::Mutex::Locker locker(server.getClientListMutex());
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    isc::util::thread::Mutex::Locker locker(
+        server.getDataSrcClientListMutex());
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::A())->code);
               find(Name("ns.test1.example"), RRType::A())->code);
     // now test1.example should have ns/AAAA
     // now test1.example should have ns/AAAA
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example")).finder_->
               find(Name("ns.test1.example"), RRType::AAAA())->code);
               find(Name("ns.test1.example"), RRType::AAAA())->code);
 
 
     // test2.example shouldn't change
     // test2.example shouldn't change
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::A())->code);
               find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              server.getDataSrcClientList(RRClass::IN())->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example")).finder_->
               find(Name("ns.test2.example"), RRType::AAAA())->code);
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 }
@@ -271,12 +285,14 @@ TEST_F(AuthCommandTest,
         "    \"cache-enable\": true,"
         "    \"cache-enable\": true,"
         "    \"cache-zones\": [\"example.org\"]"
         "    \"cache-zones\": [\"example.org\"]"
         "}]}"));
         "}]}"));
-    configureDataSource(server_, config);
+    installDataSrcClientLists(server_, configureDataSource(config));
 
 
     {
     {
-        isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
+        isc::util::thread::Mutex::Locker locker(
+            server_.getDataSrcClientListMutex());
         // Check that the A record at www.example.org does not exist
         // Check that the A record at www.example.org does not exist
-        EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
+        EXPECT_EQ(ZoneFinder::NXDOMAIN,
+                  server_.getDataSrcClientList(RRClass::IN())->
                   find(Name("example.org")).finder_->
                   find(Name("example.org")).finder_->
                   find(Name("www.example.org"), RRType::A())->code);
                   find(Name("www.example.org"), RRType::A())->code);
 
 
@@ -296,7 +312,8 @@ TEST_F(AuthCommandTest,
         sql_updater->addRRset(*rrset);
         sql_updater->addRRset(*rrset);
         sql_updater->commit();
         sql_updater->commit();
 
 
-        EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
+        EXPECT_EQ(ZoneFinder::NXDOMAIN,
+                  server_.getDataSrcClientList(RRClass::IN())->
                   find(Name("example.org")).finder_->
                   find(Name("example.org")).finder_->
                   find(Name("www.example.org"), RRType::A())->code);
                   find(Name("www.example.org"), RRType::A())->code);
     }
     }
@@ -308,9 +325,11 @@ TEST_F(AuthCommandTest,
     checkAnswer(0, "Successful load");
     checkAnswer(0, "Successful load");
 
 
     {
     {
-        isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
+        isc::util::thread::Mutex::Locker locker(
+            server_.getDataSrcClientListMutex());
         // And now it should be present too.
         // And now it should be present too.
-        EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+        EXPECT_EQ(ZoneFinder::SUCCESS,
+                  server_.getDataSrcClientList(RRClass::IN())->
                   find(Name("example.org")).finder_->
                   find(Name("example.org")).finder_->
                   find(Name("www.example.org"), RRType::A())->code);
                   find(Name("www.example.org"), RRType::A())->code);
     }
     }
@@ -321,9 +340,11 @@ TEST_F(AuthCommandTest,
     checkAnswer(1, "example.com");
     checkAnswer(1, "example.com");
 
 
     {
     {
-        isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
+        isc::util::thread::Mutex::Locker locker(
+            server_.getDataSrcClientListMutex());
         // The previous zone is not hurt in any way
         // The previous zone is not hurt in any way
-        EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+        EXPECT_EQ(ZoneFinder::SUCCESS,
+                  server_.getDataSrcClientList(RRClass::IN())->
                   find(Name("example.org")).finder_->
                   find(Name("example.org")).finder_->
                   find(Name("example.org"), RRType::SOA())->code);
                   find(Name("example.org"), RRType::SOA())->code);
     }
     }
@@ -335,16 +356,18 @@ TEST_F(AuthCommandTest,
         "    \"cache-enable\": true,"
         "    \"cache-enable\": true,"
         "    \"cache-zones\": [\"example.com\"]"
         "    \"cache-zones\": [\"example.com\"]"
         "}]}"));
         "}]}"));
-    EXPECT_THROW(configureDataSource(server_, config2),
+    EXPECT_THROW(configureDataSource(config2),
                  ConfigurableClientList::ConfigurationError);
                  ConfigurableClientList::ConfigurationError);
 
 
     result_ = execAuthServerCommand(server_, "loadzone",
     result_ = execAuthServerCommand(server_, "loadzone",
         Element::fromJSON("{\"origin\": \"example.com\"}"));
         Element::fromJSON("{\"origin\": \"example.com\"}"));
     checkAnswer(1, "Unreadable");
     checkAnswer(1, "Unreadable");
 
 
-    isc::util::thread::Mutex::Locker locker(server_.getClientListMutex());
+    isc::util::thread::Mutex::Locker locker(
+        server_.getDataSrcClientListMutex());
     // The previous zone is not hurt in any way
     // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              server_.getDataSrcClientList(RRClass::IN())->
               find(Name("example.org")).finder_->
               find(Name("example.org")).finder_->
               find(Name("example.org"), RRType::SOA())->code);
               find(Name("example.org"), RRType::SOA())->code);
 }
 }

+ 62 - 55
src/bin/auth/tests/datasrc_config_unittest.cc

@@ -60,14 +60,11 @@ private:
 
 
 typedef shared_ptr<FakeList> ListPtr;
 typedef shared_ptr<FakeList> ListPtr;
 
 
+// Forward declaration.  We need precise definition of DatasrcConfigTest
+// to complete this function.
 void
 void
 testConfigureDataSource(DatasrcConfigTest& test,
 testConfigureDataSource(DatasrcConfigTest& test,
-                        const isc::data::ConstElementPtr& config)
-{
-    // We use the test fixture for the Server type.  This makes it possible
-    // to easily fake all needed methods and look that they were called.
-    configureDataSourceGeneric<DatasrcConfigTest, FakeList>(test, config);
-}
+                        const isc::data::ConstElementPtr& config);
 
 
 void
 void
 datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&,
 datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&,
@@ -82,26 +79,29 @@ datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&,
 class DatasrcConfigTest : public ::testing::Test {
 class DatasrcConfigTest : public ::testing::Test {
 public:
 public:
     // These pretend to be the server
     // These pretend to be the server
-    ListPtr getClientList(const RRClass& rrclass) {
-        log_ += "get " + rrclass.toText() + "\n";
-        return (lists_[rrclass]);
-    }
-    void setClientList(const RRClass& rrclass, const ListPtr& list) {
-        log_ += "set " + rrclass.toText() + " " +
-            (list ? list->getConf() : "") + "\n";
-        lists_[rrclass] = list;
+    isc::util::thread::Mutex& getDataSrcClientListMutex() const {
+        return (mutex_);
     }
     }
-    vector<RRClass> getClientListClasses() const {
-        vector<RRClass> result;
-        for (std::map<RRClass, ListPtr>::const_iterator it(lists_.begin());
-             it != lists_.end(); ++it) {
-            result.push_back(it->first);
+    void swapDataSrcClientLists(shared_ptr<std::map<dns::RRClass, ListPtr> >
+                                new_lists)
+    {
+        lists_.clear();         // first empty it
+
+        // Record the operation and results.  Note that map elements are
+        // sorted by RRClass, so the ordering should be predictable.
+        for (std::map<dns::RRClass, ListPtr>::const_iterator it =
+                 new_lists->begin();
+             it != new_lists->end();
+             ++it)
+        {
+            const RRClass rrclass = it->first;
+            ListPtr list = it->second;
+            log_ += "set " + rrclass.toText() + " " +
+                (list ? list->getConf() : "") + "\n";
+            lists_[rrclass] = list;
         }
         }
-        return (result);
-    }
-    isc::util::thread::Mutex& getClientListMutex() const {
-        return (mutex_);
     }
     }
+
 protected:
 protected:
     DatasrcConfigTest() :
     DatasrcConfigTest() :
         session(ElementPtr(new ListElement), ElementPtr(new ListElement),
         session(ElementPtr(new ListElement), ElementPtr(new ListElement),
@@ -147,9 +147,8 @@ protected:
         session.addMessage(createCommand("config_update", config),
         session.addMessage(createCommand("config_update", config),
                            "data_sources", "*");
                            "data_sources", "*");
         mccs->checkCommand();
         mccs->checkCommand();
-        // Check it called the correct things (check that there's no IN yet and
-        // set a new one.
-        EXPECT_EQ("get IN\nset IN xxx\n", log_);
+        // Check that the passed config is stored.
+        EXPECT_EQ("set IN xxx\n", log_);
         EXPECT_EQ(1, lists_.size());
         EXPECT_EQ(1, lists_.size());
     }
     }
     FakeSession session;
     FakeSession session;
@@ -160,14 +159,27 @@ protected:
     mutable isc::util::thread::Mutex mutex_;
     mutable isc::util::thread::Mutex mutex_;
 };
 };
 
 
+void
+testConfigureDataSource(DatasrcConfigTest& test,
+                        const isc::data::ConstElementPtr& config)
+{
+    // We use customized (faked lists) for the List type.  This makes it
+    // possible to easily look that they were called.
+    shared_ptr<std::map<dns::RRClass, ListPtr> > lists =
+        configureDataSourceGeneric<FakeList>(config);
+    test.swapDataSrcClientLists(lists);
+}
+
 // Push there a configuration with a single list.
 // Push there a configuration with a single list.
 TEST_F(DatasrcConfigTest, createList) {
 TEST_F(DatasrcConfigTest, createList) {
     initializeINList();
     initializeINList();
 }
 }
 
 
 TEST_F(DatasrcConfigTest, modifyList) {
 TEST_F(DatasrcConfigTest, modifyList) {
-    // First, initialize the list
+    // First, initialize the list, and confirm the current config
     initializeINList();
     initializeINList();
+    EXPECT_EQ("xxx", lists_[RRClass::IN()]->getConf());
+
     // And now change the configuration of the list
     // And now change the configuration of the list
     const ElementPtr
     const ElementPtr
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}]}"));
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}]}"));
@@ -175,9 +187,7 @@ TEST_F(DatasrcConfigTest, modifyList) {
                        "*");
                        "*");
     log_ = "";
     log_ = "";
     mccs->checkCommand();
     mccs->checkCommand();
-    // This one does not set
-    EXPECT_EQ("get IN\n", log_);
-    // But this should contain the yyy configuration
+    // Now the new one should be installed.
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ(1, lists_.size());
     EXPECT_EQ(1, lists_.size());
 }
 }
@@ -191,7 +201,7 @@ TEST_F(DatasrcConfigTest, multiple) {
                        "*");
                        "*");
     mccs->checkCommand();
     mccs->checkCommand();
     // We have set commands for both classes.
     // We have set commands for both classes.
-    EXPECT_EQ("get CH\nset CH xxx\nget IN\nset IN yyy\n", log_);
+    EXPECT_EQ("set IN yyy\nset CH xxx\n", log_);
     // We should have both there
     // We should have both there
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
@@ -212,9 +222,7 @@ TEST_F(DatasrcConfigTest, updateAdd) {
                        "*");
                        "*");
     log_ = "";
     log_ = "";
     mccs->checkCommand();
     mccs->checkCommand();
-    // The CH is set, IN not
-    EXPECT_EQ("get CH\nset CH xxx\nget IN\n", log_);
-    // But this should contain the yyy configuration
+    EXPECT_EQ("set IN yyy\nset CH xxx\n", log_);
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ(2, lists_.size());
     EXPECT_EQ(2, lists_.size());
@@ -229,18 +237,18 @@ TEST_F(DatasrcConfigTest, updateDelete) {
                        "*");
                        "*");
     log_ = "";
     log_ = "";
     mccs->checkCommand();
     mccs->checkCommand();
-    EXPECT_EQ("get IN\nset IN \n", log_);
-    EXPECT_FALSE(lists_[RRClass::IN()]);
-    // In real auth server, the NULL one would be removed. However, we just
-    // store it, so the IN bucket is still in there. This checks there's nothing
-    // else.
-    EXPECT_EQ(1, lists_.size());
+
+    // No operation takes place in the configuration, and the old one is
+    // just dropped
+    EXPECT_EQ("", log_);
+    EXPECT_TRUE(lists_.empty());
 }
 }
 
 
-// Check that we can rollback an addition if something else fails
-TEST_F(DatasrcConfigTest, rollbackAddition) {
+// Check that broken new configuration doesn't break the running configuration.
+TEST_F(DatasrcConfigTest, brokenConfigForAdd) {
     initializeINList();
     initializeINList();
-    // The configuration is wrong. However, the CH one will get done first.
+    // The configuration is wrong. However, the CH one will be handled
+    // without an error first.
     const ElementPtr
     const ElementPtr
         config(buildConfig("{\"IN\": [{\"type\": 13}], "
         config(buildConfig("{\"IN\": [{\"type\": 13}], "
                            "\"CH\": [{\"type\": \"xxx\"}]}"));
                            "\"CH\": [{\"type\": \"xxx\"}]}"));
@@ -256,8 +264,9 @@ TEST_F(DatasrcConfigTest, rollbackAddition) {
     EXPECT_FALSE(lists_[RRClass::CH()]);
     EXPECT_FALSE(lists_[RRClass::CH()]);
 }
 }
 
 
-// Check that we can rollback a deletion if something else fails
-TEST_F(DatasrcConfigTest, rollbackDeletion) {
+// Similar to the previous one, but the broken config would delete part of
+// the running config.
+TEST_F(DatasrcConfigTest, brokenConfigForDelete) {
     initializeINList();
     initializeINList();
     // Put the CH there
     // Put the CH there
     const ElementPtr
     const ElementPtr
@@ -266,27 +275,25 @@ TEST_F(DatasrcConfigTest, rollbackDeletion) {
     testConfigureDataSource(*this, config1);
     testConfigureDataSource(*this, config1);
     const ElementPtr
     const ElementPtr
         config2(Element::fromJSON("{\"IN\": [{\"type\": 13}]}"));
         config2(Element::fromJSON("{\"IN\": [{\"type\": 13}]}"));
-    // This would delete CH. However, the IN one fails.
-    // As the deletions happen after the additions/settings
-    // and there's no known way to cause an exception during the
-    // deletions, it is not a true rollback, but the result should
-    // be the same.
+    // This would delete CH. However, the new config is broken, so it won't
+    // actually apply.
     EXPECT_THROW(testConfigureDataSource(*this, config2), TypeError);
     EXPECT_THROW(testConfigureDataSource(*this, config2), TypeError);
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("yyy", lists_[RRClass::IN()]->getConf());
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
     EXPECT_EQ("xxx", lists_[RRClass::CH()]->getConf());
 }
 }
 
 
-// Check that we can roll back configuration change if something
-// fails later on.
-TEST_F(DatasrcConfigTest, rollbackConfiguration) {
+// Similar to the previous cases, but the broken config would modify the
+// running config of one of the classes.
+TEST_F(DatasrcConfigTest, brokenConfigForModify) {
     initializeINList();
     initializeINList();
     // Put the CH there
     // Put the CH there
     const ElementPtr
     const ElementPtr
         config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
         config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
                                   "\"CH\": [{\"type\": \"xxx\"}]}"));
                                   "\"CH\": [{\"type\": \"xxx\"}]}"));
     testConfigureDataSource(*this, config1);
     testConfigureDataSource(*this, config1);
-    // Now, the CH happens first. But nevertheless, it should be
-    // restored to the previoeus version.
+    // Now, the CH change will be handled first without an error, then
+    // the change to the IN class will fail, and the none of the changes
+    // will apply.
     const ElementPtr
     const ElementPtr
         config2(Element::fromJSON("{\"IN\": [{\"type\": 13}], "
         config2(Element::fromJSON("{\"IN\": [{\"type\": 13}], "
                                   "\"CH\": [{\"type\": \"yyy\"}]}"));
                                   "\"CH\": [{\"type\": \"yyy\"}]}"));

+ 34 - 7
src/bin/bind10/bind10_messages.mes

@@ -141,6 +141,16 @@ it now. The new configuration is printed.
 % BIND10_RECEIVED_SIGNAL received signal %1
 % BIND10_RECEIVED_SIGNAL received signal %1
 The boss module received the given signal.
 The boss module received the given signal.
 
 
+% BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
+The boss module tried to restart a component after it failed (crashed)
+unexpectedly, but the boss then found that the component had been removed
+from its local configuration of components to run.  This is an unusal
+situation but can happen if the administrator removes the component from
+the configuration after the component's crash and before the restart time.
+The boss module simply skipped restarting that module, and the whole system
+went back to the expected state (except that the crash itself is likely
+to be a bug).
+
 % BIND10_RESURRECTED_PROCESS resurrected %1 (PID %2)
 % BIND10_RESURRECTED_PROCESS resurrected %1 (PID %2)
 The given process has been restarted successfully, and is now running
 The given process has been restarted successfully, and is now running
 with the given process id.
 with the given process id.
@@ -157,6 +167,30 @@ so BIND 10 will now shut down. The specific error is printed.
 % BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2)
 % BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2)
 The boss module is sending a SIGKILL signal to the given process.
 The boss module is sending a SIGKILL signal to the given process.
 
 
+% BIND10_SEND_SIGNAL_FAIL sending %1 to %2 (PID %3) failed: %4
+The boss module sent a single (either SIGTERM or SIGKILL) to a process,
+but it failed due to some system level error.  There are two major cases:
+the target process has already terminated but the boss module had sent
+the signal before it noticed the termination.  In this case an error
+message should indicate something like "no such process".  This can be
+safely ignored.  The other case is that the boss module doesn't have
+the privilege to send a signal to the process.  It can typically
+happen when the boss module started as a privileged process, spawned a
+subprocess, and then dropped the privilege.  It includes the case for
+the socket creator when the boss process runs with the -u command line
+option.  In this case, the boss module simply gives up to terminate
+the process explicitly because it's unlikely to succeed by keeping
+sending the signal.  Although the socket creator is implemented so
+that it will terminate automatically when the boss process exits
+(and that should be the case for any other future process running with
+a higher privilege), but it's recommended to check if there's any
+remaining BIND 10 process if this message is logged.  For all other
+cases, the boss module will keep sending the signal until it confirms
+all child processes terminate.  Although unlikely, this could prevent
+the boss module from exiting, just keeping sending the signals.  So,
+again, it's advisable to check if it really terminates when this
+message is logged.
+
 % BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
 % BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
 The boss module is sending a SIGTERM signal to the given process.
 The boss module is sending a SIGTERM signal to the given process.
 
 
@@ -264,13 +298,6 @@ During the startup process, a number of messages are exchanged between the
 Boss process and the processes it starts.  This error is output when a
 Boss process and the processes it starts.  This error is output when a
 message received by the Boss process is not recognised.
 message received by the Boss process is not recognised.
 
 
-% BIND10_START_AS_NON_ROOT_AUTH starting b10-auth as a user, not root. This might fail.
-The authoritative server is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
 % BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
 % BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
 The resolver is being started or restarted without root privileges.
 The resolver is being started or restarted without root privileges.
 If the module needs these privileges, it may have problems starting.
 If the module needs these privileges, it may have problems starting.

+ 37 - 24
src/bin/bind10/bind10_src.py.in

@@ -546,8 +546,6 @@ class BoB:
         """
         """
             Start the Authoritative server
             Start the Authoritative server
         """
         """
-        if self.uid is not None and self.__started:
-            logger.warn(BIND10_START_AS_NON_ROOT_AUTH)
         authargs = ['b10-auth']
         authargs = ['b10-auth']
         if self.verbose:
         if self.verbose:
             authargs += ['-v']
             authargs += ['-v']
@@ -693,32 +691,42 @@ class BoB:
         # from doing so
         # from doing so
         if not self.nokill:
         if not self.nokill:
             # next try sending a SIGTERM
             # next try sending a SIGTERM
-            components_to_stop = list(self.components.values())
-            for component in components_to_stop:
-                logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
-                try:
-                    component.kill()
-                except OSError:
-                    # ignore these (usually ESRCH because the child
-                    # finally exited)
-                    pass
-            # finally, send SIGKILL (unmaskable termination) until everybody dies
+            self.__kill_children(False)
+            # finally, send SIGKILL (unmaskable termination) until everybody
+            # dies
             while self.components:
             while self.components:
                 # XXX: some delay probably useful... how much is uncertain
                 # XXX: some delay probably useful... how much is uncertain
                 time.sleep(0.1)
                 time.sleep(0.1)
                 self.reap_children()
                 self.reap_children()
-                components_to_stop = list(self.components.values())
-                for component in components_to_stop:
-                    logger.info(BIND10_SEND_SIGKILL, component.name(),
-                                component.pid())
-                    try:
-                        component.kill(True)
-                    except OSError:
-                        # ignore these (usually ESRCH because the child
-                        # finally exited)
-                        pass
+                self.__kill_children(True)
             logger.info(BIND10_SHUTDOWN_COMPLETE)
             logger.info(BIND10_SHUTDOWN_COMPLETE)
 
 
+    def __kill_children(self, forceful):
+        '''Terminate remaining subprocesses by sending a signal.
+
+        The forceful paramter will be passed Component.kill().
+        This is a dedicated subroutine of shutdown(), just to unify two
+        similar cases.
+
+        '''
+        logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM
+        # We need to make a copy of values as the components may be modified
+        # in the loop.
+        for component in list(self.components.values()):
+            logger.info(logmsg, component.name(), component.pid())
+            try:
+                component.kill(forceful)
+            except OSError as ex:
+                # If kill() failed due to EPERM, it doesn't make sense to
+                # keep trying, so we just log the fact and forget that
+                # component.  Ignore other OSErrors (usually ESRCH because
+                # the child finally exited)
+                signame = "SIGKILL" if forceful else "SIGTERM"
+                logger.info(BIND10_SEND_SIGNAL_FAIL, signame,
+                            component.name(), component.pid(), ex)
+                if ex.errno == errno.EPERM:
+                    del self.components[component.pid()]
+
     def _get_process_exit_status(self):
     def _get_process_exit_status(self):
         return os.waitpid(-1, os.WNOHANG)
         return os.waitpid(-1, os.WNOHANG)
 
 
@@ -739,7 +747,7 @@ class BoB:
                 component = self.components.pop(pid)
                 component = self.components.pop(pid)
                 logger.info(BIND10_PROCESS_ENDED, component.name(), pid,
                 logger.info(BIND10_PROCESS_ENDED, component.name(), pid,
                             exit_status)
                             exit_status)
-                if component.running() and self.runnable:
+                if component.is_running() and self.runnable:
                     # Tell it it failed. But only if it matters (we are
                     # Tell it it failed. But only if it matters (we are
                     # not shutting down and the component considers itself
                     # not shutting down and the component considers itself
                     # to be running.
                     # to be running.
@@ -771,7 +779,12 @@ class BoB:
         next_restart_time = None
         next_restart_time = None
         now = time.time()
         now = time.time()
         for component in self.components_to_restart:
         for component in self.components_to_restart:
-            if not component.restart(now):
+            # If the component was removed from the configurator between since
+            # scheduled to restart, just ignore it.  The object will just be
+            # dropped here.
+            if not self._component_configurator.has_component(component):
+                logger.info(BIND10_RESTART_COMPONENT_SKIPPED, component.name())
+            elif not component.restart(now):
                 still_dead.append(component)
                 still_dead.append(component)
                 if next_restart_time is None or\
                 if next_restart_time is None or\
                    next_restart_time > component.get_restart_time():
                    next_restart_time > component.get_restart_time():

+ 69 - 2
src/bin/bind10/tests/bind10_test.py.in

@@ -929,7 +929,14 @@ class MockComponent:
         self.name = lambda: name
         self.name = lambda: name
         self.pid = lambda: pid
         self.pid = lambda: pid
         self.address = lambda: address
         self.address = lambda: address
+        self.restarted = False
 
 
+    def get_restart_time(self):
+        return 0                # arbitrary dummy value
+
+    def restart(self, now):
+        self.restarted = True
+        return True
 
 
 class TestBossCmd(unittest.TestCase):
 class TestBossCmd(unittest.TestCase):
     def test_ping(self):
     def test_ping(self):
@@ -1174,7 +1181,7 @@ class TestBossComponents(unittest.TestCase):
         # We check somewhere else that the shutdown is actually called
         # We check somewhere else that the shutdown is actually called
         # from there (the test_kills).
         # from there (the test_kills).
 
 
-    def __real_test_kill(self, nokill = False):
+    def __real_test_kill(self, nokill=False, ex_on_kill=None):
         """
         """
         Helper function that does the actual kill functionality testing.
         Helper function that does the actual kill functionality testing.
         """
         """
@@ -1188,8 +1195,23 @@ class TestBossComponents(unittest.TestCase):
             (anyway it is not told so). It does not die if it is killed
             (anyway it is not told so). It does not die if it is killed
             the first time. It dies only when killed forcefully.
             the first time. It dies only when killed forcefully.
             """
             """
+            def __init__(self):
+                # number of kill() calls, preventing infinite loop.
+                self.__call_count = 0
+
             def kill(self, forceful=False):
             def kill(self, forceful=False):
+                self.__call_count += 1
+                if self.__call_count > 2:
+                    raise Exception('Too many calls to ImmortalComponent.kill')
+
                 killed.append(forceful)
                 killed.append(forceful)
+                if ex_on_kill is not None:
+                    # If exception is given by the test, raise it here.
+                    # In the case of ESRCH, the process should have gone
+                    # somehow, so we clear the components.
+                    if ex_on_kill.errno == errno.ESRCH:
+                        bob.components = {}
+                    raise ex_on_kill
                 if forceful:
                 if forceful:
                     bob.components = {}
                     bob.components = {}
             def pid(self):
             def pid(self):
@@ -1217,7 +1239,10 @@ class TestBossComponents(unittest.TestCase):
         if nokill:
         if nokill:
             self.assertEqual([], killed)
             self.assertEqual([], killed)
         else:
         else:
-            self.assertEqual([False, True], killed)
+            if ex_on_kill is not None:
+                self.assertEqual([False], killed)
+            else:
+                self.assertEqual([False, True], killed)
 
 
         self.assertTrue(self.__called)
         self.assertTrue(self.__called)
 
 
@@ -1229,6 +1254,20 @@ class TestBossComponents(unittest.TestCase):
         """
         """
         self.__real_test_kill()
         self.__real_test_kill()
 
 
+    def test_kill_fail(self):
+        """Test cases where kill() results in an exception due to OS error.
+
+        The behavior should be different for EPERM, so we test two cases.
+
+        """
+
+        ex = OSError()
+        ex.errno, ex.strerror = errno.ESRCH, 'No such process'
+        self.__real_test_kill(ex_on_kill=ex)
+
+        ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+        self.__real_test_kill(ex_on_kill=ex)
+
     def test_nokill(self):
     def test_nokill(self):
         """
         """
         Test that the boss *doesn't* kill components which don't want to
         Test that the boss *doesn't* kill components which don't want to
@@ -1266,6 +1305,34 @@ class TestBossComponents(unittest.TestCase):
         bob.start_all_components()
         bob.start_all_components()
         self.__check_extended(self.__param)
         self.__check_extended(self.__param)
 
 
+    def __setup_restart(self, bob, component):
+        '''Common procedure for restarting a component used below.'''
+        bob.components_to_restart = { component }
+        component.restarted = False
+        bob.restart_processes()
+
+    def test_restart_processes(self):
+        '''Check some behavior on restarting processes.'''
+        bob = MockBob()
+        bob.runnable = True
+        component = MockComponent('test', 53)
+
+        # A component to be restarted will actually be restarted iff it's
+        # in the configurator's configuration.
+        # We bruteforce the configurator internal below; ugly, but the easiest
+        # way for the test.
+        bob._component_configurator._components['test'] = (None, component)
+        self.__setup_restart(bob, component)
+        self.assertTrue(component.restarted)
+        self.assertFalse(component in bob.components_to_restart)
+
+        # Remove the component from the configuration.  It won't be restarted
+        # even if scheduled, nor will remain in the to-be-restarted list.
+        del bob._component_configurator._components['test']
+        self.__setup_restart(bob, component)
+        self.assertFalse(component.restarted)
+        self.assertFalse(component in bob.components_to_restart)
+
 class SocketSrvTest(unittest.TestCase):
 class SocketSrvTest(unittest.TestCase):
     """
     """
     This tests some methods of boss related to the unix domain sockets used
     This tests some methods of boss related to the unix domain sockets used

+ 2 - 0
src/bin/dhcp6/Makefile.am

@@ -46,6 +46,7 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 
 b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
+b10_dhcp6_SOURCES += config_parser.cc config_parser.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 
 
@@ -62,6 +63,7 @@ b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 
 

+ 797 - 0
src/bin/dhcp6/config_parser.cc

@@ -0,0 +1,797 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+#include <iostream>
+#include <vector>
+#include <map>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+#include <log/logger_support.h>
+#include <dhcp/triplet.h>
+#include <dhcp/pool.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_log.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief auxiliary type used for storing element name and its parser
+typedef pair<string, ConstElementPtr> ConfigPair;
+
+/// @brief a factory method that will create a parser for a given element name
+typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+
+/// @brief a collection of factories that creates parsers for specified element names
+typedef std::map<std::string, ParserFactory*> FactoryMap;
+
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<string, string> StringStorage;
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<Pool6Ptr> PoolStorage;
+
+/// @brief Global uint32 parameters that will be used as defaults.
+Uint32Storage uint32_defaults;
+
+/// @brief global string parameters that will be used as defaults.
+StringStorage string_defaults;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    ///
+    /// @param param_name name of the parsed parameter
+    DebugParser(const std::string& param_name)
+        :param_name_(param_name) {
+    }
+
+    /// @brief builds parameter value
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    ///
+    /// @param new_config pointer to the new configuration
+    virtual void build(ConstElementPtr new_config) {
+        std::cout << "Build for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
+        value_ = new_config;
+    }
+
+    /// @brief pretends to apply the configuration
+    ///
+    /// This is a method required by base class. It pretends to apply the
+    /// configuration, but in fact it only prints the parameter out.
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    virtual void commit() {
+        // Debug message. The whole DebugParser class is used only for parser
+        // debugging, and is not used in production code. It is very convenient
+        // to keep it around. Please do not turn this cout into logger calls.
+        std::cout << "Commit for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
+    }
+
+    /// @brief factory that constructs DebugParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new DebugParser(param_name));
+    }
+
+protected:
+    /// name of the parsed parameter
+    std::string param_name_;
+
+    /// pointer to the actual value of the parameter
+    ConstElementPtr value_;
+};
+
+/// @brief Configuration parser for uint32 parameters
+///
+/// This class is a generic parser that is able to handle any uint32 integer
+/// type. By default it stores the value in external global container
+/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv6-config-inherit page.
+class Uint32Parser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor for Uint32Parser
+    /// @param param_name name of the configuration parameter being parsed
+    Uint32Parser(const std::string& param_name)
+        :storage_(&uint32_defaults), param_name_(param_name) {
+    }
+
+    /// @brief builds parameter value
+    ///
+    /// Parses configuration entry and stores it in a storage. See
+    /// \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        try {
+            value_ = boost::lexical_cast<uint32_t>(value->str());
+        } catch (const boost::bad_lexical_cast &) {
+            isc_throw(BadValue, "Failed to parse value " << value->str()
+                      << " as unsigned 32-bit integer.");
+        }
+        storage_->insert(pair<string, uint32_t>(param_name_, value_));
+    }
+
+    /// @brief does nothing
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers are expected to
+    /// use values stored in the storage, e.g. renew-timer for a given
+    /// subnet is stored in subnet-specific storage. It is not commited
+    /// here, but is rather used by \ref Subnet6Parser when constructing
+    /// the subnet.
+    virtual void commit() {
+    }
+
+    /// @brief factory that constructs Uint32Parser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new Uint32Parser(param_name));
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(Uint32Storage* storage) {
+        storage_ = storage;
+    }
+
+protected:
+    /// pointer to the storage, where parsed value will be stored
+    Uint32Storage* storage_;
+
+    /// name of the parameter to be parsed
+    std::string param_name_;
+
+    /// the actual parsed value
+    uint32_t value_;
+};
+
+/// @brief Configuration parser for string parameters
+///
+/// This class is a generic parser that is able to handle any string
+/// parameter. By default it stores the value in external global container
+/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv6-config-inherit page.
+class StringParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor for StringParser
+    /// @param param_name name of the configuration parameter being parsed
+    StringParser(const std::string& param_name)
+        :storage_(&string_defaults), param_name_(param_name) {
+    }
+
+    /// @brief parses parameter value
+    ///
+    /// Parses configuration entry and stored it in storage. See
+    /// \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        value_ = value->str();
+        boost::erase_all(value_, "\"");
+        storage_->insert(pair<string, string>(param_name_, value_));
+    }
+
+    /// @brief does nothing
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers are expected to
+    /// use values stored in the storage, e.g. renew-timer for a given
+    /// subnet is stored in subnet-specific storage. It is not commited
+    /// here, but is rather used by its parent parser when constructing
+    /// an object, e.g. the subnet.
+    virtual void commit() {
+    }
+
+    /// @brief factory that constructs StringParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new StringParser(param_name));
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(StringStorage* storage) {
+        storage_ = storage;
+    }
+
+protected:
+    /// pointer to the storage, where parsed value will be stored
+    StringStorage* storage_;
+
+    /// name of the parameter to be parsed
+    std::string param_name_;
+
+    /// the actual parsed value
+    std::string value_;
+};
+
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp6/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp6/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    /// As this is a dedicated parser, it must be used to parse
+    /// "interface" parameter only. All other types will throw exception.
+    ///
+    /// @param param_name name of the configuration parameter being parsed
+    InterfaceListConfigParser(const std::string& param_name) {
+        if (param_name != "interface") {
+            isc_throw(NotImplemented, "Internal error. Interface configuration "
+                      "parser called for the wrong parameter: " << param_name);
+        }
+    }
+
+    /// @brief parses parameters value
+    ///
+    /// Parses configuration entry (list of parameters) and stores it in
+    /// storage. See \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+            interfaces_.push_back(iface->str());
+        }
+    }
+
+    /// @brief commits interfaces list configuration
+    virtual void commit() {
+        /// @todo: Implement per interface listening. Currently always listening
+        /// on all interfaces.
+    }
+
+    /// @brief factory that constructs InterfaceListConfigParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new InterfaceListConfigParser(param_name));
+    }
+
+protected:
+    /// contains list of network interfaces
+    vector<string> interfaces_;
+};
+
+/// @brief parser for pool definition
+///
+/// This parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool6 objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// As there are no default values for pool, setStorage() must be called
+/// before build(). Otherwise exception will be thrown.
+///
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor.
+    PoolParser(const std::string& /*param_name*/)
+        :pools_(NULL) {
+        // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
+    }
+
+    /// @brief parses the actual list
+    ///
+    /// This method parses the actual list of interfaces.
+    /// No validation is done at this stage, everything is interpreted as
+    /// interface name.
+    void build(ConstElementPtr pools_list) {
+        // setStorage() should have been called before build
+        if (!pools_) {
+            isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+                      " but pool parser asked to parse pools");
+        }
+
+        BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+
+            // That should be a single pool representation. It should contain
+            // text is form prefix/len or first - last. Note that spaces
+            // are allowed
+            string txt = text_pool->stringValue();
+
+            // first let's remove any whitespaces
+            boost::erase_all(txt, " "); // space
+            boost::erase_all(txt, "\t"); // tabulation
+
+            // Is this prefix/len notation?
+            size_t pos = txt.find("/");
+            if (pos != string::npos) {
+                IOAddress addr("::");
+                uint8_t len = 0;
+                try {
+                    addr = IOAddress(txt.substr(0, pos));
+
+                    // start with the first character after /
+                    string prefix_len = txt.substr(pos + 1);
+
+                    // It is lexical cast to int and then downcast to uint8_t.
+                    // Direct cast to uint8_t (which is really an unsigned char)
+                    // will result in interpreting the first digit as output
+                    // value and throwing exception if length is written on two
+                    // digits (because there are extra characters left over).
+
+                    // No checks for values over 128. Range correctness will
+                    // be checked in Pool6 constructor.
+                    len = boost::lexical_cast<int>(prefix_len);
+                } catch (...)  {
+                    isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+                              "definition: " << text_pool->stringValue());
+                }
+
+                Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
+                pools_->push_back(pool);
+                continue;
+            }
+
+            // Is this min-max notation?
+            pos = txt.find("-");
+            if (pos != string::npos) {
+                // using min-max notation
+                IOAddress min(txt.substr(0,pos - 1));
+                IOAddress max(txt.substr(pos + 1));
+
+                Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
+
+                pools_->push_back(pool);
+                continue;
+            }
+
+            isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+                      << text_pool->stringValue() <<
+                      ". Does not contain - (for min-max) nor / (prefix/len)");
+        }
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(PoolStorage* storage) {
+        pools_ = storage;
+    }
+
+    /// @brief does nothing.
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers (for subnet) are expected
+    /// to use values stored in the storage.
+    virtual void commit() {}
+
+    /// @brief factory that constructs PoolParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new PoolParser(param_name));
+    }
+
+protected:
+    /// @brief pointer to the actual Pools storage
+    ///
+    /// That is typically a storage somewhere in Subnet parser
+    /// (an upper level parser).
+    PoolStorage* pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class Subnet6ConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    Subnet6ConfigParser(const std::string& ) {
+        // The parameter should always be "subnet", but we don't check here
+        // against it in case some wants to reuse this parser somewhere.
+    }
+
+    /// @brief parses parameter value
+    ///
+    /// @param subnet pointer to the content of subnet definition
+    void build(ConstElementPtr subnet) {
+
+        BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+
+            ParserPtr parser(createSubnet6ConfigParser(param.first));
+
+            // if this is an Uint32 parser, tell it to store the values
+            // in values_, rather than in global storage
+            boost::shared_ptr<Uint32Parser> uintParser =
+                boost::dynamic_pointer_cast<Uint32Parser>(parser);
+            if (uintParser) {
+                uintParser->setStorage(&uint32_values_);
+            } else {
+
+                boost::shared_ptr<StringParser> stringParser =
+                    boost::dynamic_pointer_cast<StringParser>(parser);
+                if (stringParser) {
+                    stringParser->setStorage(&string_values_);
+                } else {
+
+                    boost::shared_ptr<PoolParser> poolParser =
+                        boost::dynamic_pointer_cast<PoolParser>(parser);
+                    if (poolParser) {
+                        poolParser->setStorage(&pools_);
+                    }
+                }
+            }
+
+            parser->build(param.second);
+            parsers_.push_back(parser);
+        }
+
+        // Ok, we now have subnet parsed
+    }
+
+    /// @brief commits received configuration.
+    ///
+    /// This method does most of the configuration. Many other parsers are just
+    /// storing the values that are actually consumed here. Pool definitions
+    /// created in other parsers are used here and added to newly created Subnet6
+    /// objects. Subnet6 are then added to DHCP CfgMgr.
+    void commit() {
+
+        StringStorage::const_iterator it = string_values_.find("subnet");
+        if (it == string_values_.end()) {
+            isc_throw(Dhcp6ConfigError,
+                      "Mandatory subnet definition in subnet missing");
+        }
+        string subnet_txt = it->second;
+        boost::erase_all(subnet_txt, " ");
+        boost::erase_all(subnet_txt, "\t");
+
+        size_t pos = subnet_txt.find("/");
+        if (pos == string::npos) {
+            isc_throw(Dhcp6ConfigError,
+                      "Invalid subnet syntax (prefix/len expected):" << it->second);
+        }
+        IOAddress addr(subnet_txt.substr(0, pos));
+        uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+        Triplet<uint32_t> t1 = getParam("renew-timer");
+        Triplet<uint32_t> t2 = getParam("rebind-timer");
+        Triplet<uint32_t> pref = getParam("preferred-lifetime");
+        Triplet<uint32_t> valid = getParam("valid-lifetime");
+
+        /// @todo: Convert this to logger once the parser is working reliably
+        stringstream tmp;
+        tmp << addr.toText() << "/" << (int)len
+            << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
+            << pref << ", valid=" << valid;
+
+        LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
+
+        Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+
+        for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+            subnet->addPool6(*it);
+        }
+
+        CfgMgr::instance().addSubnet6(subnet);
+    }
+
+protected:
+
+    /// @brief creates parsers for entries in subnet definition
+    ///
+    /// @todo Add subnet-specific things here (e.g. subnet-specific options)
+    ///
+    /// @param config_id name od the entry
+    /// @return parser object for specified entry name
+    DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
+        FactoryMap factories;
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "preferred-lifetime", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "valid-lifetime", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "renew-timer", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "rebind-timer", Uint32Parser::Factory));
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "subnet", StringParser::Factory));
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "pool", PoolParser::Factory));
+
+        FactoryMap::iterator f = factories.find(config_id);
+        if (f == factories.end()) {
+            // Used for debugging only.
+            // return new DebugParser(config_id);
+
+            isc_throw(NotImplemented,
+                      "Parser error: Subnet6 parameter not supported: "
+                      << config_id);
+        }
+        return (f->second(config_id));
+    }
+
+    /// @brief returns value for a given parameter (after using inheritance)
+    ///
+    /// This method implements inheritance. For a given parameter name, it first
+    /// checks if there is a global value for it and overwrites it with specific
+    /// value if such value was defined in subnet.
+    ///
+    /// @param name name of the parameter
+    /// @return triplet with the parameter name
+    Triplet<uint32_t> getParam(const std::string& name) {
+        uint32_t value = 0;
+        bool found = false;
+        Uint32Storage::iterator global = uint32_defaults.find(name);
+        if (global != uint32_defaults.end()) {
+            value = global->second;
+            found = true;
+        }
+
+        Uint32Storage::iterator local = uint32_values_.find(name);
+        if (local != uint32_values_.end()) {
+            value = local->second;
+            found = true;
+        }
+
+        if (found) {
+            return (Triplet<uint32_t>(value));
+        } else {
+            isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+                      << " missing (no global default and no subnet-"
+                      << "specific value)");
+        }
+    }
+
+    /// storage for subnet-specific uint32 values
+    Uint32Storage uint32_values_;
+
+    /// storage for subnet-specific integer values
+    StringStorage string_values_;
+
+    /// storage for pools belonging to this subnet
+    PoolStorage pools_;
+
+    /// parsers are stored here
+    ParserCollection parsers_;
+};
+
+/// @brief this class parses list of subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet6
+/// definitions. It iterates over all entries and creates Subnet6ConfigParser
+/// for each entry.
+class Subnets6ListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    Subnets6ListConfigParser(const std::string&) {
+        /// parameter name is ignored
+    }
+
+    /// @brief parses contents of the list
+    ///
+    /// Iterates over all entries on the list and creates Subnet6ConfigParser
+    /// for each entry.
+    ///
+    /// @param subnets_list pointer to a list of IPv6 subnets
+    void build(ConstElementPtr subnets_list) {
+
+        // No need to define FactoryMap here. There's only one type
+        // used: Subnet6ConfigParser
+
+        BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+
+            ParserPtr parser(new Subnet6ConfigParser("subnet"));
+            parser->build(subnet);
+            subnets_.push_back(parser);
+        }
+
+    }
+
+    /// @brief commits subnets definitions.
+    ///
+    /// Iterates over all Subnet6 parsers. Each parser contains definitions
+    /// of a single subnet and its parameters and commits each subnet separately.
+    void commit() {
+        // @todo: Implement more subtle reconfiguration than toss
+        // the old one and replace with the new one.
+
+        // remove old subnets
+        CfgMgr::instance().deleteSubnets6();
+
+        BOOST_FOREACH(ParserPtr subnet, subnets_) {
+            subnet->commit();
+        }
+
+    }
+
+    /// @brief Returns Subnet6ListConfigParser object
+    /// @param param_name name of the parameter
+    /// @return Subnets6ListConfigParser object
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new Subnets6ListConfigParser(param_name));
+    }
+
+    /// @brief collection of subnet parsers.
+    ParserCollection subnets_;
+};
+
+/// @brief creates global parsers
+///
+/// This method creates global parsers that parse global parameters, i.e.
+/// those that take format of Dhcp6/param1, Dhcp6/param2 and so forth.
+///
+/// @param config_id pointer to received global configuration entry
+/// @return parser for specified global DHCPv6 parameter
+DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
+    FactoryMap factories;
+
+    //
+    factories.insert(pair<string, ParserFactory*>(
+                         "preferred-lifetime", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "valid-lifetime", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "renew-timer", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "rebind-timer", Uint32Parser::Factory));
+
+    factories.insert(pair<string, ParserFactory*>(
+                         "interface", InterfaceListConfigParser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "subnet6", Subnets6ListConfigParser::Factory));
+
+    factories.insert(pair<string, ParserFactory*>(
+                         "version", StringParser::Factory));
+
+    FactoryMap::iterator f = factories.find(config_id);
+    if (f == factories.end()) {
+        // Used for debugging only.
+        // return new DebugParser(config_id);
+
+        isc_throw(NotImplemented,
+                  "Parser error: Global configuration parameter not supported: "
+                  << config_id);
+    }
+    return (f->second(config_id));
+}
+
+/// @brief configures DHCPv6 server
+///
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
+///
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - logical error (parsing was successful, but the values are invalid)
+///
+/// @param config_set a new configuration for DHCPv6 server
+/// @return answer that contains result of reconfiguration
+ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+    if (!config_set) {
+        isc_throw(Dhcp6ConfigError,
+                  "Null pointer is passed to configuration parser");
+    }
+
+    /// @todo: append most essential info here (like "2 new subnets configured")
+    string config_details;
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
+
+    ParserCollection parsers;
+    try {
+        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+
+            ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+            parser->build(config_pair.second);
+            parsers.push_back(parser);
+        }
+    } catch (const isc::Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed:") + ex.what());
+        return (answer);
+    } catch (...) {
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed"));
+    }
+
+    try {
+        BOOST_FOREACH(ParserPtr parser, parsers) {
+            parser->commit();
+        }
+    }
+    catch (const isc::Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed:") + ex.what());
+        return (answer);
+    } catch (...) {
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed"));
+    }
+
+    LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
+
+    ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+    return (answer);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 147 - 0
src/bin/dhcp6/config_parser.h

@@ -0,0 +1,147 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+#ifndef DHCP6_CONFIG_PARSER_H
+#define DHCP6_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+class Dhcpv6Srv;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c Dhcpv6Srv object.
+class Dhcp6ConfigError : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+Dhcp6ConfigError(const char* file, size_t line, const char* what) :
+    isc::Exception(file, line, what) {}
+};
+
+class DhcpConfigParser {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    DhcpConfigParser(const DhcpConfigParser& source);
+    DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    DhcpConfigParser() {}
+public:
+    /// The destructor.
+    virtual ~DhcpConfigParser() {}
+    //@}
+
+    /// \brief Prepare configuration value.
+    ///
+    /// This method parses the "value part" of the configuration identifier
+    /// that corresponds to this derived class and prepares a new value to
+    /// apply to the server.
+    ///
+    /// This method must validate the given value both in terms of syntax
+    /// and semantics of the configuration, so that the server will be
+    /// validly configured at the time of \c commit().  Note: the given
+    /// configuration value is normally syntactically validated, but the
+    /// \c build() implementation must also expect invalid input.  If it
+    /// detects an error it may throw an exception of a derived class
+    /// of \c isc::Exception.
+    ///
+    /// Preparing a configuration value will often require resource
+    /// allocation.  If it fails, it may throw a corresponding standard
+    /// exception.
+    ///
+    /// This method is not expected to be called more than once in the
+    /// life of the object. Although multiple calls are not prohibited
+    /// by the interface, the behavior is undefined.
+    ///
+    /// \param config_value The configuration value for the identifier
+    /// corresponding to the derived class.
+    virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+    /// \brief Apply the prepared configuration value to the server.
+    ///
+    /// This method is expected to be exception free, and, as a consequence,
+    /// it should normally not involve resource allocation.
+    /// Typically it would simply perform exception free assignment or swap
+    /// operation on the value prepared in \c build().
+    /// In some cases, however, it may be very difficult to meet this
+    /// condition in a realistic way, while the failure case should really
+    /// be very rare.  In such a case it may throw, and, if the parser is
+    /// called via \c configureDhcp6Server(), the caller will convert the
+    /// exception as a fatal error.
+    ///
+    /// This method is expected to be called after \c build(), and only once.
+    /// The result is undefined otherwise.
+    virtual void commit() = 0;
+};
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief a collection of parsers
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+
+/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function throws an exception of class \c Dhcp6ConfigError.
+/// If the given configuration requires resource allocation and it fails,
+/// a corresponding standard exception will be thrown.
+/// Other exceptions may also be thrown, depending on the implementation of
+/// the underlying derived class of \c Dhcp6ConfigError.
+/// In any case the strong guarantee is provided as described above except
+/// in the very rare cases where the \c commit() method of a parser throws
+/// an exception.  If that happens this function converts the exception
+/// into a \c FatalError exception and rethrows it.  This exception is
+/// expected to be caught at the highest level of the application to terminate
+/// the program gracefully.
+///
+/// \param server The \c Dhcpv6Srv object to be configured.
+/// \param config_set A JSON style configuration to apply to \c server.
+isc::data::ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& server,
+                     isc::data::ConstElementPtr config_set);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP6_CONFIG_PARSER_H

+ 25 - 5
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -25,6 +25,7 @@
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/spec_config.h>
 #include <dhcp6/spec_config.h>
+#include <dhcp6/config_parser.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
@@ -47,8 +48,15 @@ ConstElementPtr
 ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
 ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
               .arg(new_config->str());
               .arg(new_config->str());
-    ConstElementPtr answer = isc::config::createAnswer(0,
-                             "Thank you for sending config.");
+
+    if (server_) {
+        return (configureDhcp6Server(*server_, new_config));
+    }
+
+    // That should never happen as we install config_handler after we instantiate
+    // the server.
+    ConstElementPtr answer = isc::config::createAnswer(1,
+           "Configuration rejected, server is during startup/shutdown phase.");
     return (answer);
     return (answer);
 }
 }
 
 
@@ -86,7 +94,7 @@ void ControlledDhcpv6Srv::sessionReader(void) {
 }
 }
 
 
 void ControlledDhcpv6Srv::establishSession() {
 void ControlledDhcpv6Srv::establishSession() {
-    
+
     string specfile;
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
     if (getenv("B10_FROM_BUILD")) {
         specfile = string(getenv("B10_FROM_BUILD")) +
         specfile = string(getenv("B10_FROM_BUILD")) +
@@ -96,15 +104,27 @@ void ControlledDhcpv6Srv::establishSession() {
     }
     }
 
 
     /// @todo: Check if session is not established already. Throw, if it is.
     /// @todo: Check if session is not established already. Throw, if it is.
-    
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
               .arg(specfile);
               .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
     cc_session_ = new Session(io_service_.get_io_service());
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
-                                          dhcp6ConfigHandler,
+                                          NULL,
                                           dhcp6CommandHandler, false);
                                           dhcp6CommandHandler, false);
     config_session_->start();
     config_session_->start();
 
 
+    // We initially create ModuleCCSession() without configHandler, as
+    // the session module is too eager to send partial configuration.
+    // We want to get the full configuration, so we explicitly call
+    // getFullConfig() and then pass it to our configHandler.
+    config_session_->setConfigHandler(dhcp6ConfigHandler);
+
+    try {
+        configureDhcp6Server(*this, config_session_->getFullConfig());
+    } catch (const Dhcp6ConfigError& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
+    }
+
     /// Integrate the asynchronous I/O model of BIND 10 configuration
     /// Integrate the asynchronous I/O model of BIND 10 configuration
     /// control with the "select" model of the DHCP server.  This is
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv6Session.
     /// fully explained in \ref dhcpv6Session.

+ 79 - 0
src/bin/dhcp6/dhcp6.dox

@@ -0,0 +1,79 @@
+/**
+ @page dhcpv6 DHCPv6 Server Component
+
+ BIND10 offers DHCPv6 server implementation. It is implemented as
+ b10-dhcp6 component. Its primary code is located in
+ isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
+ especially lib::dhcp::Pkt6, isc::dhcp::Option and
+ isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ functionality, i.e. it is able to receive and process incoming
+ requests and trasmit responses. However, it does not have database
+ management, so it returns only one, hardcoded lease to whoever asks
+ for it.
+
+ DHCPv6 server component does not support relayed traffic yet, as
+ support for relay decapsulation is not implemented yet.
+
+ DHCPv6 server component does not use BIND10 logging yet.
+
+ @section dhcpv6-session BIND10 message queue integration
+
+ DHCPv4 server component is now integrated with BIND10 message queue.
+ It follows the same principle as DHCPv4. See \ref dhcpv4Session for
+ details.
+
+ @section dhcpv6-config-parser Configuration Parser in DHCPv6
+
+ b10-dhcp6 component uses BIND10 cfgmgr for commands and configuration. During
+ initial configuration (See \ref
+ isc::dhcp::ControlledDhcpv6Srv::establishSession()), the configuration handler
+ callback is installed (see isc::dhcp::ControlledDhcpv6Srv::dhcp6ConfigHandler().
+ It is called every time there is a new configuration. In particular, it is
+ called every time during daemon start process. It contains a
+ isc::data::ConstElementPtr to a new configuration.  This simple handler calls
+ \ref isc::dhcp::configureDhcp6Server() method that processes received configuration.
+
+ This method iterates over list of received configuration elements and creates a
+ list of parsers for each received entry. Parser is an object that is derived
+ from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created
+ (constructor), its value is set (using build() method). Once all parsers are
+ build, the configuration is then applied ("commited") and commit() method is
+ called.
+
+ All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
+ are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g. \ref
+ isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
+ some cases, e.g. subnet6 definitions, the configuration entry is not a simple
+ value, but a map or a list itself. In such case, the parser iterates over all
+ elements and creates parsers for a given scope. This process may be repeated
+ (sort of) recursively.
+
+ @section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
+
+ One notable useful features of DHCP configuration is its parameter inheritance.
+ For example, renew-timer value may be specified at a global scope and it then
+ applies to all subnets. However, some subnets may have it overwritten with more
+ specific values that takes precedence over global values that are considered
+ defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
+ isc::dhcp::StringParser) implement that inheritance. By default, they store
+ values in global uint32_defaults and string_defaults storages. However, it is
+ possible to instruct them to store parsed values in more specific
+ storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
+ that has its own storage that is unique for each subnet. Finally, during commit
+ phase (commit() method), appropriate parsers can use apply parameter inheritance.
+
+ Debugging configuration parser may be confusing. Therefore there is a special
+ class called \ref isc::dhcp::DummyParser. It does not configure anything, but just
+ accepts any parameter of any type. If requested to commit configuration, it will
+ print out received parameter name and its value. This class is not currently used,
+ but it is convenient to have it every time a new parameter is added to DHCP
+ configuration. For that purpose it should be left in the code.
+
+ Parameter inheritance is done during reconfiguration phase, as reconfigurations
+ are rare, so extra logic here is not a problem. On the other hand, values of
+ those parameters may be used thousands times per second, so its use must be as
+ simple as possible. In fact, currently the code has to call Subnet6->getT1() and
+ do not implement any fancy inheritance logic.
+
+ */

+ 90 - 2
src/bin/dhcp6/dhcp6.spec

@@ -4,9 +4,97 @@
     "module_description": "DHCPv6 server daemon",
     "module_description": "DHCPv6 server daemon",
     "config_data": [
     "config_data": [
       { "item_name": "interface",
       { "item_name": "interface",
-        "item_type": "string",
+        "item_type": "list",
         "item_optional": false,
         "item_optional": false,
-        "item_default": "eth0"
+        "item_default": [ "all" ],
+        "list_item_spec":
+        {
+          "item_name": "interface_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": "all"
+        }
+      } ,
+
+      { "item_name": "renew-timer",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 1000
+      },
+
+      { "item_name": "rebind-timer",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 2000
+      },
+
+      { "item_name": "preferred-lifetime",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 3000
+      },
+
+      { "item_name": "valid-lifetime",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 4000
+      },
+
+      { "item_name": "subnet6",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+            "item_name": "single-subnet6",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {},
+            "map_item_spec": [
+
+                { "item_name": "subnet",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": ""
+                },
+
+                { "item_name": "renew-timer",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 1000
+                },
+
+                { "item_name": "rebind-timer",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 2000
+                },
+
+                { "item_name": "preferred-lifetime",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 3000
+                },
+
+                { "item_name": "valid-lifetime",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 7200
+                },
+                { "item_name": "pool",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "type",
+                        "item_type": "string",
+                        "item_optional": false,
+                        "item_default": ""
+                    }
+                }
+            ]
+        }
       }
       }
     ],
     ],
     "commands": [
     "commands": [

+ 20 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -109,3 +109,23 @@ processed any command-line switches and is starting.
 This is a debug message issued during the IPv6 DHCP server startup.
 This is a debug message issued during the IPv6 DHCP server startup.
 It lists some information about the parameters with which the server
 It lists some information about the parameters with which the server
 is running.
 is running.
+
+% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial DHCPv6
+configuration has failed. The server will start, but nothing will be
+served until the configuration has been corrected.
+
+% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives a
+configuration. That happens start up and also when a server configuration
+change is committed by the administrator.
+
+% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified subnet.
+
+% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. it is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.

+ 7 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -42,6 +42,13 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
+    if (port == 0) {
+        // used for testing purposes. Some tests, e.g. configuration parser,
+        // require Dhcpv6Srv object, but they don't really need it to do
+        // anything. This speed up and simplifies the tests.
+        return;
+    }
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
 
     // First call to instance() will create IfaceMgr (it's a singleton)
     // First call to instance() will create IfaceMgr (it's a singleton)

+ 3 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -46,9 +46,11 @@ TESTS += dhcp6_unittests
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
+dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
 
 
 if USE_CLANGPP
 if USE_CLANGPP
@@ -62,6 +64,7 @@ dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

+ 243 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -0,0 +1,243 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/config_parser.h>
+#include <config/ccsession.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+class Dhcp6ParserTest : public ::testing::Test {
+public:
+    Dhcp6ParserTest()
+    :rcode_(-1) {
+        // Open port 0 means to not do anything at all. We don't want to
+        // deal with sockets here, just check if configuration handling
+        // is sane.
+        srv_ = new Dhcpv6Srv(0);
+    }
+
+    ~Dhcp6ParserTest() {
+        delete srv_;
+    };
+
+    Dhcpv6Srv* srv_;
+
+    int rcode_;
+    ConstElementPtr comment_;
+};
+
+// Goal of this test is a verification if a very simple config update
+// with just a bumped version number. That's the simplest possible
+// config update.
+TEST_F(Dhcp6ParserTest, version) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"version\": 0}")));
+
+    // returned value must be 0 (configuration accepted)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+}
+
+/// The goal of this test is to verify that the code accepts only
+/// valid commands and malformed or unsupported parameters are rejected.
+TEST_F(Dhcp6ParserTest, bogus_command) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"bogus\": 5}")));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(1, rcode_);
+}
+
+/// The goal of this test is to verify if wrongly defined subnet will
+/// be rejected. Properly defined subnet must include at least one
+/// pool definition.
+TEST_F(Dhcp6ParserTest, empty_subnet) {
+
+    ConstElementPtr status;
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
+                                      "\"preferred-lifetime\": 3000,"
+                                      "\"rebind-timer\": 2000, "
+                                      "\"renew-timer\": 1000, "
+                                      "\"subnet6\": [  ], "
+                                      "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp6ParserTest, subnet_local) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"preferred-lifetime\": 3,"
+        "    \"valid-lifetime\": 4,"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // returned value should be 0 (configuration success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1, subnet->getT1());
+    EXPECT_EQ(2, subnet->getT2());
+    EXPECT_EQ(3, subnet->getPreferred());
+    EXPECT_EQ(4, subnet->getValid());
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"4001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 2 (values error)
+    // as the pool does not belong to that subnet
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(2, rcode_);
+}
+
+// Goal of this test is to verify if pools can be defined
+// using prefix/length notation. There is no separate test for min-max
+// notation as it was tested in several previous tests.
+TEST_F(Dhcp6ParserTest, pool_prefix_len) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+};

+ 8 - 8
src/lib/cc/data.cc

@@ -57,32 +57,32 @@ Element::toWire(std::ostream& ss) const {
 }
 }
 
 
 bool
 bool
-Element::getValue(long int&) {
+Element::getValue(long int&) const {
     return (false);
     return (false);
 }
 }
 
 
 bool
 bool
-Element::getValue(double&) {
+Element::getValue(double&) const {
     return (false);
     return (false);
 }
 }
 
 
 bool
 bool
-Element::getValue(bool&) {
+Element::getValue(bool&) const {
     return (false);
     return (false);
 }
 }
 
 
 bool
 bool
-Element::getValue(std::string&) {
+Element::getValue(std::string&) const {
     return (false);
     return (false);
 }
 }
 
 
 bool
 bool
-Element::getValue(std::vector<ConstElementPtr>&) {
+Element::getValue(std::vector<ConstElementPtr>&) const {
     return (false);
     return (false);
 }
 }
 
 
 bool
 bool
-Element::getValue(std::map<std::string, ConstElementPtr>&) {
+Element::getValue(std::map<std::string, ConstElementPtr>&) const {
     return (false);
     return (false);
 }
 }
 
 
@@ -167,7 +167,7 @@ Element::find(const std::string&) const {
 }
 }
 
 
 bool
 bool
-Element::find(const std::string&, ConstElementPtr) const {
+Element::find(const std::string&, ConstElementPtr&) const {
     return (false);
     return (false);
 }
 }
 
 
@@ -812,7 +812,7 @@ MapElement::set(const std::string& key, ConstElementPtr value) {
 }
 }
 
 
 bool
 bool
-MapElement::find(const std::string& id, ConstElementPtr t) const {
+MapElement::find(const std::string& id, ConstElementPtr& t) const {
     try {
     try {
         ConstElementPtr p = find(id);
         ConstElementPtr p = find(id);
         if (p) {
         if (p) {

+ 22 - 22
src/lib/cc/data.h

@@ -71,7 +71,7 @@ public:
 /// the type in question.
 /// the type in question.
 ///
 ///
 class Element {
 class Element {
-    
+
 private:
 private:
     // technically the type could be omitted; is it useful?
     // technically the type could be omitted; is it useful?
     // should we remove it or replace it with a pure virtual
     // should we remove it or replace it with a pure virtual
@@ -112,7 +112,7 @@ public:
     /// \returns true if the other ElementPtr has the same type and
     /// \returns true if the other ElementPtr has the same type and
     ///          value
     ///          value
     virtual bool equals(const Element& other) const = 0;
     virtual bool equals(const Element& other) const = 0;
-    
+
     /// Converts the Element to JSON format and appends it to
     /// Converts the Element to JSON format and appends it to
     /// the given stringstream.
     /// the given stringstream.
     virtual void toJSON(std::ostream& ss) const = 0;
     virtual void toJSON(std::ostream& ss) const = 0;
@@ -152,12 +152,12 @@ public:
     /// data to the given reference and returning true
     /// data to the given reference and returning true
     ///
     ///
     //@{
     //@{
-    virtual bool getValue(long int& t);
-    virtual bool getValue(double& t);
-    virtual bool getValue(bool& t);
-    virtual bool getValue(std::string& t);
-    virtual bool getValue(std::vector<ConstElementPtr>& t);
-    virtual bool getValue(std::map<std::string, ConstElementPtr>& t);
+    virtual bool getValue(long int& t) const;
+    virtual bool getValue(double& t) const;
+    virtual bool getValue(bool& t) const;
+    virtual bool getValue(std::string& t) const;
+    virtual bool getValue(std::vector<ConstElementPtr>& t) const;
+    virtual bool getValue(std::map<std::string, ConstElementPtr>& t) const;
     //@}
     //@}
 
 
     ///
     ///
@@ -209,7 +209,7 @@ public:
     virtual size_t size() const;
     virtual size_t size() const;
     //@}
     //@}
 
 
-    
+
     /// \name MapElement functions
     /// \name MapElement functions
     ///
     ///
     /// \brief If the Element on which these functions are called are not
     /// \brief If the Element on which these functions are called are not
@@ -253,12 +253,12 @@ public:
     /// \param identifier The identifier of the element to find
     /// \param identifier The identifier of the element to find
     /// \param t Reference to store the resulting ElementPtr, if found.
     /// \param t Reference to store the resulting ElementPtr, if found.
     /// \return true if the element was found, false if not.
     /// \return true if the element was found, false if not.
-    virtual bool find(const std::string& identifier, ConstElementPtr t) const;
+    virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
     //@}
     //@}
 
 
 
 
     /// \name Factory functions
     /// \name Factory functions
-    
+
     // TODO: should we move all factory functions to a different class
     // TODO: should we move all factory functions to a different class
     // so as not to burden the Element base with too many functions?
     // so as not to burden the Element base with too many functions?
     // and/or perhaps even to a separate header?
     // and/or perhaps even to a separate header?
@@ -349,7 +349,7 @@ public:
     /// These function pparse the wireformat at the given stringstream
     /// These function pparse the wireformat at the given stringstream
     /// (of the given length). If there is a parse error an exception
     /// (of the given length). If there is a parse error an exception
     /// of the type isc::cc::DecodeError is raised.
     /// of the type isc::cc::DecodeError is raised.
-    
+
     //@{
     //@{
     /// Creates an Element from the wire format in the given
     /// Creates an Element from the wire format in the given
     /// stringstream of the given length.
     /// stringstream of the given length.
@@ -378,7 +378,7 @@ public:
     IntElement(long int v) : Element(integer), i(v) { }
     IntElement(long int v) : Element(integer), i(v) { }
     long int intValue() const { return (i); }
     long int intValue() const { return (i); }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(long int& t) { t = i; return (true); }
+    bool getValue(long int& t) const { t = i; return (true); }
     using Element::setValue;
     using Element::setValue;
     bool setValue(const long int v) { i = v; return (true); }
     bool setValue(const long int v) { i = v; return (true); }
     void toJSON(std::ostream& ss) const;
     void toJSON(std::ostream& ss) const;
@@ -392,7 +392,7 @@ public:
     DoubleElement(double v) : Element(real), d(v) {};
     DoubleElement(double v) : Element(real), d(v) {};
     double doubleValue() const { return (d); }
     double doubleValue() const { return (d); }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(double& t) { t = d; return (true); }
+    bool getValue(double& t) const { t = d; return (true); }
     using Element::setValue;
     using Element::setValue;
     bool setValue(const double v) { d = v; return (true); }
     bool setValue(const double v) { d = v; return (true); }
     void toJSON(std::ostream& ss) const;
     void toJSON(std::ostream& ss) const;
@@ -406,7 +406,7 @@ public:
     BoolElement(const bool v) : Element(boolean), b(v) {};
     BoolElement(const bool v) : Element(boolean), b(v) {};
     bool boolValue() const { return (b); }
     bool boolValue() const { return (b); }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(bool& t) { t = b; return (true); }
+    bool getValue(bool& t) const { t = b; return (true); }
     using Element::setValue;
     using Element::setValue;
     bool setValue(const bool v) { b = v; return (true); }
     bool setValue(const bool v) { b = v; return (true); }
     void toJSON(std::ostream& ss) const;
     void toJSON(std::ostream& ss) const;
@@ -427,7 +427,7 @@ public:
     StringElement(std::string v) : Element(string), s(v) {};
     StringElement(std::string v) : Element(string), s(v) {};
     std::string stringValue() const { return (s); }
     std::string stringValue() const { return (s); }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(std::string& t) { t = s; return (true); }
+    bool getValue(std::string& t) const { t = s; return (true); }
     using Element::setValue;
     using Element::setValue;
     bool setValue(const std::string& v) { s = v; return (true); }
     bool setValue(const std::string& v) { s = v; return (true); }
     void toJSON(std::ostream& ss) const;
     void toJSON(std::ostream& ss) const;
@@ -441,7 +441,7 @@ public:
     ListElement() : Element(list) {}
     ListElement() : Element(list) {}
     const std::vector<ConstElementPtr>& listValue() const { return (l); }
     const std::vector<ConstElementPtr>& listValue() const { return (l); }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(std::vector<ConstElementPtr>& t) {
+    bool getValue(std::vector<ConstElementPtr>& t) const {
         t = l;
         t = l;
         return (true);
         return (true);
     }
     }
@@ -474,7 +474,7 @@ public:
         return (m);
         return (m);
     }
     }
     using Element::getValue;
     using Element::getValue;
-    bool getValue(std::map<std::string, ConstElementPtr>& t) {
+    bool getValue(std::map<std::string, ConstElementPtr>& t) const {
         t = m;
         t = m;
         return (true);
         return (true);
     }
     }
@@ -495,7 +495,7 @@ public:
         return (m.find(s) != m.end());
         return (m.find(s) != m.end());
     }
     }
     void toJSON(std::ostream& ss) const;
     void toJSON(std::ostream& ss) const;
-    
+
     // we should name the two finds better...
     // we should name the two finds better...
     // find the element at id; raises TypeError if one of the
     // find the element at id; raises TypeError if one of the
     // elements at path except the one we're looking for is not a
     // elements at path except the one we're looking for is not a
@@ -507,7 +507,7 @@ public:
     // returns true if found, or false if not found (either because
     // returns true if found, or false if not found (either because
     // it doesnt exist or one of the elements in the path is not
     // it doesnt exist or one of the elements in the path is not
     // a MapElement)
     // a MapElement)
-    bool find(const std::string& id, ConstElementPtr t) const;
+    bool find(const std::string& id, ConstElementPtr& t) const;
 
 
     bool equals(const Element& other) const;
     bool equals(const Element& other) const;
 };
 };
@@ -569,6 +569,6 @@ bool operator!=(const Element& a, const Element& b);
 } }
 } }
 #endif // _ISC_DATA_H
 #endif // _ISC_DATA_H
 
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
 // mode: c++
-// End: 
+// End:

+ 197 - 52
src/lib/cc/tests/data_unittests.cc

@@ -112,7 +112,7 @@ TEST(Element, from_and_to_json) {
         std::string s = std::string(pe.what());
         std::string s = std::string(pe.what());
         EXPECT_EQ("String expected in <string>:1:3", s);
         EXPECT_EQ("String expected in <string>:1:3", s);
     }
     }
-    
+
     sv.clear();
     sv.clear();
     sv.push_back("{1}");
     sv.push_back("{1}");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
@@ -172,14 +172,14 @@ TEST(Element, from_and_to_json) {
 
 
 }
 }
 
 
-TEST(Element, create_and_value_throws) {
-    // this test checks whether elements throw exceptions if the
-    // incorrect type is requested
-    ElementPtr el;
+template <typename T>
+void
+testGetValueInt() {
+    T el;
     long int i;
     long int i;
     double d;
     double d;
     bool b;
     bool b;
-    std::string s("asdf");
+    std::string s;
     std::vector<ConstElementPtr> v;
     std::vector<ConstElementPtr> v;
     std::map<std::string, ConstElementPtr> m;
     std::map<std::string, ConstElementPtr> m;
 
 
@@ -196,27 +196,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(i, 1);
-    i = 2;
-    EXPECT_TRUE(el->setValue(i));
-    EXPECT_EQ(2, el->intValue());
-    EXPECT_FALSE(el->setValue(d));
-    EXPECT_FALSE(el->setValue(b));
-    EXPECT_FALSE(el->setValue(s));
-    EXPECT_FALSE(el->setValue(v));
-    EXPECT_FALSE(el->setValue(m));
-    EXPECT_THROW(el->get(1), TypeError);
-    EXPECT_THROW(el->set(1, el), TypeError);
-    EXPECT_THROW(el->add(el), TypeError);
-    EXPECT_THROW(el->remove(1), TypeError);
-    EXPECT_THROW(el->size(), TypeError);
-    EXPECT_THROW(el->get("foo"), TypeError);
-    EXPECT_THROW(el->set("foo", el), TypeError);
-    EXPECT_THROW(el->remove("foo"), TypeError);
-    EXPECT_THROW(el->contains("foo"), TypeError);
-    ConstElementPtr tmp;
-    EXPECT_FALSE(el->find("foo", tmp));
-    
+    EXPECT_EQ(1, i);
+}
+
+template <typename T>
+void
+testGetValueDouble() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
 
     el = Element::create(1.1);
     el = Element::create(1.1);
     EXPECT_THROW(el->intValue(), TypeError);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -231,15 +223,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(d, 1.1);
-    d = 2.2;
-    EXPECT_TRUE(el->setValue(d));
-    EXPECT_EQ(2.2, el->doubleValue());
-    EXPECT_FALSE(el->setValue(i));
-    EXPECT_FALSE(el->setValue(b));
-    EXPECT_FALSE(el->setValue(s));
-    EXPECT_FALSE(el->setValue(v));
-    EXPECT_FALSE(el->setValue(m));
+    EXPECT_EQ(1.1, d);
+}
+
+template <typename T>
+void
+testGetValueBool() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
 
     el = Element::create(true);
     el = Element::create(true);
     EXPECT_THROW(el->intValue(), TypeError);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -254,10 +250,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(b, true);
-    b = false;
-    EXPECT_TRUE(el->setValue(b));
-    EXPECT_FALSE(el->boolValue());
+    EXPECT_EQ(true, b);
+}
+
+template <typename T>
+void
+testGetValueString() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
 
     el = Element::create("foo");
     el = Element::create("foo");
     EXPECT_THROW(el->intValue(), TypeError);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -272,10 +277,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_TRUE(el->getValue(s));
     EXPECT_TRUE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(s, "foo");
-    s = "bar";
-    EXPECT_TRUE(el->setValue(s));
-    EXPECT_EQ("bar", el->stringValue());
+    EXPECT_EQ("foo", s);
+}
+
+template <typename T>
+void
+testGetValueList() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
 
     el = Element::createList();
     el = Element::createList();
     EXPECT_THROW(el->intValue(), TypeError);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -291,9 +305,18 @@ TEST(Element, create_and_value_throws) {
     EXPECT_TRUE(el->getValue(v));
     EXPECT_TRUE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_EQ("[  ]", el->str());
     EXPECT_EQ("[  ]", el->str());
-    v.push_back(Element::create(1));
-    EXPECT_TRUE(el->setValue(v));
-    EXPECT_EQ("[ 1 ]", el->str());
+}
+
+template <typename T>
+void
+testGetValueMap() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
 
     el = Element::createMap();
     el = Element::createMap();
     EXPECT_THROW(el->intValue(), TypeError);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -308,7 +331,128 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_TRUE(el->getValue(m));
     EXPECT_TRUE(el->getValue(m));
+    EXPECT_EQ("{  }", el->str());
+}
+
+TEST(Element, create_and_value_throws) {
+    // this test checks whether elements throw exceptions if the
+    // incorrect type is requested
+    ElementPtr el;
+    ConstElementPtr cel;
+    long int i = 0;
+    double d = 0.0;
+    bool b = false;
+    std::string s("asdf");
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
+    ConstElementPtr tmp;
+
+    testGetValueInt<ElementPtr>();
+    testGetValueInt<ConstElementPtr>();
+
+    el = Element::create(1);
+    i = 2;
+    EXPECT_TRUE(el->setValue(i));
+    EXPECT_EQ(2, el->intValue());
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueDouble<ElementPtr>();
+    testGetValueDouble<ConstElementPtr>();
+
+    el = Element::create(1.1);
+    d = 2.2;
+    EXPECT_TRUE(el->setValue(d));
+    EXPECT_EQ(2.2, el->doubleValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueBool<ElementPtr>();
+    testGetValueBool<ConstElementPtr>();
 
 
+    el = Element::create(true);
+    b = false;
+    EXPECT_TRUE(el->setValue(b));
+    EXPECT_FALSE(el->boolValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueString<ElementPtr>();
+    testGetValueString<ConstElementPtr>();
+
+    el = Element::create("foo");
+    s = "bar";
+    EXPECT_TRUE(el->setValue(s));
+    EXPECT_EQ("bar", el->stringValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueList<ElementPtr>();
+    testGetValueList<ConstElementPtr>();
+
+    el = Element::createList();
+    v.push_back(Element::create(1));
+    EXPECT_TRUE(el->setValue(v));
+    EXPECT_EQ("[ 1 ]", el->str());
+
+    testGetValueMap<ElementPtr>();
+    testGetValueMap<ConstElementPtr>();
+
+    el = Element::createMap();
+    EXPECT_NO_THROW(el->set("foo", Element::create("bar")));
+    EXPECT_EQ("{ \"foo\": \"bar\" }", el->str());
 }
 }
 
 
 // Helper for escape check; it puts the given string in a StringElement,
 // Helper for escape check; it puts the given string in a StringElement,
@@ -382,7 +526,7 @@ TEST(Element, MapElement) {
     // this function checks the specific functions for ListElements
     // this function checks the specific functions for ListElements
     ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }");
     ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }");
     ConstElementPtr el2;
     ConstElementPtr el2;
-    
+
     EXPECT_EQ(el->get("name")->stringValue(), "foo");
     EXPECT_EQ(el->get("name")->stringValue(), "foo");
     EXPECT_EQ(el->get("value2")->getType(), Element::map);
     EXPECT_EQ(el->get("value2")->getType(), Element::map);
 
 
@@ -396,11 +540,12 @@ TEST(Element, MapElement) {
 
 
     EXPECT_EQ(el->find("value2/number")->intValue(), 42);
     EXPECT_EQ(el->find("value2/number")->intValue(), 42);
     EXPECT_TRUE(isNull(el->find("value2/nothing/")));
     EXPECT_TRUE(isNull(el->find("value2/nothing/")));
-   
+
     EXPECT_EQ(el->find("value1")->stringValue(), "bar");
     EXPECT_EQ(el->find("value1")->stringValue(), "bar");
     EXPECT_EQ(el->find("value1/")->stringValue(), "bar");
     EXPECT_EQ(el->find("value1/")->stringValue(), "bar");
-    
+
     EXPECT_TRUE(el->find("value1", el2));
     EXPECT_TRUE(el->find("value1", el2));
+    EXPECT_EQ("bar", el2->stringValue());
     EXPECT_FALSE(el->find("name/error", el2));
     EXPECT_FALSE(el->find("name/error", el2));
 
 
     // A map element whose (only) element has the maximum length of tag.
     // A map element whose (only) element has the maximum length of tag.
@@ -410,7 +555,7 @@ TEST(Element, MapElement) {
                        "9123456789abcdefa123456789abcdefb123456789abcdef"
                        "9123456789abcdefa123456789abcdefb123456789abcdef"
                        "c123456789abcdefd123456789abcdefe123456789abcdef"
                        "c123456789abcdefd123456789abcdefe123456789abcdef"
                        "f123456789abcde");
                        "f123456789abcde");
-    
+
     EXPECT_EQ(255, long_maptag.length()); // check prerequisite
     EXPECT_EQ(255, long_maptag.length()); // check prerequisite
     el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
     el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
     EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
     EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
@@ -689,7 +834,7 @@ TEST(Element, merge) {
     c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     merge(b, a);
     merge(b, a);
     EXPECT_EQ(*b, *c);
     EXPECT_EQ(*b, *c);
-    
+
     // And some tests with multiple values
     // And some tests with multiple values
     a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
     a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
     b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
     b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");

+ 9 - 63
src/lib/datasrc/memory/domaintree.h

@@ -947,7 +947,7 @@ public:
         PARTIALMATCH, ///< A superdomain node was found
         PARTIALMATCH, ///< A superdomain node was found
         NOTFOUND,   ///< Not even any superdomain was found
         NOTFOUND,   ///< Not even any superdomain was found
         /// \brief Returned by insert() if a node of the name already exists
         /// \brief Returned by insert() if a node of the name already exists
-        ALREADYEXISTS,
+        ALREADYEXISTS
     };
     };
 
 
     /// \brief Allocate and construct \c DomainTree
     /// \brief Allocate and construct \c DomainTree
@@ -1080,55 +1080,25 @@ public:
     ///    of it. In that case, node parameter is left intact.
     ///    of it. In that case, node parameter is left intact.
     //@{
     //@{
 
 
-    /// \brief Simple find.
+    /// \brief Simple find
     ///
     ///
     /// Acts as described in the \ref find section.
     /// Acts as described in the \ref find section.
     Result find(const isc::dns::Name& name,
     Result find(const isc::dns::Name& name,
-                DomainTreeNode<T>** node) const {
-        DomainTreeNodeChain<T> node_path;
-        const isc::dns::LabelSequence ls(name);
-        return (find<void*>(ls, node, node_path, NULL, NULL));
-    }
-
-    /// \brief Simple find returning immutable node.
-    ///
-    /// Acts as described in the \ref find section, but returns immutable node
-    /// pointer.
-    Result find(const isc::dns::Name& name,
                 const DomainTreeNode<T>** node) const {
                 const DomainTreeNode<T>** node) const {
         DomainTreeNodeChain<T> node_path;
         DomainTreeNodeChain<T> node_path;
-        DomainTreeNode<T> *target_node = NULL;
         const isc::dns::LabelSequence ls(name);
         const isc::dns::LabelSequence ls(name);
-        Result ret = (find<void*>(ls, &target_node, node_path, NULL, NULL));
-        if (ret != NOTFOUND) {
-            *node = target_node;
-        }
+        Result ret = (find<void*>(ls, node, node_path, NULL, NULL));
         return (ret);
         return (ret);
     }
     }
 
 
     /// \brief Simple find, with node_path tracking
     /// \brief Simple find, with node_path tracking
     ///
     ///
     /// Acts as described in the \ref find section.
     /// Acts as described in the \ref find section.
-    Result find(const isc::dns::Name& name, DomainTreeNode<T>** node,
-                DomainTreeNodeChain<T>& node_path) const
-    {
-        const isc::dns::LabelSequence ls(name);
-        return (find<void*>(ls, node, node_path, NULL, NULL));
-    }
-
-    /// \brief Simple find returning immutable node, with node_path tracking
-    ///
-    /// Acts as described in the \ref find section, but returns immutable node
-    /// pointer.
     Result find(const isc::dns::Name& name, const DomainTreeNode<T>** node,
     Result find(const isc::dns::Name& name, const DomainTreeNode<T>** node,
                 DomainTreeNodeChain<T>& node_path) const
                 DomainTreeNodeChain<T>& node_path) const
     {
     {
-        DomainTreeNode<T> *target_node = NULL;
         const isc::dns::LabelSequence ls(name);
         const isc::dns::LabelSequence ls(name);
-        Result ret = (find<void*>(ls, &target_node, node_path, NULL, NULL));
-        if (ret != NOTFOUND) {
-            *node = target_node;
-        }
+        Result ret = (find<void*>(ls, node, node_path, NULL, NULL));
         return (ret);
         return (ret);
     }
     }
 
 
@@ -1143,13 +1113,9 @@ public:
                 bool (*callback)(const DomainTreeNode<T>&, CBARG),
                 bool (*callback)(const DomainTreeNode<T>&, CBARG),
                 CBARG callback_arg) const
                 CBARG callback_arg) const
     {
     {
-        DomainTreeNode<T>* target_node = NULL;
         const isc::dns::LabelSequence ls(name);
         const isc::dns::LabelSequence ls(name);
-        Result ret = find(ls, &target_node, node_path, callback,
+        Result ret = find(ls, node, node_path, callback,
                           callback_arg);
                           callback_arg);
-        if (ret != NOTFOUND) {
-            *node = target_node;
-        }
         return (ret);
         return (ret);
     }
     }
 
 
@@ -1229,30 +1195,10 @@ public:
     ///     \c true, it returns immediately with the current node.
     ///     \c true, it returns immediately with the current node.
     template <typename CBARG>
     template <typename CBARG>
     Result find(const isc::dns::LabelSequence& target_labels_orig,
     Result find(const isc::dns::LabelSequence& target_labels_orig,
-                DomainTreeNode<T>** node,
-                DomainTreeNodeChain<T>& node_path,
-                bool (*callback)(const DomainTreeNode<T>&, CBARG),
-                CBARG callback_arg) const;
-
-    /// \brief Simple find returning immutable node.
-    ///
-    /// Acts as described in the \ref find section, but returns immutable
-    /// node pointer.
-    template <typename CBARG>
-    Result find(const isc::dns::LabelSequence& target_labels,
                 const DomainTreeNode<T>** node,
                 const DomainTreeNode<T>** node,
                 DomainTreeNodeChain<T>& node_path,
                 DomainTreeNodeChain<T>& node_path,
                 bool (*callback)(const DomainTreeNode<T>&, CBARG),
                 bool (*callback)(const DomainTreeNode<T>&, CBARG),
-                CBARG callback_arg) const
-    {
-        DomainTreeNode<T>* target_node = NULL;
-        Result ret = find(target_labels, &target_node, node_path,
-                          callback, callback_arg);
-        if (ret != NOTFOUND) {
-            *node = target_node;
-        }
-        return (ret);
-    }
+                CBARG callback_arg) const;
     //@}
     //@}
 
 
     /// \brief return the next bigger node in DNSSEC order from a given node
     /// \brief return the next bigger node in DNSSEC order from a given node
@@ -1515,7 +1461,7 @@ template <typename T>
 template <typename CBARG>
 template <typename CBARG>
 typename DomainTree<T>::Result
 typename DomainTree<T>::Result
 DomainTree<T>::find(const isc::dns::LabelSequence& target_labels_orig,
 DomainTree<T>::find(const isc::dns::LabelSequence& target_labels_orig,
-                    DomainTreeNode<T>** target,
+                    const DomainTreeNode<T>** target,
                     DomainTreeNodeChain<T>& node_path,
                     DomainTreeNodeChain<T>& node_path,
                     bool (*callback)(const DomainTreeNode<T>&, CBARG),
                     bool (*callback)(const DomainTreeNode<T>&, CBARG),
                     CBARG callback_arg) const
                     CBARG callback_arg) const
@@ -1526,11 +1472,11 @@ DomainTree<T>::find(const isc::dns::LabelSequence& target_labels_orig,
                   " and label sequence");
                   " and label sequence");
     }
     }
 
 
-    DomainTreeNode<T>* node;
+    const DomainTreeNode<T>* node;
 
 
     if (!node_path.isEmpty()) {
     if (!node_path.isEmpty()) {
         // Get the top node in the node chain
         // Get the top node in the node chain
-        node = const_cast<DomainTreeNode<T>*>(node_path.top());
+        node = node_path.top();
         // Start searching from its down pointer
         // Start searching from its down pointer
         node = node->getDown();
         node = node->getDown();
     } else {
     } else {

+ 10 - 31
src/lib/datasrc/memory/memory_client.cc

@@ -34,7 +34,6 @@
 #include <dns/nsec3hash.h>
 #include <dns/nsec3hash.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
-#include <dns/rrsetlist.h>
 #include <dns/masterload.h>
 #include <dns/masterload.h>
 
 
 #include <boost/function.hpp>
 #include <boost/function.hpp>
@@ -627,22 +626,20 @@ InMemoryClient::InMemoryClientImpl::load(
     // node must point to a valid node now
     // node must point to a valid node now
     assert(node != NULL);
     assert(node != NULL);
 
 
-    std::string* tstr = node->setData(new std::string(filename));
+    const std::string* tstr = node->setData(new std::string(filename));
     delete tstr;
     delete tstr;
 
 
-    ZoneTable::AddResult result(zone_table_->addZone(mem_sgmt_, rrclass_,
-                                                     zone_name));
+    const ZoneTable::AddResult result(zone_table_->addZone(mem_sgmt_, rrclass_,
+                                                           zone_name,
+                                                           holder.release()));
     if (result.code == result::SUCCESS) {
     if (result.code == result::SUCCESS) {
         // Only increment the zone count if the zone doesn't already
         // Only increment the zone count if the zone doesn't already
         // exist.
         // exist.
         ++zone_count_;
         ++zone_count_;
     }
     }
-
-    ZoneTable::FindResult fr(zone_table_->setZoneData(zone_name,
-                                                      holder.release()));
-    assert(fr.code == result::SUCCESS);
-    if (fr.zone_data != NULL) {
-        ZoneData::destroy(mem_sgmt_, fr.zone_data, rrclass_);
+    // Destroy the old instance of the zone if there was any
+    if (result.zone_data != NULL) {
+        ZoneData::destroy(mem_sgmt_, result.zone_data, rrclass_);
     }
     }
 
 
     return (result.code);
     return (result.code);
@@ -732,9 +729,9 @@ InMemoryClient::load(const isc::dns::Name& zone_name,
 
 
 const std::string
 const std::string
 InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
 InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
-    FileNameNode* node(NULL);
-    FileNameTree::Result result = impl_->file_name_tree_->find(zone_name,
-                                                               &node);
+    const FileNameNode* node(NULL);
+    const FileNameTree::Result result = impl_->file_name_tree_->find(zone_name,
+                                                                     &node);
     if (result == FileNameTree::EXACTMATCH) {
     if (result == FileNameTree::EXACTMATCH) {
         return (*node->getData());
         return (*node->getData());
     } else {
     } else {
@@ -742,24 +739,6 @@ InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
     }
     }
 }
 }
 
 
-result::Result
-InMemoryClient::add(const isc::dns::Name& zone_name,
-                    const ConstRRsetPtr& rrset)
-{
-    const ZoneTable::FindResult result =
-        impl_->zone_table_->findZone(zone_name);
-    if (result.code != result::SUCCESS) {
-        isc_throw(DataSourceError, "No such zone: " + zone_name.toText());
-    }
-
-    const ConstRRsetPtr sig_rrset =
-        rrset ? rrset->getRRsig() : ConstRRsetPtr();
-    impl_->add(rrset, sig_rrset, zone_name, *result.zone_data);
-
-    // add() doesn't allow duplicate add, so we always return SUCCESS.
-    return (result::SUCCESS);
-}
-
 namespace {
 namespace {
 
 
 class MemoryIterator : public ZoneIterator {
 class MemoryIterator : public ZoneIterator {

+ 0 - 29
src/lib/datasrc/memory/memory_client.h

@@ -139,35 +139,6 @@ public:
     /// zone from a file before.
     /// zone from a file before.
     const std::string getFileName(const isc::dns::Name& zone_name) const;
     const std::string getFileName(const isc::dns::Name& zone_name) const;
 
 
-    /// \brief Inserts an rrset into the zone.
-    ///
-    /// It puts another RRset into the zone.
-    ///
-    /// In the current implementation, this method doesn't allow an existing
-    /// RRset to be updated or overridden.  So the caller must make sure that
-    /// all RRs of the same type and name must be given in the form of a
-    /// single RRset.  The current implementation will also require that
-    /// when an RRSIG is added, the RRset to be covered has already been
-    /// added.  These restrictions are probably too strict when this data
-    /// source accepts various forms of input, so they should be revisited
-    /// later.
-    ///
-    /// Except for NullRRset and OutOfZone, this method does not guarantee
-    /// strong exception safety (it is currently not needed, if it is needed
-    /// in future, it should be implemented).
-    ///
-    /// \throw NullRRset \c rrset is a NULL pointer.
-    /// \throw OutOfZone The owner name of \c rrset is outside of the
-    /// origin of the zone.
-    /// \throw AddError Other general errors.
-    /// \throw Others This method might throw standard allocation exceptions.
-    ///
-    /// \param rrset The set to add.
-    /// \return SUCCESS or EXIST (if an rrset for given name and type already
-    ///    exists).
-    result::Result add(const isc::dns::Name& zone_name,
-                       const isc::dns::ConstRRsetPtr& rrset);
-
     /// \brief RRset is NULL exception.
     /// \brief RRset is NULL exception.
     ///
     ///
     /// This is thrown if the provided RRset parameter is NULL.
     /// This is thrown if the provided RRset parameter is NULL.

+ 1 - 1
src/lib/datasrc/memory/zone_finder.cc

@@ -428,7 +428,7 @@ FindNodeResult findNode(const ZoneData& zone_data,
                         ZoneFinder::FindOptions options,
                         ZoneFinder::FindOptions options,
                         bool out_of_zone_ok = false)
                         bool out_of_zone_ok = false)
 {
 {
-    ZoneNode* node = NULL;
+    const ZoneNode* node = NULL;
     FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
     FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
 
 
     const ZoneTree& tree(zone_data.getZoneTree());
     const ZoneTree& tree(zone_data.getZoneTree());

+ 13 - 31
src/lib/datasrc/memory/zone_table.cc

@@ -69,17 +69,13 @@ ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable,
 
 
 ZoneTable::AddResult
 ZoneTable::AddResult
 ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
 ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
-                   const Name& zone_name)
+                   const Name& zone_name, ZoneData* content)
 {
 {
-    // Create a new ZoneData instance first.  If the specified name already
-    // exists in the table, the new data will soon be destroyed, but we want
-    // to make sure if this allocation fails the tree won't be changed to
-    // provide as strong guarantee as possible.  In practice, we generally
-    // expect the caller tries to add a zone only when it's a new one, so
-    // this should be a minor concern.
-    SegmentObjectHolder<ZoneData, RRClass> holder(
-        mem_sgmt, ZoneData::create(mem_sgmt, zone_name), zone_class);
-
+    if (content == NULL) {
+        isc_throw(isc::BadValue, "Zone content must not be NULL");
+    }
+    SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, content,
+                                                  zone_class);
     // Get the node where we put the zone
     // Get the node where we put the zone
     ZoneTableNode* node(NULL);
     ZoneTableNode* node(NULL);
     switch (zones_->insert(mem_sgmt, zone_name, &node)) {
     switch (zones_->insert(mem_sgmt, zone_name, &node)) {
@@ -94,18 +90,18 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
     // Can Not Happen
     // Can Not Happen
     assert(node != NULL);
     assert(node != NULL);
 
 
-    // Is it empty? We either just created it or it might be nonterminal
-    if (node->isEmpty()) {
-        node->setData(holder.get());
-        return (AddResult(result::SUCCESS, holder.release()));
-    } else { // There's something there already
-        return (AddResult(result::EXIST, node->getData()));
+    // We can release now, setData never throws
+    ZoneData* old = node->setData(holder.release());
+    if (old != NULL) {
+        return (AddResult(result::EXIST, old));
+    } else {
+        return (AddResult(result::SUCCESS, NULL));
     }
     }
 }
 }
 
 
 ZoneTable::FindResult
 ZoneTable::FindResult
 ZoneTable::findZone(const Name& name) const {
 ZoneTable::findZone(const Name& name) const {
-    ZoneTableNode* node(NULL);
+    const ZoneTableNode* node(NULL);
     result::Result my_result;
     result::Result my_result;
 
 
     // Translate the return codes
     // Translate the return codes
@@ -132,20 +128,6 @@ ZoneTable::findZone(const Name& name) const {
     return (FindResult(my_result, node->getData()));
     return (FindResult(my_result, node->getData()));
 }
 }
 
 
-ZoneTable::FindResult
-ZoneTable::setZoneData(const Name& name, ZoneData* data)
-{
-    ZoneTableNode* node(NULL);
-
-    ZoneTableTree::Result result(zones_->find(name, &node));
-
-    if (result != ZoneTableTree::EXACTMATCH) {
-        return (FindResult(result::NOTFOUND, NULL));
-    } else {
-        return (FindResult(result::SUCCESS, node->setData(data)));
-    }
-}
-
 } // end of namespace memory
 } // end of namespace memory
 } // end of namespace datasrc
 } // end of namespace datasrc
 } // end of namespace isc
 } // end of namespace isc

+ 26 - 37
src/lib/datasrc/memory/zone_table.h

@@ -74,23 +74,23 @@ private:
     typedef DomainTreeNode<ZoneData> ZoneTableNode;
     typedef DomainTreeNode<ZoneData> ZoneTableNode;
 
 
 public:
 public:
-    /// \brief Result data of addZone() method.
-    struct AddResult {
-        AddResult(result::Result param_code, ZoneData* param_zone_data) :
-            code(param_code), zone_data(param_zone_data)
-        {}
-        const result::Result code;
-        ZoneData* const zone_data;
-    };
+     /// \brief Result data of addZone() method.
+     struct AddResult {
+         AddResult(result::Result param_code, ZoneData* param_zone_data) :
+             code(param_code), zone_data(param_zone_data)
+         {}
+         const result::Result code;
+         ZoneData* const zone_data;
+     };
 
 
     /// \brief Result data of findZone() method.
     /// \brief Result data of findZone() method.
     struct FindResult {
     struct FindResult {
         FindResult(result::Result param_code,
         FindResult(result::Result param_code,
-                   ZoneData* param_zone_data) :
+                   const ZoneData* param_zone_data) :
             code(param_code), zone_data(param_zone_data)
             code(param_code), zone_data(param_zone_data)
         {}
         {}
         const result::Result code;
         const result::Result code;
-        ZoneData* const zone_data;
+        const ZoneData* const zone_data;
     };
     };
 
 
 private:
 private:
@@ -140,30 +140,29 @@ public:
 
 
     /// Add a new zone to the \c ZoneTable.
     /// Add a new zone to the \c ZoneTable.
     ///
     ///
-    /// This method creates a new \c ZoneData for the given zone name and
-    /// holds it in the internal table.  The newly created zone data will be
-    /// returned via the \c zone_data member of the return value.  If the given
-    /// zone name already exists in the table, a new data object won't be
-    /// created; instead, the existing corresponding data will be returned.
-    ///
-    /// The zone table keeps the ownership of the created zone data; the
-    /// caller must not try to destroy it directly.  (We'll eventually
-    /// add an interface to delete specific zone data from the table).
+    /// This method adds a given zone data to the internal table.
     ///
     ///
     /// \throw std::bad_alloc Internal resource allocation fails.
     /// \throw std::bad_alloc Internal resource allocation fails.
     ///
     ///
     /// \param mem_sgmt The \c MemorySegment to allocate zone data to be
     /// \param mem_sgmt The \c MemorySegment to allocate zone data to be
-    /// created.  It must be the same segment that was used to create
-    /// the zone table at the time of create().
+    ///     created.  It must be the same segment that was used to create
+    ///     the zone table at the time of create().
     /// \param zone_name The name of the zone to be added.
     /// \param zone_name The name of the zone to be added.
     /// \param zone_class The RR class of the zone.  It must be the RR class
     /// \param zone_class The RR class of the zone.  It must be the RR class
-    /// that is supposed to be associated to the zone table.
+    ///     that is supposed to be associated to the zone table.
+    /// \param content This one should hold the zone content (the ZoneData).
+    ///     The ownership is passed onto the zone table. Must not be null.
+    ///     Must correspond to the name and class and must be allocated from
+    ///     mem_sgmt.
     /// \return \c result::SUCCESS If the zone is successfully
     /// \return \c result::SUCCESS If the zone is successfully
-    /// added to the zone table.
-    /// \return \c result::EXIST The zone table already contains
-    /// zone of the same origin.
-    AddResult addZone(util::MemorySegment& mem_sgmt, dns::RRClass zone_class,
-                      const dns::Name& zone_name);
+    ///     added to the zone table.
+    /// \return \c result::EXIST The zone table already contained
+    ///     zone of the same origin. The old data is replaced and returned
+    ///     inside the result.
+    AddResult addZone(util::MemorySegment& mem_sgmt,
+                      dns::RRClass zone_class,
+                      const dns::Name& zone_name,
+                      ZoneData* content);
 
 
     /// Find a zone that best matches the given name in the \c ZoneTable.
     /// Find a zone that best matches the given name in the \c ZoneTable.
     ///
     ///
@@ -185,16 +184,6 @@ public:
     /// \return A \c FindResult object enclosing the search result (see above).
     /// \return A \c FindResult object enclosing the search result (see above).
     FindResult findZone(const isc::dns::Name& name) const;
     FindResult findZone(const isc::dns::Name& name) const;
 
 
-    /// Override the ZoneData for a node (zone) in the zone tree.
-    ///
-    /// \throw none
-    ///
-    /// \param name A domain name for which the zone data is set.
-    /// \param data The new zone data to set.
-    /// \return A \c FindResult object containing the old data if the
-    /// zone was found.
-    FindResult setZoneData(const isc::dns::Name& name, ZoneData* data);
-
 private:
 private:
     boost::interprocess::offset_ptr<ZoneTableTree> zones_;
     boost::interprocess::offset_ptr<ZoneTableTree> zones_;
 };
 };

+ 0 - 1
src/lib/datasrc/memory_datasrc.cc

@@ -20,7 +20,6 @@
 #include <dns/nsec3hash.h>
 #include <dns/nsec3hash.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
-#include <dns/rrsetlist.h>
 #include <dns/masterload.h>
 #include <dns/masterload.h>
 
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/memory_datasrc.h>

+ 1 - 1
src/lib/datasrc/memory_datasrc_link.cc

@@ -129,7 +129,7 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
                 result = false;
                 result = false;
             } else {
             } else {
                 try {
                 try {
-                    RRClass rrc(config->get("class")->stringValue());
+                    RRClass(config->get("class")->stringValue());
                 } catch (const isc::Exception& rrce) {
                 } catch (const isc::Exception& rrce) {
                     addError(errors,
                     addError(errors,
                              "Error parsing class config for memory backend: " +
                              "Error parsing class config for memory backend: " +

+ 16 - 15
src/lib/datasrc/tests/memory/domaintree_unittest.cc

@@ -256,8 +256,8 @@ TEST_F(DomainTreeTest, subTreeRoot) {
 
 
     // "g.h" is not a subtree root
     // "g.h" is not a subtree root
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
-              dtree_expose_empty_node.find(Name("g.h"), &dtnode));
-    EXPECT_FALSE(dtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
+              dtree_expose_empty_node.find(Name("g.h"), &cdtnode));
+    EXPECT_FALSE(cdtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
 
 
     // fission the node "g.h"
     // fission the node "g.h"
     EXPECT_EQ(TestDomainTree::ALREADYEXISTS,
     EXPECT_EQ(TestDomainTree::ALREADYEXISTS,
@@ -270,8 +270,8 @@ TEST_F(DomainTreeTest, subTreeRoot) {
 
 
     // "g.h" should be a subtree root now.
     // "g.h" should be a subtree root now.
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
-              dtree_expose_empty_node.find(Name("g.h"), &dtnode));
-    EXPECT_TRUE(dtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
+              dtree_expose_empty_node.find(Name("g.h"), &cdtnode));
+    EXPECT_TRUE(cdtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
 }
 }
 
 
 TEST_F(DomainTreeTest, additionalNodeFission) {
 TEST_F(DomainTreeTest, additionalNodeFission) {
@@ -286,8 +286,8 @@ TEST_F(DomainTreeTest, additionalNodeFission) {
 
 
     // "t.0" is not a subtree root
     // "t.0" is not a subtree root
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
-              dtree_expose_empty_node.find(Name("t.0"), &dtnode));
-    EXPECT_FALSE(dtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
+              dtree_expose_empty_node.find(Name("t.0"), &cdtnode));
+    EXPECT_FALSE(cdtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
 
 
     // fission the node "t.0"
     // fission the node "t.0"
     EXPECT_EQ(TestDomainTree::ALREADYEXISTS,
     EXPECT_EQ(TestDomainTree::ALREADYEXISTS,
@@ -300,8 +300,8 @@ TEST_F(DomainTreeTest, additionalNodeFission) {
 
 
     // "t.0" should be a subtree root now.
     // "t.0" should be a subtree root now.
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
-              dtree_expose_empty_node.find(Name("t.0"), &dtnode));
-    EXPECT_TRUE(dtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
+              dtree_expose_empty_node.find(Name("t.0"), &cdtnode));
+    EXPECT_TRUE(cdtnode->getFlag(TestDomainTreeNode::FLAG_SUBTREE_ROOT));
 }
 }
 
 
 TEST_F(DomainTreeTest, findName) {
 TEST_F(DomainTreeTest, findName) {
@@ -328,10 +328,10 @@ TEST_F(DomainTreeTest, findName) {
     EXPECT_EQ(TestDomainTree::PARTIALMATCH,
     EXPECT_EQ(TestDomainTree::PARTIALMATCH,
               dtree_expose_empty_node.find(Name("m.d.e.f"), &cdtnode));
               dtree_expose_empty_node.find(Name("m.d.e.f"), &cdtnode));
 
 
-    // find dtnode
+    // find cdtnode
     EXPECT_EQ(TestDomainTree::EXACTMATCH, dtree.find(Name("q.w.y.d.e.f"),
     EXPECT_EQ(TestDomainTree::EXACTMATCH, dtree.find(Name("q.w.y.d.e.f"),
-                                                   &dtnode));
-    EXPECT_EQ(Name("q"), dtnode->getName());
+                                                   &cdtnode));
+    EXPECT_EQ(Name("q"), cdtnode->getName());
 }
 }
 
 
 TEST_F(DomainTreeTest, findError) {
 TEST_F(DomainTreeTest, findError) {
@@ -411,11 +411,12 @@ performCallbackTest(TestDomainTree& dtree,
                                                         Name("example"),
                                                         Name("example"),
                                                         &parentdtnode));
                                                         &parentdtnode));
     // the child/parent nodes shouldn't "inherit" the callback flag.
     // the child/parent nodes shouldn't "inherit" the callback flag.
-    // "dtnode" may be invalid due to the insertion, so we need to re-find
-    // it.
+    // "dtnode" should still validly point to "callback.example", but we
+    // explicitly confirm it.
     EXPECT_EQ(TestDomainTree::EXACTMATCH, dtree.find(Name("callback.example"),
     EXPECT_EQ(TestDomainTree::EXACTMATCH, dtree.find(Name("callback.example"),
-                                                   &dtnode));
-    EXPECT_TRUE(dtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
+                                                     &cdtnode));
+    ASSERT_EQ(dtnode, cdtnode);
+    EXPECT_TRUE(cdtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
     EXPECT_FALSE(subdtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
     EXPECT_FALSE(subdtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
     EXPECT_FALSE(parentdtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
     EXPECT_FALSE(parentdtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
 
 

+ 12 - 93
src/lib/datasrc/tests/memory/memory_client_unittest.cc

@@ -22,7 +22,6 @@
 #include <dns/nsec3hash.h>
 #include <dns/nsec3hash.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
-#include <dns/rrsetlist.h>
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/masterload.h>
 #include <dns/masterload.h>
 
 
@@ -543,29 +542,6 @@ TEST_F(MemoryClientTest, loadRRSIGs) {
     EXPECT_EQ(1, client_->getZoneCount());
     EXPECT_EQ(1, client_->getZoneCount());
 }
 }
 
 
-TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) {
-    client_->load(Name("example.org"),
-                  TEST_DATA_DIR "/example.org-rrsigs.zone");
-
-    RRsetPtr rrset(new RRset(Name("example.org"),
-                             RRClass::IN(), RRType::A(), RRTTL(3600)));
-    rrset->addRdata(in::A("192.0.2.1"));
-    rrset->addRdata(in::A("192.0.2.2"));
-
-    RRsetPtr rrsig(new RRset(Name("example.org"), zclass_,
-                             RRType::RRSIG(), RRTTL(300)));
-    rrsig->addRdata(generic::RRSIG("A 5 3 3600 20000101000000 20000201000000 "
-                                   "12345 example.org. FAKEFAKEFAKE"));
-    rrsig->addRdata(generic::RRSIG("NS 5 3 3600 20000101000000 20000201000000 "
-                                   "54321 example.org. FAKEFAKEFAKEFAKE"));
-    rrset->addRRsig(rrsig);
-
-    EXPECT_THROW(client_->add(Name("example.org"), rrset),
-                 InMemoryClient::AddError);
-
-    // Teardown checks for memory segment leaks
-}
-
 TEST_F(MemoryClientTest, getZoneCount) {
 TEST_F(MemoryClientTest, getZoneCount) {
     EXPECT_EQ(0, client_->getZoneCount());
     EXPECT_EQ(0, client_->getZoneCount());
     client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
     client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
@@ -655,75 +631,6 @@ TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
     EXPECT_THROW(iterator->getSOA(), isc::NotImplemented);
     EXPECT_THROW(iterator->getSOA(), isc::NotImplemented);
 }
 }
 
 
-TEST_F(MemoryClientTest, addRRsetToNonExistentZoneThrows) {
-    // The zone "example.org" doesn't exist, so we can't add an RRset to
-    // it.
-    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
-                               RRTTL(300)));
-    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
-    EXPECT_THROW(client_->add(Name("example.org"), rrset_a), DataSourceError);
-}
-
-TEST_F(MemoryClientTest, addOutOfZoneThrows) {
-    // Out of zone names should throw.
-    client_->load(Name("example.org"),
-                  TEST_DATA_DIR "/example.org-empty.zone");
-
-    RRsetPtr rrset_a(new RRset(Name("a.example.com"),
-                               RRClass::IN(), RRType::A(), RRTTL(300)));
-    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
-
-    EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
-                 OutOfZone);
-    // Teardown checks for memory segment leaks
-}
-
-TEST_F(MemoryClientTest, addNullRRsetThrows) {
-    client_->load(Name("example.org"),
-                  TEST_DATA_DIR "/example.org-rrsigs.zone");
-
-    EXPECT_THROW(client_->add(Name("example.org"), ConstRRsetPtr()),
-                 InMemoryClient::NullRRset);
-
-    // Teardown checks for memory segment leaks
-}
-
-TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
-    client_->load(Name("example.org"),
-                  TEST_DATA_DIR "/example.org-rrsigs.zone");
-
-    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
-                               RRTTL(300)));
-    EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
-                 InMemoryClient::AddError);
-
-    // Teardown checks for memory segment leaks
-}
-
-TEST_F(MemoryClientTest, add) {
-    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
-
-    // Add another RRset
-    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
-                               RRTTL(300)));
-    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
-    client_->add(Name("example.org"), rrset_a);
-
-    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
-
-    // First we have the SOA
-    ConstRRsetPtr rrset(iterator->getNextRRset());
-    EXPECT_TRUE(rrset);
-    EXPECT_EQ(RRType::A(), rrset->getType());
-
-    rrset = iterator->getNextRRset();
-    EXPECT_TRUE(rrset);
-    EXPECT_EQ(RRType::SOA(), rrset->getType());
-
-    // There's nothing else in this zone
-    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
-}
-
 TEST_F(MemoryClientTest, findZoneData) {
 TEST_F(MemoryClientTest, findZoneData) {
     client_->load(Name("example.org"),
     client_->load(Name("example.org"),
                   TEST_DATA_DIR "/example.org-rrsigs.zone");
                   TEST_DATA_DIR "/example.org-rrsigs.zone");
@@ -774,4 +681,16 @@ TEST_F(MemoryClientTest, getJournalReaderNotImplemented) {
     EXPECT_THROW(client_->getJournalReader(Name("."), 0, 0),
     EXPECT_THROW(client_->getJournalReader(Name("."), 0, 0),
                  isc::NotImplemented);
                  isc::NotImplemented);
 }
 }
+
+// TODO (upon merge of #2268): Re-add (and modify not to need
+// InMemoryClient::add) the tests removed in
+// 7a628baa1a158b5837d6f383e10b30542d2ac59b. Maybe some of them
+// are really not needed.
+//
+// * MemoryClientTest::loadRRSIGsRdataMixedCoveredTypes
+// * MemoryClientTest::addRRsetToNonExistentZoneThrows
+// * MemoryClientTest::addOutOfZoneThrows
+// * MemoryClientTest::addNullRRsetThrows
+// * MemoryClientTest::addEmptyRRsetThrows
+// * MemoryClientTest::add
 }
 }

+ 1 - 1
src/lib/datasrc/tests/memory/zone_data_unittest.cc

@@ -108,7 +108,7 @@ void
 checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type,
 checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type,
                   const RdataSet* expected_set)
                   const RdataSet* expected_set)
 {
 {
-    ZoneNode* node = NULL;
+    const ZoneNode* node = NULL;
     tree.find(name, &node);
     tree.find(name, &node);
     ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
     ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
     EXPECT_EQ(expected_set, RdataSet::find(node->getData(), type));
     EXPECT_EQ(expected_set, RdataSet::find(node->getData(), type));

+ 69 - 21
src/lib/datasrc/tests/memory/zone_table_unittest.cc

@@ -22,6 +22,7 @@
 #include <datasrc/result.h>
 #include <datasrc/result.h>
 #include <datasrc/memory/zone_data.h>
 #include <datasrc/memory/zone_data.h>
 #include <datasrc/memory/zone_table.h>
 #include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/segment_object_holder.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -30,6 +31,7 @@
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using namespace isc::datasrc::memory;
 using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::detail;
 
 
 namespace {
 namespace {
 // Memory segment specified for tests.  It normally behaves like a "local"
 // Memory segment specified for tests.  It normally behaves like a "local"
@@ -87,46 +89,89 @@ TEST_F(ZoneTableTest, create) {
 }
 }
 
 
 TEST_F(ZoneTableTest, addZone) {
 TEST_F(ZoneTableTest, addZone) {
+    // It doesn't accept empty (NULL) zones
+    EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
+                 isc::BadValue);
+
+    SegmentObjectHolder<ZoneData, RRClass> holder1(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+    const ZoneData* data1(holder1.get());
     // Normal successful case.
     // Normal successful case.
-    const ZoneTable::AddResult result1 =
-        zone_table->addZone(mem_sgmt_, zclass_, zname1);
+    const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zclass_,
+                                                           zname1,
+                                                           holder1.release()));
     EXPECT_EQ(result::SUCCESS, result1.code);
     EXPECT_EQ(result::SUCCESS, result1.code);
+    EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
+    // It got released by it
+    EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder1.get());
 
 
     // Duplicate add doesn't replace the existing data.
     // Duplicate add doesn't replace the existing data.
-    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_, zclass_,
-                                                 zname1).code);
-    EXPECT_EQ(result1.zone_data,
-              zone_table->addZone(mem_sgmt_, zclass_, zname1).zone_data);
+    SegmentObjectHolder<ZoneData, RRClass> holder2(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+    const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
+                                                           zname1,
+                                                           holder2.release()));
+    EXPECT_EQ(result::EXIST, result2.code);
+    // The old one gets out
+    EXPECT_EQ(data1, result2.zone_data);
+    // It releases this one even when we replace the old zone
+    EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder2.get());
+    // We need to release the old one manually
+    ZoneData::destroy(mem_sgmt_, result2.zone_data, zclass_);
+
+    SegmentObjectHolder<ZoneData, RRClass> holder3(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")),
+                                    zclass_);
     // names are compared in a case insensitive manner.
     // names are compared in a case insensitive manner.
-    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_, zclass_,
-                                                 Name("EXAMPLE.COM")).code);
+    const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_, zclass_,
+                                                           Name("EXAMPLE.COM"),
+                                                           holder3.release()));
+    EXPECT_EQ(result::EXIST, result3.code);
+    ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
     // Add some more different ones.  Should just succeed.
     // Add some more different ones.  Should just succeed.
+    SegmentObjectHolder<ZoneData, RRClass> holder4(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
     EXPECT_EQ(result::SUCCESS,
     EXPECT_EQ(result::SUCCESS,
-              zone_table->addZone(mem_sgmt_, zclass_, zname2).code);
+              zone_table->addZone(mem_sgmt_, zclass_, zname2,
+                                  holder4.release()).code);
+    SegmentObjectHolder<ZoneData, RRClass> holder5(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
     EXPECT_EQ(result::SUCCESS,
     EXPECT_EQ(result::SUCCESS,
-              zone_table->addZone(mem_sgmt_, zclass_, zname3).code);
+              zone_table->addZone(mem_sgmt_, zclass_, zname3,
+                                  holder5.release()).code);
 
 
     // Have the memory segment throw an exception in extending the internal
     // Have the memory segment throw an exception in extending the internal
     // tree.  It still shouldn't cause memory leak (which would be detected
     // tree.  It still shouldn't cause memory leak (which would be detected
     // in TearDown()).
     // in TearDown()).
-    mem_sgmt_.setThrowCount(2);
-    EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org")),
+    SegmentObjectHolder<ZoneData, RRClass> holder6(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, Name("example.org")), zclass_);
+    mem_sgmt_.setThrowCount(1);
+    EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org"),
+                                     holder6.release()),
                  std::bad_alloc);
                  std::bad_alloc);
 }
 }
 
 
 TEST_F(ZoneTableTest, findZone) {
 TEST_F(ZoneTableTest, findZone) {
-    const ZoneTable::AddResult add_result1 =
-        zone_table->addZone(mem_sgmt_, zclass_, zname1);
-    EXPECT_EQ(result::SUCCESS, add_result1.code);
+    SegmentObjectHolder<ZoneData, RRClass> holder1(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+    ZoneData* zone_data = holder1.get();
+    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_, zname1,
+                                                   holder1.release()).code);
+    SegmentObjectHolder<ZoneData, RRClass> holder2(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
     EXPECT_EQ(result::SUCCESS,
     EXPECT_EQ(result::SUCCESS,
-              zone_table->addZone(mem_sgmt_, zclass_, zname2).code);
+              zone_table->addZone(mem_sgmt_, zclass_, zname2,
+                                  holder2.release()).code);
+    SegmentObjectHolder<ZoneData, RRClass> holder3(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
     EXPECT_EQ(result::SUCCESS,
     EXPECT_EQ(result::SUCCESS,
-              zone_table->addZone(mem_sgmt_, zclass_, zname3).code);
+              zone_table->addZone(mem_sgmt_, zclass_, zname3,
+                                  holder3.release()).code);
 
 
     const ZoneTable::FindResult find_result1 =
     const ZoneTable::FindResult find_result1 =
         zone_table->findZone(Name("example.com"));
         zone_table->findZone(Name("example.com"));
     EXPECT_EQ(result::SUCCESS, find_result1.code);
     EXPECT_EQ(result::SUCCESS, find_result1.code);
-    EXPECT_EQ(add_result1.zone_data, find_result1.zone_data);
+    EXPECT_EQ(zone_data, find_result1.zone_data);
 
 
     EXPECT_EQ(result::NOTFOUND,
     EXPECT_EQ(result::NOTFOUND,
               zone_table->findZone(Name("example.org")).code);
               zone_table->findZone(Name("example.org")).code);
@@ -137,14 +182,17 @@ TEST_F(ZoneTableTest, findZone) {
     // and the code should be PARTIALMATCH.
     // and the code should be PARTIALMATCH.
     EXPECT_EQ(result::PARTIALMATCH,
     EXPECT_EQ(result::PARTIALMATCH,
               zone_table->findZone(Name("www.example.com")).code);
               zone_table->findZone(Name("www.example.com")).code);
-    EXPECT_EQ(add_result1.zone_data,
+    EXPECT_EQ(zone_data,
               zone_table->findZone(Name("www.example.com")).zone_data);
               zone_table->findZone(Name("www.example.com")).zone_data);
 
 
     // make sure the partial match is indeed the longest match by adding
     // make sure the partial match is indeed the longest match by adding
     // a zone with a shorter origin and query again.
     // a zone with a shorter origin and query again.
+    SegmentObjectHolder<ZoneData, RRClass> holder4(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, Name("com")), zclass_);
     EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
     EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
-                                                   Name("com")).code);
-    EXPECT_EQ(add_result1.zone_data,
+                                                   Name("com"),
+                                                   holder4.release()).code);
+    EXPECT_EQ(zone_data,
               zone_table->findZone(Name("www.example.com")).zone_data);
               zone_table->findZone(Name("www.example.com")).zone_data);
 }
 }
 }
 }

+ 0 - 1
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -22,7 +22,6 @@
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
-#include <dns/rrsetlist.h>
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/masterload.h>
 #include <dns/masterload.h>
 
 

+ 2 - 0
src/lib/dhcp/Makefile.am

@@ -16,6 +16,7 @@ CLEANFILES = *.gcno *.gcda
 lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
 lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
 libb10_dhcp___la_SOURCES  =
 libb10_dhcp___la_SOURCES  =
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
+libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
@@ -28,6 +29,7 @@ libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += duid.cc duid.h
 
 
 libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h

+ 95 - 10
src/lib/dhcp/addr_utilities.cc

@@ -13,17 +13,39 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <string.h>
 #include <string.h>
+#include <exceptions/exceptions.h>
 #include <dhcp/addr_utilities.h>
 #include <dhcp/addr_utilities.h>
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
-namespace isc {
-namespace dhcp {
-
-isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+namespace {
+
+/// @brief mask used for first/last address calculation in a IPv4 prefix
+///
+/// Using a static mask is faster than calculating it dynamically every time.
+const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
+                              0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
+                              0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
+                              0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
+                              0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
+                              0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
+                              0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
+                              0x0000000f, 0x00000007, 0x00000003, 0x00000001,
+                              0x00000000 };
+
+/// @brief mask used for first/last address calculation in a IPv6 prefix
+const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+
+/// @brief calculates the first IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix
+/// @param len prefix length
+isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                             uint8_t len) {
                                             uint8_t len) {
 
 
-    const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     uint8_t packed[V6ADDRESS_LEN];
     uint8_t packed[V6ADDRESS_LEN];
 
 
     // First we copy the whole address as 16 bytes.
     // First we copy the whole address as 16 bytes.
@@ -36,7 +58,7 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
 
 
         // Get the appropriate mask. It has relevant bits (those that should
         // Get the appropriate mask. It has relevant bits (those that should
         // stay) set and irrelevant (those that should be wiped) cleared.
         // stay) set and irrelevant (those that should be wiped) cleared.
-        uint8_t mask = bitMask[len % 8];
+        uint8_t mask = bitMask6[len % 8];
 
 
         // Let's leave only whatever the mask says should not be cleared.
         // Let's leave only whatever the mask says should not be cleared.
         packed[len / 8] = packed[len / 8] & mask;
         packed[len / 8] = packed[len / 8] & mask;
@@ -55,10 +77,50 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 }
 
 
-isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+/// @brief calculates the first IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix
+/// @param len netmask length (0-32)
+isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
+                                            uint8_t len) {
+    uint32_t addr = prefix;
+    if (len > 32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    return (IOAddress(addr & (~bitMask4[len])));
+}
+
+/// @brief calculates the last IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix that we calculate first address for
+/// @param len netmask length (0-32)
+isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
+                                           uint8_t len) {
+    uint32_t addr = prefix;
+    if (len>32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    return (IOAddress(addr | bitMask4[len]));
+}
+
+/// @brief calculates the last IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use lastAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix that we calculate first address for
+/// @param len netmask length (0-128)
+isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                            uint8_t len) {
                                            uint8_t len) {
 
 
-    const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     uint8_t packed[V6ADDRESS_LEN];
     uint8_t packed[V6ADDRESS_LEN];
 
 
     // First we copy the whole address as 16 bytes.
     // First we copy the whole address as 16 bytes.
@@ -70,10 +132,10 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
     if (len % 8 != 0) {
     if (len % 8 != 0) {
         // Get the appropriate mask. It has relevant bits (those that should
         // Get the appropriate mask. It has relevant bits (those that should
         // stay) set and irrelevant (those that should be set to 1) cleared.
         // stay) set and irrelevant (those that should be set to 1) cleared.
-        uint8_t mask = bitMask[len % 8];
+        uint8_t mask = bitMask6[len % 8];
 
 
         // Let's set those irrelevant bits with 1. It would be perhaps
         // Let's set those irrelevant bits with 1. It would be perhaps
-        // easier to not use negation here and invert bitMask content. However,
+        // easier to not use negation here and invert bitMask6 content. However,
         // with this approach, we can use the same mask in first and last
         // with this approach, we can use the same mask in first and last
         // address calculations.
         // address calculations.
         packed[len / 8] = packed[len / 8] | ~mask;
         packed[len / 8] = packed[len / 8] | ~mask;
@@ -92,5 +154,28 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 }
 
 
+}; // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+                                            uint8_t len) {
+    if (prefix.getFamily() == AF_INET) {
+        return firstAddrInPrefix4(prefix, len);
+    } else {
+        return firstAddrInPrefix6(prefix, len);
+    }
+}
+
+isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+                                           uint8_t len) {
+    if (prefix.getFamily() == AF_INET) {
+        return lastAddrInPrefix4(prefix, len);
+    } else {
+        return lastAddrInPrefix6(prefix, len);
+    }
+}
+
 };
 };
 };
 };

+ 33 - 0
src/lib/dhcp/cfgmgr.cc

@@ -68,6 +68,39 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
     subnets6_.push_back(subnet);
     subnets6_.push_back(subnet);
 }
 }
 
 
+Subnet4Ptr
+CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
+
+    // If there's only one subnet configured, let's just use it
+    // The idea is to keep small deployments easy. In a small network - one
+    // router that also runs DHCPv6 server. Users specifies a single pool and
+    // expects it to just work. Without this, the server would complain that it
+    // doesn't have IP address on its interfaces that matches that
+    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
+    // The server does not need to have a global address (using just link-local
+    // is ok for DHCPv6 server) from the pool it serves.
+    if (subnets4_.size() == 1) {
+        return (subnets4_[0]);
+    }
+
+    // If there is more than one, we need to choose the proper one
+    for (Subnet4Collection::iterator subnet = subnets4_.begin();
+         subnet != subnets4_.end(); ++subnet) {
+        if ((*subnet)->inRange(hint)) {
+            return (*subnet);
+        }
+    }
+
+    // sorry, we don't support that subnet
+    return (Subnet4Ptr());
+}
+
+void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
+    /// @todo: Check that this new subnet does not cross boundaries of any
+    /// other already defined subnet.
+    subnets4_.push_back(subnet);
+}
+
 CfgMgr::CfgMgr() {
 CfgMgr::CfgMgr() {
 }
 }
 
 

+ 43 - 4
src/lib/dhcp/cfgmgr.h

@@ -72,7 +72,7 @@ public:
     /// accessing it.
     /// accessing it.
     static CfgMgr& instance();
     static CfgMgr& instance();
 
 
-    /// @brief get subnet by address
+    /// @brief get IPv6 subnet by address
     ///
     ///
     /// Finds a matching subnet, based on an address. This can be used
     /// Finds a matching subnet, based on an address. This can be used
     /// in two cases: when trying to find an appropriate lease based on
     /// in two cases: when trying to find an appropriate lease based on
@@ -83,7 +83,7 @@ public:
     /// @param hint an address that belongs to a searched subnet
     /// @param hint an address that belongs to a searched subnet
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
 
-    /// @brief get subnet by interface-id
+    /// @brief get IPv6 subnet by interface-id
     ///
     ///
     /// Another possibility to find a subnet is based on interface-id.
     /// Another possibility to find a subnet is based on interface-id.
     ///
     ///
@@ -91,13 +91,44 @@ public:
     /// @todo This method is not currently supported.
     /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
 
-    /// @brief adds a subnet6
+    /// @brief adds an IPv6 subnet
     void addSubnet6(const Subnet6Ptr& subnet);
     void addSubnet6(const Subnet6Ptr& subnet);
 
 
     /// @todo: Add subnet6 removal routines. Currently it is not possible
     /// @todo: Add subnet6 removal routines. Currently it is not possible
     /// to remove subnets. The only case where subnet6 removal would be
     /// to remove subnets. The only case where subnet6 removal would be
     /// needed is a dynamic server reconfiguration - a use case that is not
     /// needed is a dynamic server reconfiguration - a use case that is not
     /// planned to be supported any time soon.
     /// planned to be supported any time soon.
+
+    /// @brief removes all subnets
+    ///
+    /// This method removes all existing subnets. It is used during
+    /// reconfiguration - old configuration is wiped and new definitions
+    /// are used to recreate subnets.
+    ///
+    /// @todo Implement more intelligent approach. Note that comparison
+    /// between old and new configuration is tricky. For example: is
+    /// 2000::/64 and 2000::/48 the same subnet or is it something
+    /// completely new?
+    void deleteSubnets6() {
+        subnets6_.clear();
+    }
+
+    /// @brief get IPv4 subnet by address
+    ///
+    /// Finds a matching subnet, based on an address. This can be used
+    /// in two cases: when trying to find an appropriate lease based on
+    /// a) relay link address (that must be the address that is on link)
+    /// b) our global address on the interface the message was received on
+    ///    (for directly connected clients)
+    ///
+    /// @param hint an address that belongs to a searched subnet
+    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
+
+    /// @brief adds a subnet4
+    void addSubnet4(const Subnet4Ptr& subnet);
+
+    /// @brief removes all IPv4 subnets
+    void removeSubnets4();
 protected:
 protected:
 
 
     /// @brief Protected constructor.
     /// @brief Protected constructor.
@@ -111,13 +142,21 @@ protected:
     /// @brief virtual desctructor
     /// @brief virtual desctructor
     virtual ~CfgMgr();
     virtual ~CfgMgr();
 
 
-    /// @brief a container for Subnet6
+    /// @brief a container for IPv6 subnets.
     ///
     ///
     /// That is a simple vector of pointers. It does not make much sense to
     /// That is a simple vector of pointers. It does not make much sense to
     /// optimize access time (e.g. using a map), because typical search
     /// optimize access time (e.g. using a map), because typical search
     /// pattern will use calling inRange() method on each subnet until
     /// pattern will use calling inRange() method on each subnet until
     /// a match is found.
     /// a match is found.
     Subnet6Collection subnets6_;
     Subnet6Collection subnets6_;
+
+    /// @brief a container for IPv4 subnets.
+    ///
+    /// That is a simple vector of pointers. It does not make much sense to
+    /// optimize access time (e.g. using a map), because typical search
+    /// pattern will use calling inRange() method on each subnet until
+    /// a match is found.
+    Subnet4Collection subnets4_;
 };
 };
 
 
 } // namespace isc::dhcp
 } // namespace isc::dhcp

+ 90 - 0
src/lib/dhcp/duid.cc

@@ -0,0 +1,90 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <vector>
+#include <exceptions/exceptions.h>
+#include <stdint.h>
+#include <util/io_utilities.h>
+#include <dhcp/duid.h>
+
+namespace isc {
+namespace dhcp {
+
+DUID::DUID(const std::vector<uint8_t>& duid) {
+    if (duid.size() > MAX_DUID_LEN) {
+        isc_throw(OutOfRange, "DUID too large");
+    } else {
+        duid_ = duid;
+    }
+}
+
+DUID::DUID(const uint8_t * data, size_t len) {
+    if (len > MAX_DUID_LEN) {
+        isc_throw(OutOfRange, "DUID too large");
+    }
+
+    duid_ = std::vector<uint8_t>(data, data + len);
+}
+
+const std::vector<uint8_t> DUID::getDuid() const {
+    return (duid_);
+}
+
+DUID::DUIDType DUID::getType() const {
+    if (duid_.size() < 2) {
+        return (DUID_UNKNOWN);
+    }
+    uint16_t type = (duid_[0] << 8) + duid_[1];
+    if (type < DUID_MAX) {
+        return (static_cast<DUID::DUIDType>(type));
+    } else {
+        return (DUID_UNKNOWN);
+    }
+}
+
+bool DUID::operator == (const DUID& other) const {
+    return (this->duid_ == other.duid_);
+}
+
+bool DUID::operator != (const DUID& other) const {
+    return (this->duid_ != other.duid_);
+}
+
+/// constructor based on vector<uint8_t>
+ClientId::ClientId(const std::vector<uint8_t>& clientid)
+    :DUID(clientid) {
+}
+
+/// constructor based on C-style data
+ClientId::ClientId(const uint8_t *clientid, size_t len)
+    :DUID(clientid, len) {
+}
+
+/// @brief returns a copy of client-id data
+const std::vector<uint8_t> ClientId::getClientId() const {
+    return (duid_);
+}
+
+// compares two client-ids
+bool ClientId::operator == (const ClientId& other) const {
+    return (this->duid_ == other.duid_);
+}
+
+// compares two client-ids
+bool ClientId::operator != (const ClientId& other) const {
+    return (this->duid_ != other.duid_);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 98 - 0
src/lib/dhcp/duid.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+#include <unistd.h>
+#include <vector>
+#include <asiolink/io_address.h>
+
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds DUID (DHCPv6 Unique Identifier)
+///
+/// This class holds DUID, that is used in client-id, server-id and
+/// several other options. It is used to identify DHCPv6 entity.
+class DUID {
+ public:
+    /// @brief maximum duid size
+    /// As defined in RFC3315, section 9.1
+    static const size_t MAX_DUID_LEN = 128;
+
+    /// @brief specifies DUID type
+    typedef enum {
+        DUID_UNKNOWN = 0, ///< invalid/unknown type
+        DUID_LLT = 1,     ///< link-layer + time, see RFC3315, section 9.2
+        DUID_EN = 2,      ///< enterprise-id, see RFC3315, section 9.3
+        DUID_LL = 3,      ///< link-layer, see RFC3315, section 9.4
+        DUID_UUID = 4,    ///< UUID, see RFC6355
+        DUID_MAX          ///< not a real type, just maximum defined value + 1
+    } DUIDType;
+
+    /// @brief creates a DUID
+    DUID(const std::vector<uint8_t>& duid);
+
+    /// @brief creates a DUID
+    DUID(const uint8_t *duid, size_t len);
+
+    /// @brief returns a const reference to the actual DUID value
+    ///
+    /// Note: For safety reasons, this method returns a copy of data as
+    /// otherwise the reference would be only valid as long as the object that
+    /// returned it. In any case, this method should be used only sporadically.
+    /// If there are frequent uses, we must implement some other method
+    /// (e.g. storeSelf()) that will avoid data copying.
+    const std::vector<uint8_t> getDuid() const;
+
+    /// @brief returns DUID type
+    DUIDType getType() const;
+
+    // compares two DUIDs
+    bool operator == (const DUID& other) const;
+
+    // compares two DUIDs
+    bool operator != (const DUID& other) const;
+
+ protected:
+    /// the actual content of the DUID
+    std::vector<uint8_t> duid_;
+};
+
+/// @brief Holds Client identifier or client IPv4 address
+///
+/// This class is intended to be a generic IPv4 client identifier. It can hold
+/// a client-id
+class ClientId : DUID {
+ public:
+
+    /// constructor based on vector<uint8_t>
+    ClientId(const std::vector<uint8_t>& clientid);
+
+    /// constructor based on C-style data
+    ClientId(const uint8_t *clientid, size_t len);
+
+    /// @brief returns reference to the client-id data
+    ///
+    const std::vector<uint8_t> getClientId() const;
+
+    // compares two client-ids
+    bool operator == (const ClientId& other) const;
+
+    // compares two client-ids
+    bool operator != (const ClientId& other) const;
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 68 - 0
src/lib/dhcp/lease_mgr.cc

@@ -0,0 +1,68 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <sstream>
+#include <iostream>
+#include <map>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <algorithm>
+#include <iterator>
+#include <exceptions/exceptions.h>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+#include "lease_mgr.h"
+
+using namespace std;
+
+using namespace isc::dhcp;
+
+LeaseMgr::LeaseMgr(const std::string& dbconfig) {
+
+    if (dbconfig.length() == 0) {
+        return;
+    }
+
+    vector<string> tokens;
+
+    // we need to pass a string to is_any_of, not just char *. Otherwise there
+    // are cryptic warnings on Debian6 running g++ 4.4 in /usr/include/c++/4.4
+    // /bits/stl_algo.h:2178 "array subscript is above array bounds"
+    boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+    BOOST_FOREACH(std::string token, tokens) {
+        size_t pos = token.find("=");
+        if (pos != string::npos) {
+            string name = token.substr(0, pos);
+            string value = token.substr(pos + 1);
+            parameters_.insert(pair<string,string>(name, value));
+        } else {
+            isc_throw(InvalidParameter, "Cannot parse " << token
+                      << ", expected format is name=value");
+        }
+
+    }
+}
+
+std::string LeaseMgr::getParameter(const std::string& name) const {
+    std::map<std::string, std::string>::const_iterator param
+        = parameters_.find(name);
+    if (param == parameters_.end()) {
+        isc_throw(BadValue, "Parameter not found");
+    }
+    return (param->second);
+}
+
+LeaseMgr::~LeaseMgr() {
+}

+ 480 - 0
src/lib/dhcp/lease_mgr.h

@@ -0,0 +1,480 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <fstream>
+#include <vector>
+#include <map>
+#include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/option.h>
+#include <dhcp/duid.h>
+
+/// @file dhcp/lease_mgr.h
+/// @brief An abstract API for lease database
+///
+/// This file contains declarations of Lease4, Lease6 and LeaseMgr classes.
+/// They are essential components of the interface to any database backend.
+/// Each concrete database backend (e.g. MySQL) will define a class derived
+/// from LeaseMgr class.
+///
+/// Failover considerations:
+/// There are no intermediate plans to implement DHCPv4 failover
+/// (draft-ietf-dhc-failover-12.txt). Currently (Oct. 2012) the DHCPv6 failover
+/// is being defined in DHC WG in IETF (draft-ietf-dhcpv6-failover-requirements,
+/// draft-ietf-dhcpv6-dailover-design), but the work is not advanced enough
+/// for implementation plans yet. v4 failover requires additional parameters
+/// to be kept with a lease. It is likely that v6 failover will require similar
+/// fields. Such implementation will require database schema extension.
+/// We have designed a way to expand/upgrade schemas during upgrades: a database
+/// schema is versioned and sanity checks about required version will be done
+/// upon start and/or upgrade. With this mechanism in place, we can add new
+/// fields to the database. In particular we can use that capability to
+/// introduce failover related fields.
+///
+/// However, there is another approach that can be reliably used to provide
+/// failover, even without the actual failover protocol implemented. As the
+/// first backend will use MySQL, we will be able to use Multi-Master capability
+/// offered by MySQL and use two separatate Kea instances connecting to the
+/// same database.
+///
+/// Nevertheless, we hope to have failover protocol eventually implemented in
+/// the Kea.
+
+namespace isc {
+namespace dhcp {
+
+/// @brief specifies unique subnet identifier
+/// @todo: Move this to subnet.h once ticket #2237 is merged
+typedef uint32_t SubnetID;
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 {
+    /// IPv4 address
+    isc::asiolink::IOAddress addr_;
+
+    /// @brief Address extension
+    ///
+    /// It is envisaged that in some cases IPv4 address will be accompanied with some
+    /// additional data. One example of such use are Address + Port solutions (or
+    /// Port-restricted Addresses), where several clients may get the same address, but
+    /// different port ranges. This feature is not expected to be widely used.
+    /// Under normal circumstances, the value should be 0.
+    uint32_t ext_;
+
+    /// @brief hardware address
+    std::vector<uint8_t> hwaddr_;
+
+    /// @brief client identifier
+    boost::shared_ptr<ClientId> client_id_;
+
+    /// @brief renewal timer
+    ///
+    /// Specifies renewal time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t1_;
+
+    /// @brief rebinding timer
+    ///
+    /// Specifies rebinding time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t2_;
+
+    /// @brief valid lifetime
+    ///
+    /// Expressed as number of seconds since cltt
+    uint32_t valid_lft_;
+
+    /// @brief client last transmission time
+    ///
+    /// Specifies a timestamp, when last transmission from a client was received.
+    time_t cltt_;
+
+    /// @brief Subnet identifier
+    ///
+    /// Specifies subnet-id of the subnet that the lease belongs to
+    SubnetID subnet_id_;
+
+    /// @brief Is this a fixed lease?
+    ///
+    /// Fixed leases are kept after they are released/expired.
+    bool fixed_;
+
+    /// @brief client hostname
+    ///
+    /// This field may be empty
+    std::string hostname_;
+
+    /// @brief did we update AAAA record for this lease?
+    bool fqdn_fwd_;
+
+    /// @brief did we update PTR record for this lease?
+    bool fqdn_rev_;
+
+    /// @brief Lease comments.
+    ///
+    /// Currently not used. It may be used for keeping comments made by the
+    /// system administrator.
+    std::string comments_;
+
+    /// @todo: Add DHCPv4 failover related fields here
+};
+
+/// @brief Pointer to a Lease4 structure.
+typedef boost::shared_ptr<Lease4> Lease4Ptr;
+
+/// @brief A collection of IPv4 leases.
+typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
+
+/// @brief Structure that holds a lease for IPv6 address and/or prefix
+///
+/// For performance reasons it is a simple structure, not a class. Had we chose to
+/// make it a class, all fields would have to be made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access rather than through getters/setters is warranted.
+struct Lease6 {
+    typedef enum {
+        LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
+        LEASE_IA_TA, /// the lease contains temporary IPv6 address
+        LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
+    } LeaseType;
+
+    /// @brief specifies lease type (normal addr, temporary addr, prefix)
+    LeaseType type_;
+
+    /// IPv6 address
+    isc::asiolink::IOAddress addr_;
+
+    /// IPv6 prefix length (used only for PD)
+    uint8_t prefixlen_;
+
+    /// @brief IAID
+    ///
+    /// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
+    /// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
+    /// than once in a message. To differentiate between them, IAID field is present
+    uint32_t iaid_;
+
+    /// @brief hardware address
+    ///
+    /// This field is not really used and is optional at best. The concept of identifying
+    /// clients by their hardware address was replaced in DHCPv6 by DUID concept. Each
+    /// client has its own unique DUID (DHCP Unique IDentifier). Furthermore, client's
+    /// HW address is not always available, because client may be behind a relay (relay
+    /// stores only link-local address).
+    std::vector<uint8_t> hwaddr_;
+
+    /// @brief client identifier
+    boost::shared_ptr<DUID> duid_;
+
+    /// @brief preferred lifetime
+    ///
+    /// This parameter specifies preferred lifetime since the lease was assigned/renewed
+    /// (cltt), expressed in seconds.
+    uint32_t preferred_lft_;
+
+    /// @brief valid lifetime
+    ///
+    /// This parameter specified valid lifetime since the lease was assigned/renewed
+    /// (cltt), expressed in seconds.
+    uint32_t valid_lft_;
+
+    /// @brief T1 timer
+    ///
+    /// Specifies renewal time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t1_;
+
+    /// @brief T2 timer
+    ///
+    /// Specifies rebinding time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t2_;
+
+    /// @brief client last transmission time
+    ///
+    /// Specifies a timestamp, when last transmission from a client was received.
+    time_t cltt_;
+
+    /// @brief Subnet identifier
+    ///
+    /// Specifies subnet-id of the subnet that the lease belongs to
+    SubnetID subnet_id_;
+
+    /// @brief Is this a fixed lease?
+    ///
+    /// Fixed leases are kept after they are released/expired.
+    bool fixed_;
+
+    /// @brief client hostname
+    ///
+    /// This field may be empty
+    std::string hostname_;
+
+    /// @brief did we update AAAA record for this lease?
+    bool fqdn_fwd_;
+
+    /// @brief did we update PTR record for this lease?
+    bool fqdn_rev_;
+
+    /// @brief Lease comments
+    ///
+    /// This field is currently not used.
+    std::string comments_;
+
+    /// @todo: Add DHCPv6 failover related fields here
+};
+
+/// @brief Pointer to a Lease6 structure.
+typedef boost::shared_ptr<Lease6> Lease6Ptr;
+
+/// @brief Const pointer to a Lease6 structure.
+typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
+
+/// @brief A collection of IPv6 leases.
+typedef std::vector< boost::shared_ptr<Lease6Ptr> > Lease6Collection;
+
+/// @brief Abstract Lease Manager
+///
+/// This is an abstract API for lease database backends. It provides unified
+/// interface to all backends. As this is an abstract class, it should not
+/// be used directly, but rather specialized derived class should be used
+/// instead.
+class LeaseMgr {
+public:
+
+    /// Client Hardware address
+    typedef std::vector<uint8_t> HWAddr;
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ///
+    /// @param dbconfig database configuration
+    LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor (closes file)
+    virtual ~LeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease4Ptr lease) = 0;
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease6Ptr lease) = 0;
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
+    ///
+    /// This method is used to get a lease for specific subnet_id. There can be
+    /// at most one lease for any given subnet, so this method returns a single
+    /// pointer.
+    ///
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns an IPv4 lease for specified IPv4 address
+    ///
+    /// This method return a lease that is associated with a given address.
+    /// For other query types (by hardware addr, by client-id) there can be
+    /// several leases in different subnets (e.g. for mobile clients that
+    /// got address in different subnets). However, for a single address
+    /// there can be only one lease, so this method returns a pointer to
+    /// a single lease, not a container of leases.
+    ///
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const = 0;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param clientid client identifier
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const ClientId& clientid) const = 0;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId& clientid,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// For a given address, we assume that there will be only one lease.
+    /// The assumtion here is that there will not be site or link-local
+    /// addresses used, so there is no way of having address duplication.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const = 0;
+
+    /// @brief Returns existing IPv6 leases for a given DUID+IA combination
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Collection getLease6(const DUID& duid,
+                                       uint32_t iaid) const = 0;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    /// @param subnet_id subnet id of the subnet the lease belongs to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease4(Lease4Ptr lease4) = 0;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease6(Lease6Ptr lease6) = 0;
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    virtual bool deleteLease4(uint32_t addr) = 0;
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    virtual bool deleteLease6(isc::asiolink::IOAddress addr) = 0;
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    virtual std::string getName() const = 0;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    virtual std::string getDescription() const = 0;
+
+    /// @brief Returns backend version.
+    ///
+    /// @todo: We will need to implement 3 version functions eventually:
+    /// A. abstract API version
+    /// B. backend version
+    /// C. database version (stored in the database scheme)
+    ///
+    /// and then check that:
+    /// B>=A and B=C (it is ok to have newer backend, as it should be backward
+    /// compatible)
+    /// Also if B>C, some database upgrade procedure may be triggered
+    virtual std::string getVersion() const = 0;
+
+    /// @todo: Add host management here
+    /// As host reservation is outside of scope for 2012, support for hosts
+    /// is currently postponed.
+
+protected:
+    /// @brief returns value of the parameter
+    std::string getParameter(const std::string& name) const;
+
+    /// @brief list of parameters passed in dbconfig
+    ///
+    /// That will be mostly used for storing database name, username,
+    /// password and other parameters required for DB access. It is not
+    /// intended to keep any DHCP-related parameters.
+    std::map<std::string, std::string> parameters_;
+};
+
+}; // end of isc::dhcp namespace
+
+}; // end of isc namespace

+ 32 - 1
src/lib/dhcp/pool.cc

@@ -30,6 +30,38 @@ bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
     return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
     return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
 }
 }
 
 
+Pool4::Pool4(const isc::asiolink::IOAddress& first,
+             const isc::asiolink::IOAddress& last)
+    :Pool(first, last) {
+    // check if specified address boundaries are sane
+    if (first.getFamily() != AF_INET || last.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+    }
+
+    if (last < first) {
+        isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
+    }
+}
+
+Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
+             uint8_t prefix_len)
+    :Pool(prefix, IOAddress("0.0.0.0")) {
+
+    // check if the prefix is sane
+    if (prefix.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+    }
+
+    // check if the prefix length is sane
+    if (prefix_len == 0 || prefix_len > 32) {
+        isc_throw(BadValue, "Invalid prefix length");
+    }
+
+    // Let's now calculate the last address in defined pool
+    last_ = lastAddrInPrefix(prefix, prefix_len);
+}
+
+
 Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
 Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
              const isc::asiolink::IOAddress& last)
              const isc::asiolink::IOAddress& last)
     :Pool(first, last), type_(type), prefix_len_(0) {
     :Pool(first, last), type_(type), prefix_len_(0) {
@@ -52,7 +84,6 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
         // last_ = first;
         // last_ = first;
     }
     }
 
 
-
     // TYPE_PD is not supported by this constructor. first-last style
     // TYPE_PD is not supported by this constructor. first-last style
     // parameters are for IA and TA only. There is another dedicated
     // parameters are for IA and TA only. There is another dedicated
     // constructor for that (it uses prefix/length)
     // constructor for that (it uses prefix/length)

+ 27 - 0
src/lib/dhcp/pool.h

@@ -91,6 +91,33 @@ protected:
     std::string comments_;
     std::string comments_;
 };
 };
 
 
+/// @brief Pool information for IPv4 addresses
+///
+/// It holds information about pool4, i.e. a range of IPv4 address space that
+/// is configured for DHCP allocation.
+class Pool4 : public Pool {
+public:
+    /// @brief the constructor for Pool4 "min-max" style definition
+    ///
+    /// @param first the first address in a pool
+    /// @param last the last address in a pool
+    Pool4(const isc::asiolink::IOAddress& first,
+          const isc::asiolink::IOAddress& last);
+
+    /// @brief the constructor for Pool4 "prefix/len" style definition
+    ///
+    /// @param prefix specifies prefix of the pool
+    /// @param prefix_len specifies length of the prefix of the pool
+    Pool4(const isc::asiolink::IOAddress& prefix,
+          uint8_t prefix_len);
+};
+
+/// @brief a pointer an IPv4 Pool
+typedef boost::shared_ptr<Pool4> Pool4Ptr;
+
+/// @brief a container for IPv4 Pools
+typedef std::vector<Pool4Ptr> Pool4Collection;
+
 /// @brief Pool information for IPv6 addresses and prefixes
 /// @brief Pool information for IPv6 addresses and prefixes
 ///
 ///
 /// It holds information about pool6, i.e. a range of IPv6 address space that
 /// It holds information about pool6, i.e. a range of IPv6 address space that

+ 44 - 0
src/lib/dhcp/subnet.cc

@@ -41,6 +41,50 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     return ((first <= addr) && (addr <= last));
     return ((first <= addr) && (addr <= last));
 }
 }
 
 
+Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
+                 const Triplet<uint32_t>& t1,
+                 const Triplet<uint32_t>& t2,
+                 const Triplet<uint32_t>& valid_lifetime)
+    :Subnet(prefix, length, t1, t2, valid_lifetime) {
+    if (prefix.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
+                  << " specified in subnet4");
+    }
+}
+
+void Subnet4::addPool4(const Pool4Ptr& pool) {
+    IOAddress first_addr = pool->getFirstAddress();
+    IOAddress last_addr = pool->getLastAddress();
+
+    if (!inRange(first_addr) || !inRange(last_addr)) {
+        isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
+                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
+                  << ") subnet4");
+    }
+
+    /// @todo: Check that pools do not overlap
+
+    pools_.push_back(pool);
+}
+
+Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
+    Pool4Ptr candidate;
+    for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+
+        // if we won't find anything better, then let's just use the first pool
+        if (!candidate) {
+            candidate = *pool;
+        }
+
+        // if the client provided a pool and there's a pool that hint is valid in,
+        // then let's use that pool
+        if ((*pool)->inRange(hint)) {
+            return (*pool);
+        }
+    }
+    return (candidate);
+}
+
 Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
 Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& t2,

+ 51 - 0
src/lib/dhcp/subnet.h

@@ -93,6 +93,57 @@ protected:
     Triplet<uint32_t> valid_;
     Triplet<uint32_t> valid_;
 };
 };
 
 
+/// @brief A configuration holder for IPv4 subnet.
+///
+/// This class represents an IPv4 subnet.
+class Subnet4 : public Subnet {
+public:
+
+    /// @brief Constructor with all parameters
+    ///
+    /// @param prefix Subnet4 prefix
+    /// @param length prefix length
+    /// @param t1 renewal timer (in seconds)
+    /// @param t2 rebind timer (in seconds)
+    /// @param valid_lifetime preferred lifetime of leases (in seconds)
+    Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
+            const Triplet<uint32_t>& t1,
+            const Triplet<uint32_t>& t2,
+            const Triplet<uint32_t>& valid_lifetime);
+
+    /// @brief Returns a pool that specified address belongs to
+    ///
+    /// @param hint address that the returned pool should cover (optional)
+    /// @return Pointer to found pool4 (or NULL)
+    Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
+                      isc::asiolink::IOAddress("0.0.0.0"));
+
+    /// @brief Adds a new pool.
+    /// @param pool pool to be added
+    void addPool4(const Pool4Ptr& pool);
+
+    /// @brief returns all pools
+    ///
+    /// The reference is only valid as long as the object that
+    /// returned it.
+    ///
+    /// @return a collection of all pools
+    const Pool4Collection& getPools() const {
+        return pools_;
+    }
+
+protected:
+    /// @brief collection of pools in that list
+    Pool4Collection pools_;
+};
+
+/// @brief A pointer to a Subnet4 object
+typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
+
+/// @brief A collection of Subnet6 objects
+typedef std::vector<Subnet4Ptr> Subnet4Collection;
+
+
 /// @brief A configuration holder for IPv6 subnet.
 /// @brief A configuration holder for IPv6 subnet.
 ///
 ///
 /// This class represents an IPv6 subnet.
 /// This class represents an IPv6 subnet.

+ 2 - 0
src/lib/dhcp/tests/Makefile.am

@@ -28,6 +28,7 @@ TESTS += libdhcp++_unittests libdhcpsrv_unittests
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += lease_mgr_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
@@ -35,6 +36,7 @@ libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
+libdhcp___unittests_SOURCES += duid_unittest.cc
 
 
 libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp___unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 libdhcp___unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 63 - 2
src/lib/dhcp/tests/addr_utilities_unittest.cc

@@ -26,7 +26,67 @@ using namespace std;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
-TEST(Pool6Test, lastAddrInPrefix) {
+// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
+    IOAddress addr1("192.0.2.1");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.255.255.255", lastAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.0.255.255",   lastAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.0.2.255",     lastAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.0.2.127", lastAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.0.2.63",  lastAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.0.2.31",  lastAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.0.2.15",  lastAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.0.2.7",   lastAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.0.2.3",   lastAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases
+    IOAddress anyAddr("0.0.0.0");
+    EXPECT_EQ("127.255.255.255", lastAddrInPrefix(anyAddr, 1).toText());
+    EXPECT_EQ("255.255.255.255", lastAddrInPrefix(anyAddr, 0).toText());
+    EXPECT_EQ("0.0.0.0", lastAddrInPrefix(anyAddr, 32).toText());
+}
+
+// This test checks if firstAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, firstAddrInPrefix4) {
+    IOAddress addr1("192.223.2.255");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.0.0.0",   firstAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.223.0.0", firstAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.223.2.0", firstAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.223.2.128", firstAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.223.2.192", firstAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.223.2.224", firstAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.223.2.240", firstAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.223.2.248", firstAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.223.2.252", firstAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.223.2.254", firstAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.223.2.255", firstAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases.
+    IOAddress bcast("255.255.255.255");
+    EXPECT_EQ("128.0.0.0", firstAddrInPrefix(bcast, 1).toText());
+    EXPECT_EQ("0.0.0.0", firstAddrInPrefix(bcast, 0).toText());
+    EXPECT_EQ("255.255.255.255", firstAddrInPrefix(bcast, 32).toText());
+
+}
+
+/// This test checks if lastAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, lastAddrInPrefix6) {
     IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
     IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
 
 
     // Prefixes rounded to nibbles are easy...
     // Prefixes rounded to nibbles are easy...
@@ -63,7 +123,8 @@ TEST(Pool6Test, lastAddrInPrefix) {
     EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
     EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
 }
 }
 
 
-TEST(Pool6Test, firstAddrInPrefix) {
+/// This test checks if firstAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
     IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
     IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
 
 
     // Prefixes rounded to nibbles are easy...
     // Prefixes rounded to nibbles are easy...

+ 36 - 0
src/lib/dhcp/tests/cfgmgr_unittest.cc

@@ -34,6 +34,38 @@ namespace {
 
 
 // This test verifies if the configuration manager is able to hold and return
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
 // valid leases
+TEST(CfgMgrTest, subnet4) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    ASSERT_TRUE(&cfg_mgr != 0);
+
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    // there shouldn't be any subnet configured at this stage
+    EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+
+    cfg_mgr.addSubnet4(subnet1);
+
+    // Now we have only one subnet, any request will be served from it
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
+
+    // Now we add more subnets and check that both old and new subnets
+    // are accessible.
+    cfg_mgr.addSubnet4(subnet2);
+    cfg_mgr.addSubnet4(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+
+    // Try to find an address that does not belong to any subnet
+    EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+}
+
+// This test verifies if the configuration manager is able to hold and return
+// valid leases
 TEST(CfgMgrTest, subnet6) {
 TEST(CfgMgrTest, subnet6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
 
@@ -58,6 +90,10 @@ TEST(CfgMgrTest, subnet6) {
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
     EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
     EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
 
 
+    cfg_mgr.deleteSubnets6();
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
 }
 }
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 169 - 0
src/lib/dhcp/tests/duid_unittest.cc

@@ -0,0 +1,169 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+// don't import the entire boost namespace.  It will unexpectedly hide uint8_t
+// for some systems.
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(DuidTest, constructor) {
+
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+
+    vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+    scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+    scoped_ptr<DUID> duid2(new DUID(data2));
+
+    vector<uint8_t> vecdata = duid1->getDuid();
+    EXPECT_TRUE(data2 == vecdata);
+    EXPECT_EQ(DUID::DUID_LLT, duid1->getType());
+
+    vecdata = duid2->getDuid();
+    EXPECT_TRUE(data2 == vecdata);
+
+    EXPECT_EQ(DUID::DUID_LLT, duid2->getType());
+}
+
+// This test verifies if DUID size restrictions are implemented
+// properly.
+TEST(DuidTest, size) {
+    const int MAX_DUID_SIZE = 128;
+    uint8_t data[MAX_DUID_SIZE + 1];
+    vector<uint8_t> data2;
+    for (uint8_t i = 0; i < MAX_DUID_SIZE + 1; ++i) {
+        data[i] = i;
+        if (i < MAX_DUID_SIZE)
+            data2.push_back(i);
+    }
+    ASSERT_EQ(data2.size(), MAX_DUID_SIZE);
+
+    scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_SIZE));
+    scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
+
+    EXPECT_THROW(
+        scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_SIZE + 1)),
+        OutOfRange);
+
+    // that's one too much
+    data2.push_back(128);
+
+    EXPECT_THROW(
+        scoped_ptr<DUID> toolarge2(new DUID(data2)),
+        OutOfRange);
+}
+
+// This test verifies if the implementation supports all defined
+// DUID types.
+TEST(DuidTest, getType) {
+    uint8_t llt[] =     {0, 1, 2, 3, 4, 5, 6};
+    uint8_t en[] =      {0, 2, 2, 3, 4, 5, 6};
+    uint8_t ll[] =      {0, 3, 2, 3, 4, 5, 6};
+    uint8_t uuid[] =    {0, 4, 2, 3, 4, 5, 6};
+    uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6};
+
+    scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt)));
+    scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en)));
+    scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll)));
+    scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid)));
+    scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid)));
+
+    EXPECT_EQ(DUID::DUID_LLT,     duid_llt->getType());
+    EXPECT_EQ(DUID::DUID_EN,      duid_en->getType());
+    EXPECT_EQ(DUID::DUID_LL,      duid_ll->getType());
+    EXPECT_EQ(DUID::DUID_UUID,    duid_uuid->getType());
+    EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
+}
+
+// This test checks if the comparison operators are sane.
+TEST(DuidTest, operators) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    uint8_t data2[] = {0, 1, 2, 3, 4};
+    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+    scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+    scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2)));
+    scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3)));
+    scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4)));
+
+    EXPECT_TRUE(*duid1 == *duid4);
+    EXPECT_FALSE(*duid1 == *duid2);
+    EXPECT_FALSE(*duid1 == *duid3);
+
+    EXPECT_FALSE(*duid1 != *duid4);
+    EXPECT_TRUE(*duid1 != *duid2);
+    EXPECT_TRUE(*duid1 != *duid3);
+}
+
+// This test verifies if the ClientId constructors are working properly
+// and passed parameters are used
+TEST(ClientIdTest, constructor) {
+    IOAddress addr2("192.0.2.1");
+    IOAddress addr3("2001:db8:1::1");
+
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+    // checks for C-style construtor (uint8_t * + len)
+    scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+    vector<uint8_t> vecdata = id1->getClientId();
+    EXPECT_TRUE(data2 == vecdata);
+
+    // checks for vector-based constructor
+    scoped_ptr<ClientId> id2(new ClientId(data2));
+    vecdata = id2->getClientId();
+    EXPECT_TRUE(data2 == vecdata);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(ClientIdTest, operators) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    uint8_t data2[] = {0, 1, 2, 3, 4};
+    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+    scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+    scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2)));
+    scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3)));
+    scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4)));
+
+    EXPECT_TRUE(*id1 == *id4);
+    EXPECT_FALSE(*id1 == *id2);
+    EXPECT_FALSE(*id1 == *id3);
+
+    EXPECT_FALSE(*id1 != *id4);
+    EXPECT_TRUE(*id1 != *id2);
+    EXPECT_TRUE(*id1 != *id3);
+}
+
+} // end of anonymous namespace

+ 296 - 0
src/lib/dhcp/tests/lease_mgr_unittest.cc

@@ -0,0 +1,296 @@
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/lease_mgr.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+// This is a concrete implementation of a Lease database.
+// It does not do anything useful now, and is used for abstract LeaseMgr
+// class testing. It may later evolve into more useful backend if the
+// need arises. We can reuse code from memfile benchmark. See code in
+// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
+class Memfile_LeaseMgr : public LeaseMgr {
+public:
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ///
+    /// @param dbconfig database configuration
+    Memfile_LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor (closes file)
+    virtual ~Memfile_LeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease4Ptr lease);
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease6Ptr lease);
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return a collection of leases
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+
+    /// @brief Returns existing IPv4 lease for specific address and subnet
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// @param clientid client identifier
+    virtual Lease4Collection getLease4(const ClientId& clientid) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId& clientid,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    ///
+    /// @return collection of IPv6 leases
+    Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    /// @param subnet_id identifier of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease4(Lease4Ptr lease4);
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease6(Lease6Ptr lease6);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease4(uint32_t addr);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease6(isc::asiolink::IOAddress addr);
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    std::string getName() const { return "memfile"; }
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    std::string getDescription() const;
+
+    /// @brief Returns backend version.
+    std::string getVersion() const { return "test-version"; }
+
+    using LeaseMgr::getParameter;
+
+protected:
+
+
+};
+
+Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
+    : LeaseMgr(dbconfig) {
+}
+
+Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+}
+
+bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease4>) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease6>) {
+    return (false);
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+    return (Lease4Collection());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
+    return (Lease4Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(isc::asiolink::IOAddress) const {
+    return (Lease6Ptr());
+}
+
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+    return (Lease6Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
+                                      SubnetID) const {
+    return (Lease6Ptr());
+}
+
+void Memfile_LeaseMgr::updateLease4(Lease4Ptr ) {
+}
+
+void Memfile_LeaseMgr::updateLease6(Lease6Ptr ) {
+
+}
+
+bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::deleteLease6(isc::asiolink::IOAddress ) {
+    return (false);
+}
+
+std::string Memfile_LeaseMgr::getDescription() const {
+    return (string("This is a dummy memfile backend implementation.\n"
+                   "It does not offer any useful lease management and its only\n"
+                   "purpose is to test abstract lease manager API."));
+}
+
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class LeaseMgrTest : public ::testing::Test {
+public:
+    LeaseMgrTest() {
+    }
+};
+
+// This test checks if the LeaseMgr can be instantiated and that it
+// parses parameters string properly.
+TEST_F(LeaseMgrTest, constructor) {
+
+    // should not throw any exceptions here
+    Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
+    delete leaseMgr;
+
+    leaseMgr = new Memfile_LeaseMgr("param1=value1 param2=value2");
+
+    EXPECT_EQ("value1", leaseMgr->getParameter("param1"));
+    EXPECT_EQ("value2", leaseMgr->getParameter("param2"));
+    EXPECT_THROW(leaseMgr->getParameter("param3"), BadValue);
+
+    delete leaseMgr;
+}
+
+// There's no point in calling any other methods in LeaseMgr, as they
+// are purely virtual, so we would only call Memfile_LeaseMgr methods.
+// Those methods are just stubs that does not return anything.
+// It seems likely that we will need to extend the memfile code for
+// allocation engine tests, so we may implement tests that call
+// Memfile_LeaseMgr methods then.
+
+}; // end of anonymous namespace

+ 73 - 0
src/lib/dhcp/tests/pool_unittest.cc

@@ -27,6 +27,79 @@ using namespace isc::asiolink;
 
 
 namespace {
 namespace {
 
 
+TEST(Pool4Test, constructor_first_last) {
+
+    // let's construct 192.0.2.1-192.0.2.255 pool
+    Pool4 pool1(IOAddress("192.0.2.1"), IOAddress("192.0.2.255"));
+
+    EXPECT_EQ(IOAddress("192.0.2.1"), pool1.getFirstAddress());
+    EXPECT_EQ(IOAddress("192.0.2.255"), pool1.getLastAddress());
+
+    // This is Pool4, IPv6 addresses do not belong here
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::1"),
+                       IOAddress("192.168.0.5")), BadValue);
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.168.0.2"),
+                       IOAddress("2001:db8::1")), BadValue);
+
+    // Should throw. Range should be 192.0.2.1-192.0.2.2, not
+    // the other way around.
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.0.2.2"),
+                       IOAddress("192.0.2.1")), BadValue);
+}
+
+TEST(Pool4Test, constructor_prefix_len) {
+
+    // let's construct 2001:db8:1::/96 pool
+    Pool4 pool1(IOAddress("192.0.2.0"), 25);
+
+    EXPECT_EQ("192.0.2.0", pool1.getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.127", pool1.getLastAddress().toText());
+
+    // No such thing as /33 prefix
+    EXPECT_THROW(Pool4(IOAddress("192.0.2.1"), 33), BadValue);
+
+    // /0 prefix does not make sense
+    EXPECT_THROW(Pool4(IOAddress("192.0.2.0"), 0), BadValue);
+
+    // This is Pool6, IPv4 addresses do not belong here
+    EXPECT_THROW(Pool4(IOAddress("2001:db8::1"), 20), BadValue);
+}
+
+TEST(Pool4Test, in_range) {
+   Pool4 pool1(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
+
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.0")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.10")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.17")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.20")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.21")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.255")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("255.255.255.255")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("0.0.0.0")));
+}
+
+// This test creates 100 pools and verifies that their IDs are unique.
+TEST(Pool4Test, unique_id) {
+
+    const int num_pools = 100;
+    std::vector<Pool4Ptr> pools;
+
+    for (int i = 0; i < num_pools; ++i) {
+        pools.push_back(Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
+                                           IOAddress("192.0.2.255"))));
+    }
+
+    for (int i = 0; i < num_pools; ++i) {
+        for (int j = i + 1; j < num_pools; ++j) {
+            if (pools[i]->getId() == pools[j]->getId()) {
+                FAIL() << "Pool-ids must be unique";
+            }
+        }
+    }
+
+}
+
+
 TEST(Pool6Test, constructor_first_last) {
 TEST(Pool6Test, constructor_first_last) {
 
 
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool

+ 80 - 2
src/lib/dhcp/tests/subnet_unittest.cc

@@ -29,6 +29,83 @@ using namespace isc::asiolink;
 
 
 namespace {
 namespace {
 
 
+TEST(Subnet4Test, constructor) {
+    EXPECT_NO_THROW(Subnet4 subnet1(IOAddress("192.0.2.2"), 16,
+                                    1, 2, 3));
+
+    EXPECT_THROW(Subnet4 subnet2(IOAddress("192.0.2.0"), 33, 1, 2, 3),
+                BadValue); // invalid prefix length
+    EXPECT_THROW(Subnet4 subnet3(IOAddress("2001:db8::1"), 24, 1, 2, 3),
+                BadValue); // IPv6 addresses are not allowed in Subnet4
+}
+
+TEST(Subnet4Test, in_range) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    EXPECT_EQ(1000, subnet.getT1());
+    EXPECT_EQ(2000, subnet.getT2());
+    EXPECT_EQ(3000, subnet.getValid());
+
+    EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.255")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("192.0.3.0")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("0.0.0.0")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
+}
+
+TEST(Subnet4Test, Pool4InSubnet4) {
+
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
+
+    Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+    Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+    Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+
+    subnet->addPool4(pool1);
+
+    // If there's only one pool, get that pool
+    Pool4Ptr mypool = subnet->getPool4();
+    EXPECT_EQ(mypool, pool1);
+
+
+    subnet->addPool4(pool2);
+    subnet->addPool4(pool3);
+
+    // If there are more than one pool and we didn't provide hint, we
+    // should get the first pool
+    mypool = subnet->getPool4();
+
+    EXPECT_EQ(mypool, pool1);
+
+    // If we provide a hint, we should get a pool that this hint belongs to
+    mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+
+    EXPECT_EQ(mypool, pool3);
+
+}
+
+TEST(Subnet4Test, Subnet4_Pool4_checks) {
+
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
+
+    // this one is in subnet
+    Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
+    subnet->addPool4(pool1);
+
+    // this one is larger than the subnet!
+    Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
+
+    EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+
+    // this one is totally out of blue
+    Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
+    EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+}
+
+// Tests for Subnet6
+
 TEST(Subnet6Test, constructor) {
 TEST(Subnet6Test, constructor) {
 
 
     EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
     EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
@@ -48,7 +125,6 @@ TEST(Subnet6Test, in_range) {
     EXPECT_EQ(3000, subnet.getPreferred());
     EXPECT_EQ(3000, subnet.getPreferred());
     EXPECT_EQ(4000, subnet.getValid());
     EXPECT_EQ(4000, subnet.getValid());
 
 
-
     EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
     EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
@@ -106,7 +182,9 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
     Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
     Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
     EXPECT_THROW(subnet->addPool6(pool3), BadValue);
     EXPECT_THROW(subnet->addPool6(pool3), BadValue);
 
 
-}
 
 
+    Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
+    EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+}
 
 
 };
 };

+ 5 - 0
src/lib/dhcp/triplet.h

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#ifndef TRIPLET_H
+#define TRIPLET_H
+
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 namespace isc {
 namespace isc {
@@ -108,3 +111,5 @@ protected:
 
 
 } // namespace isc::dhcp
 } // namespace isc::dhcp
 } // namespace isc
 } // namespace isc
+
+#endif // ifdef TRIPLET_H

+ 0 - 1
src/lib/dns/Makefile.am

@@ -107,7 +107,6 @@ libb10_dns___la_SOURCES += rdatafields.h rdatafields.cc
 libb10_dns___la_SOURCES += rrclass.cc
 libb10_dns___la_SOURCES += rrclass.cc
 libb10_dns___la_SOURCES += rrparamregistry.h
 libb10_dns___la_SOURCES += rrparamregistry.h
 libb10_dns___la_SOURCES += rrset.h rrset.cc
 libb10_dns___la_SOURCES += rrset.h rrset.cc
-libb10_dns___la_SOURCES += rrsetlist.h rrsetlist.cc
 libb10_dns___la_SOURCES += rrttl.h rrttl.cc
 libb10_dns___la_SOURCES += rrttl.h rrttl.cc
 libb10_dns___la_SOURCES += rrtype.cc
 libb10_dns___la_SOURCES += rrtype.cc
 libb10_dns___la_SOURCES += question.h question.cc
 libb10_dns___la_SOURCES += question.h question.cc

+ 0 - 60
src/lib/dns/rrsetlist.cc

@@ -1,60 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <vector>
-
-#include <boost/foreach.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-#include <dns/rrset.h>
-#include <dns/rrsetlist.h>
-
-namespace isc {
-namespace dns {
-
-void
-RRsetList::addRRset(RRsetPtr rrsetptr) {
-    ConstRRsetPtr rrset_found = findRRset(rrsetptr->getType(),
-                                          rrsetptr->getClass());
-    if (rrset_found != NULL) {
-        isc_throw(DuplicateRRset, "RRset is being doubly added to RRsetList: "
-                  "type=" << rrsetptr->getType() << ", class=" <<
-                  rrsetptr->getClass());
-    }
-    rrsets_.push_back(rrsetptr);
-}
-
-void
-RRsetList::append(RRsetList& source) {
-    BOOST_FOREACH(RRsetPtr rrset, source) {
-        addRRset(rrset);
-    }
-}
-
-RRsetPtr
-RRsetList::findRRset(const RRType& rrtype, const RRClass& rrclass) {
-    BOOST_FOREACH(RRsetPtr rrsetptr, rrsets_) {
-        if ((rrsetptr->getClass() == rrclass) &&
-            (rrsetptr->getType() == rrtype)) {
-            return (rrsetptr);
-        }
-    }
-    return (RRsetPtr());
-}
-
-}
-}

+ 0 - 132
src/lib/dns/rrsetlist.h

@@ -1,132 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __RRSETLIST_H
-#define __RRSETLIST_H 1
-
-#include <iostream>
-#include <iterator>
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-
-#include <dns/rrset.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-namespace isc {
-namespace dns {
-
-class DuplicateRRset : public Exception {
-public:
-    DuplicateRRset(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-template <typename T, typename P, typename R>
-class RRsetListIterator :
-        public std::iterator<std::input_iterator_tag, RRsetPtr> {
-public:
-    RRsetListIterator() {}
-    explicit RRsetListIterator(const T& it) :
-        it_(it) {}
-    RRsetListIterator& operator++()
-    {
-        ++it_;
-        return (*this);
-    }
-    RRsetListIterator operator++(int)
-    {
-        RRsetListIterator tmp(*this);
-        ++it_;
-        return (tmp);
-    }
-    R operator*() const
-    {
-        return (*it_);
-    }
-    P operator->() const
-    {
-        return (&(operator*()));
-    }
-    bool operator==(const RRsetListIterator& other)
-    {
-        return (it_ == other.it_);
-    }
-    bool operator!=(const RRsetListIterator& other)
-    {
-        return (it_ != other.it_);
-    }
-    
-private:
-    T it_;
-};
-
-/// A set of RRsets.
-///
-/// \note Do not use this class unless you really understand what
-/// you're doing and you're 100% sure that this class is the best choice
-/// for your purpose.
-///
-/// Counter intuitively, this class is not a "list" of RRsets but a
-/// "set" of them; it doesn't allow multiple RRsets of the same RR
-/// type and RR class to be added at the same time.  And, for that
-/// reason, adding an RRset is more expensive than you'd expect.  The
-/// class name is confusing, but was named so as a result of
-/// compromise: "RRsetset" would look awkward; RRsets would be
-/// confusing (with RRset).
-///
-/// In any case, if you want a list like container of RRsets, your best choice
-/// would be \c std::vector<RRset> or \c std::list<RRset>, not this class.
-/// In fact, in many cases \c RRsetList will be a suboptimal choice.
-/// This class is defined publicly as part of libdns++ for a historical
-/// reason and is actually quite specific to a particular need for libdatasrc.
-/// If you are tempted to use it, think twice to assess if this class
-/// is really what you want.  Again, in many cases the answer will be no.
-class RRsetList {
-private:
-    RRsetList(const RRsetList& source);
-    RRsetList& operator=(const RRsetList& source);
-public:
-    RRsetList() {}
-    void addRRset(RRsetPtr new_rrsetptr);
-    void append(RRsetList& source);
-    RRsetPtr findRRset(const RRType& rrtype, const RRClass& rrclass);
-
-    typedef RRsetListIterator<std::vector<RRsetPtr>::iterator,
-                              RRsetPtr*,
-                              RRsetPtr&> iterator;
-    typedef RRsetListIterator<std::vector<RRsetPtr>::const_iterator,
-                              const RRsetPtr*,
-                              const RRsetPtr&> const_iterator;
-
-    const_iterator begin() const { return (const_iterator(rrsets_.begin())); }
-    const_iterator end() const { return (const_iterator(rrsets_.end())); }
-
-    iterator begin() { return (iterator(rrsets_.begin())); }
-    iterator end() { return (iterator(rrsets_.end())); }
-
-    size_t size() const { return (rrsets_.size()); }
-
-private:
-    std::vector<RRsetPtr> rrsets_;
-};
-
-} // end of namespace dns
-} // end of namespace isc
-#endif  // __RRSETLIST_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 1 - 1
src/lib/dns/tests/Makefile.am

@@ -56,7 +56,7 @@ run_unittests_SOURCES += rdata_minfo_unittest.cc
 run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rdata_naptr_unittest.cc
 run_unittests_SOURCES += rdata_naptr_unittest.cc
 run_unittests_SOURCES += rdata_hinfo_unittest.cc
 run_unittests_SOURCES += rdata_hinfo_unittest.cc
-run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
+run_unittests_SOURCES += rrset_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += masterload_unittest.cc
 run_unittests_SOURCES += masterload_unittest.cc

+ 0 - 188
src/lib/dns/tests/rrsetlist_unittest.cc

@@ -1,188 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <vector>
-#include <boost/foreach.hpp>
-
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-#include <dns/rrsetlist.h>
-#include <dns/rrset.h>
-#include <dns/rrttl.h>
-
-#include <gtest/gtest.h>
-
-using namespace std;
-using namespace isc::dns;
-using namespace isc::dns::rdata;
-
-namespace {
-class RRsetListTest : public ::testing::Test {
-protected:
-    RRsetListTest() : example_name(Name("example.com")),
-                      example_ttl(RRTTL(3600))
-    {}
-    void setupList(RRsetList& list);
-    Name example_name;
-    RRTTL example_ttl;
-};
-
-const in::A rdata_in_a("192.0.2.1");
-const in::AAAA rdata_in_aaaa("2001:db8::1234");
-const generic::NS rdata_ns("ns.example.com");
-const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"),
-                             2010012601, 3600, 300, 3600000, 1200);
-const generic::CNAME rdata_cname("target.example.com");
-const generic::DNAME rdata_dname("dtarget.example.com");
-
-void
-RRsetListTest::setupList(RRsetList& list) {
-    RRsetPtr a(new RRset(Name("example.com"), RRClass::IN(),
-                         RRType::A(), example_ttl));
-    RRsetPtr aaaa(new RRset(Name("example.com"), RRClass::IN(),
-                            RRType::AAAA(), example_ttl));
-    RRsetPtr ns(new RRset(Name("example.com"), RRClass::IN(),
-                          RRType::NS(), example_ttl));
-    RRsetPtr soa(new RRset(Name("example.com"), RRClass::IN(),
-                           RRType::SOA(), example_ttl));
-    RRsetPtr cname(new RRset(Name("example.com"), RRClass::IN(),
-                             RRType::CNAME(), example_ttl));
-
-    a->addRdata(rdata_in_a);
-    aaaa->addRdata(rdata_in_aaaa);
-    ns->addRdata(rdata_ns);
-    soa->addRdata(rdata_soa);
-    cname->addRdata(rdata_cname);
-
-    list.addRRset(a);
-    list.addRRset(aaaa);
-    list.addRRset(ns);
-    list.addRRset(soa);
-    list.addRRset(cname);
-}
-
-TEST_F(RRsetListTest, emptyOnInitialCreate) {
-    RRsetList list;
-    EXPECT_EQ(list.size(), 0);
-}
-
-TEST_F(RRsetListTest, addRRsets) {
-    RRsetList list;
-    setupList(list);
-    EXPECT_EQ(list.size(), 5);
-}
-
-TEST_F(RRsetListTest, append) {
-    RRsetList list1;
-    setupList(list1);
-    RRsetList list2;
-    RRsetPtr dname(new RRset(Name("example.com"), RRClass::IN(),
-                             RRType::DNAME(), example_ttl));
-    dname->addRdata(rdata_dname);
-    list2.addRRset(dname);
-    list1.append(list2);
-    EXPECT_EQ(list2.size(), 1);
-    EXPECT_EQ(list1.size(), 6);
-
-    RRsetPtr rrset = list1.findRRset(RRType::DNAME(), RRClass::IN());
-    EXPECT_EQ(RRType::DNAME(), rrset->getType());
-
-    EXPECT_THROW(list1.append(list2), DuplicateRRset);
-}
-
-TEST_F(RRsetListTest, extraRRset) {
-    RRsetList list;
-    setupList(list);
-    RRsetPtr cname(new RRset(Name("another.example.com"), RRClass::IN(),
-                             RRType::CNAME(), example_ttl));
-    EXPECT_THROW(list.addRRset(cname), DuplicateRRset);
-}
-
-void
-checkFindResult(RRsetList& list, const Name& name,
-                const RRType& rrtype, const RRClass& rrclass,
-                const RRTTL& rrttl)
-{
-    RRsetPtr rrset = list.findRRset(rrtype, rrclass);;
-    EXPECT_EQ(name, rrset->getName());
-    EXPECT_EQ(rrtype, rrset->getType());
-    EXPECT_EQ(rrclass, rrset->getClass());
-    EXPECT_EQ(rrttl, rrset->getTTL());
-}
-
-TEST_F(RRsetListTest, findRRset) {
-    RRsetList list;
-    setupList(list);
- 
-    checkFindResult(list, example_name, RRType::A(), RRClass::IN(),
-                    example_ttl);
-    checkFindResult(list, example_name, RRType::CNAME(), RRClass::IN(),
-                    example_ttl);
-    checkFindResult(list, example_name, RRType::AAAA(), RRClass::IN(),
-                    example_ttl);
-    checkFindResult(list, example_name, RRType::NS(), RRClass::IN(),
-                    example_ttl);
-    checkFindResult(list, example_name, RRType::SOA(), RRClass::IN(),
-                    example_ttl);
-}
-
-TEST_F(RRsetListTest, checkData) {
-    RRsetList list;
-    RRsetPtr a(new RRset(Name("example.com"), RRClass::IN(),
-                         RRType::A(), example_ttl));
-    a->addRdata(rdata_in_a);
-    list.addRRset(a);
-
-    RdataIteratorPtr it =
-        list.findRRset(RRType::A(), RRClass::IN())->getRdataIterator();
-    EXPECT_FALSE(it->isLast());
-    EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
-}
-
-TEST_F(RRsetListTest, iterate) {
-    RRsetList list;
-    setupList(list);
-
-    bool has_a = false, has_aaaa = false, has_ns = false, has_soa = false,
-        has_cname = false;
-    int i = 0;
-    BOOST_FOREACH(RRsetPtr rrset, list) {
-        if (rrset->getType() == RRType::A()) {
-            has_a = true;
-        }
-        if (rrset->getType() == RRType::AAAA()) {
-            has_aaaa = true;
-        }
-        if (rrset->getType() == RRType::NS()) {
-            has_ns = true;
-        }
-        if (rrset->getType() == RRType::SOA()) {
-            has_soa = true;
-        }
-        if (rrset->getType() == RRType::CNAME()) {
-            has_cname = true;
-        }
-        ++i;
-    }
-    EXPECT_TRUE(has_a);
-    EXPECT_TRUE(has_aaaa);
-    EXPECT_TRUE(has_ns);
-    EXPECT_TRUE(has_soa);
-    EXPECT_TRUE(has_cname);
-    EXPECT_TRUE(i == 5);
-}
-
-}

+ 27 - 9
src/lib/python/isc/bind10/component.py

@@ -45,6 +45,7 @@ COMPONENT_RESTART_DELAY = 10
 
 
 STATE_DEAD = 'dead'
 STATE_DEAD = 'dead'
 STATE_STOPPED = 'stopped'
 STATE_STOPPED = 'stopped'
+STATE_RESTARTING = 'restarting'
 STATE_RUNNING = 'running'
 STATE_RUNNING = 'running'
 
 
 def get_signame(signal_number):
 def get_signame(signal_number):
@@ -68,6 +69,7 @@ class BaseComponent:
       explicitly).
       explicitly).
     - Running - after start() was called, it started successfully and is
     - Running - after start() was called, it started successfully and is
       now running.
       now running.
+    - Restarting - the component failed (crashed) and is waiting for a restart
     - Dead - it failed and can not be resurrected.
     - Dead - it failed and can not be resurrected.
 
 
     Init
     Init
@@ -79,11 +81,11 @@ class BaseComponent:
                     |            |                |
                     |            |                |
                     |failure     | failed()       |
                     |failure     | failed()       |
                     |            |                |
                     |            |                |
-                    v            |                |
+                    v            |                | start()/restart()
                     +<-----------+                |
                     +<-----------+                |
                     |                             |
                     |                             |
                     |  kind == dispensable or kind|== needed and failed late
                     |  kind == dispensable or kind|== needed and failed late
-                    +-----------------------------+
+                    +-----------------------> Restarting
                     |
                     |
                     | kind == core or kind == needed and it failed too soon
                     | kind == core or kind == needed and it failed too soon
                     v
                     v
@@ -163,7 +165,7 @@ class BaseComponent:
         """
         """
         if self.__state == STATE_DEAD:
         if self.__state == STATE_DEAD:
             raise ValueError("Can't resurrect already dead component")
             raise ValueError("Can't resurrect already dead component")
-        if self.running():
+        if self.is_running():
             raise ValueError("Can't start already running component")
             raise ValueError("Can't start already running component")
         logger.info(BIND10_COMPONENT_START, self.name())
         logger.info(BIND10_COMPONENT_START, self.name())
         self.__state = STATE_RUNNING
         self.__state = STATE_RUNNING
@@ -188,7 +190,7 @@ class BaseComponent:
         """
         """
         # This is not tested. It talks with the outher world, which is out
         # This is not tested. It talks with the outher world, which is out
         # of scope of unittests.
         # of scope of unittests.
-        if not self.running():
+        if not self.is_running():
             raise ValueError("Can't stop a component which is not running")
             raise ValueError("Can't stop a component which is not running")
         logger.info(BIND10_COMPONENT_STOP, self.name())
         logger.info(BIND10_COMPONENT_STOP, self.name())
         self.__state = STATE_STOPPED
         self.__state = STATE_STOPPED
@@ -234,9 +236,9 @@ class BaseComponent:
 
 
         logger.error(BIND10_COMPONENT_FAILED, self.name(), self.pid(),
         logger.error(BIND10_COMPONENT_FAILED, self.name(), self.pid(),
                      exit_str)
                      exit_str)
-        if not self.running():
+        if not self.is_running():
             raise ValueError("Can't fail component that isn't running")
             raise ValueError("Can't fail component that isn't running")
-        self.__state = STATE_STOPPED
+        self.__state = STATE_RESTARTING # tentatively set, maybe changed to DEAD
         self._failed_internal()
         self._failed_internal()
         # If it is a core component or the needed component failed to start
         # If it is a core component or the needed component failed to start
         # (including it stopped really soon)
         # (including it stopped really soon)
@@ -284,7 +286,7 @@ class BaseComponent:
         else:
         else:
             return False
             return False
 
 
-    def running(self):
+    def is_running(self):
         """
         """
         Informs if the component is currently running. It assumes the failed
         Informs if the component is currently running. It assumes the failed
         is called whenever the component really fails and there might be some
         is called whenever the component really fails and there might be some
@@ -296,6 +298,15 @@ class BaseComponent:
         """
         """
         return self.__state == STATE_RUNNING
         return self.__state == STATE_RUNNING
 
 
+    def is_restarting(self):
+        """Informs if the component has failed and is waiting for a restart.
+
+        Unlike the case of is_running(), if this returns True it always means
+        the corresponding process has died and not yet restarted.
+
+        """
+        return self.__state == STATE_RESTARTING
+
     def _start_internal(self):
     def _start_internal(self):
         """
         """
         This method does the actual starting of a process. You need to override
         This method does the actual starting of a process. You need to override
@@ -560,6 +571,13 @@ class Configurator:
         self._running = False
         self._running = False
         self.__reconfigure_internal(self._components, {})
         self.__reconfigure_internal(self._components, {})
 
 
+    def has_component(self, component):
+        '''Return if a specified component is configured.'''
+        # Values of self._components are tuples of (config, component).
+        # Extract the components of the tuples and see if the given one
+        # is included.
+        return component in map(lambda x: x[1], self._components.values())
+
     def reconfigure(self, configuration):
     def reconfigure(self, configuration):
         """
         """
         Changes configuration from the current one to the provided. It
         Changes configuration from the current one to the provided. It
@@ -591,7 +609,7 @@ class Configurator:
         for cname in old.keys():
         for cname in old.keys():
             if cname not in new:
             if cname not in new:
                 component = self._components[cname][1]
                 component = self._components[cname][1]
-                if component.running():
+                if component.is_running() or component.is_restarting():
                     plan.append({
                     plan.append({
                         'command': STOP_CMD,
                         'command': STOP_CMD,
                         'component': component,
                         'component': component,
@@ -674,7 +692,7 @@ class Configurator:
                     self._components[task['name']] = (task['config'],
                     self._components[task['name']] = (task['config'],
                                                       component)
                                                       component)
                 elif command == STOP_CMD:
                 elif command == STOP_CMD:
-                    if component.running():
+                    if component.is_running():
                         component.stop()
                         component.stop()
                     del self._components[task['name']]
                     del self._components[task['name']]
                 else:
                 else:

+ 5 - 0
src/lib/python/isc/bind10/sockcreator.py

@@ -214,9 +214,14 @@ class Creator(Parser):
                                 socket.SOCK_STREAM)
                                 socket.SOCK_STREAM)
         env = copy.deepcopy(os.environ)
         env = copy.deepcopy(os.environ)
         env['PATH'] = path
         env['PATH'] = path
+        # We explicitly set close_fs to True; it's False by default before
+        # Python 3.2.  If we don't close the remaining FDs, the copy of
+        # 'local' will prevent the child process from terminating when
+        # the parent process died abruptly.
         self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
         self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
                                           stdin=remote.fileno(),
                                           stdin=remote.fileno(),
                                           stdout=remote2.fileno(),
                                           stdout=remote2.fileno(),
+                                          close_fds=True,
                                           preexec_fn=self.__preexec_work)
                                           preexec_fn=self.__preexec_work)
         remote.close()
         remote.close()
         remote2.close()
         remote2.close()

+ 43 - 22
src/lib/python/isc/bind10/tests/component_test.py

@@ -191,7 +191,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertFalse(self.__start_called)
         self.assertFalse(self.__start_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__failed_called)
         self.assertFalse(self.__failed_called)
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
+        self.assertFalse(component.is_restarting())
         # We can't stop or fail the component yet
         # We can't stop or fail the component yet
         self.assertRaises(ValueError, component.stop)
         self.assertRaises(ValueError, component.stop)
         self.assertRaises(ValueError, component.failed, 1)
         self.assertRaises(ValueError, component.failed, 1)
@@ -204,7 +205,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(self.__start_called)
         self.assertTrue(self.__start_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__failed_called)
         self.assertFalse(self.__failed_called)
-        self.assertTrue(component.running())
+        self.assertTrue(component.is_running())
+        self.assertFalse(component.is_restarting())
 
 
     def __check_dead(self, component):
     def __check_dead(self, component):
         """
         """
@@ -215,7 +217,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__stop_called)
         self.assertTrue(self.__failed_called)
         self.assertTrue(self.__failed_called)
         self.assertEqual(1, self._exitcode)
         self.assertEqual(1, self._exitcode)
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
+        self.assertFalse(component.is_restarting())
         # Surely it can't be stopped when already dead
         # Surely it can't be stopped when already dead
         self.assertRaises(ValueError, component.stop)
         self.assertRaises(ValueError, component.stop)
         # Nor started
         # Nor started
@@ -234,7 +237,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(self.__start_called)
         self.assertTrue(self.__start_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__stop_called)
         self.assertTrue(self.__failed_called)
         self.assertTrue(self.__failed_called)
-        self.assertTrue(component.running())
+        self.assertTrue(component.is_running())
+        self.assertFalse(component.is_restarting())
         # Check it can't be started again
         # Check it can't be started again
         self.assertRaises(ValueError, component.start)
         self.assertRaises(ValueError, component.start)
 
 
@@ -246,7 +250,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(self.__start_called)
         self.assertTrue(self.__start_called)
         self.assertFalse(self.__stop_called)
         self.assertFalse(self.__stop_called)
         self.assertTrue(self.__failed_called)
         self.assertTrue(self.__failed_called)
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
+        self.assertTrue(component.is_restarting())
 
 
     def __do_start_stop(self, kind):
     def __do_start_stop(self, kind):
         """
         """
@@ -270,7 +275,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(self.__start_called)
         self.assertTrue(self.__start_called)
         self.assertTrue(self.__stop_called)
         self.assertTrue(self.__stop_called)
         self.assertFalse(self.__failed_called)
         self.assertFalse(self.__failed_called)
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
+        self.assertFalse(component.is_restarting())
         # Check it can't be stopped twice
         # Check it can't be stopped twice
         self.assertRaises(ValueError, component.stop)
         self.assertRaises(ValueError, component.stop)
         # Or failed
         # Or failed
@@ -553,10 +559,10 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertIsNone(component.pid())
         self.assertIsNone(component.pid())
         self.assertEqual(['hello'], component._params)
         self.assertEqual(['hello'], component._params)
         self.assertEqual('Address', component._address)
         self.assertEqual('Address', component._address)
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
         self.assertEqual({}, self.__registered_processes)
         self.assertEqual({}, self.__registered_processes)
         component.start()
         component.start()
-        self.assertTrue(component.running())
+        self.assertTrue(component.is_running())
         # Some versions of unittest miss assertIsInstance
         # Some versions of unittest miss assertIsInstance
         self.assertTrue(isinstance(component._procinfo, TestProcInfo))
         self.assertTrue(isinstance(component._procinfo, TestProcInfo))
         self.assertEqual(42, component.pid())
         self.assertEqual(42, component.pid())
@@ -580,11 +586,11 @@ class ComponentTests(BossUtils, unittest.TestCase):
         """
         """
         component = Component('component', self, 'needed', 'Address')
         component = Component('component', self, 'needed', 'Address')
         component.start()
         component.start()
-        self.assertTrue(component.running())
+        self.assertTrue(component.is_running())
         self.assertEqual('component', self.__start_simple_params)
         self.assertEqual('component', self.__start_simple_params)
         component.pid = lambda: 42
         component.pid = lambda: 42
         component.stop()
         component.stop()
-        self.assertFalse(component.running())
+        self.assertFalse(component.is_running())
         self.assertEqual(('component', 'Address', 42),
         self.assertEqual(('component', 'Address', 42),
                          self.__stop_process_params)
                          self.__stop_process_params)
 
 
@@ -609,7 +615,7 @@ class ComponentTests(BossUtils, unittest.TestCase):
         component = Component('component', self, 'needed', 'Address',
         component = Component('component', self, 'needed', 'Address',
                               [], ProcInfo)
                               [], ProcInfo)
         component.start()
         component.start()
-        self.assertTrue(component.running())
+        self.assertTrue(component.is_running())
         component.kill()
         component.kill()
         self.assertTrue(process.terminated)
         self.assertTrue(process.terminated)
         self.assertFalse(process.killed)
         self.assertFalse(process.killed)
@@ -872,12 +878,13 @@ class ConfiguratorTest(BossUtils, unittest.TestCase):
             'priority': 6,
             'priority': 6,
             'special': 'test',
             'special': 'test',
             'process': 'additional',
             'process': 'additional',
-            'kind': 'needed'
+            'kind': 'dispensable' # need to be dispensable so it could restart
         }
         }
         self.log = []
         self.log = []
         plan = configurator._build_plan(self.__build_components(self.__core),
         plan = configurator._build_plan(self.__build_components(self.__core),
                                         bigger)
                                         bigger)
-        self.assertEqual([('additional', 'init'), ('additional', 'needed')],
+        self.assertEqual([('additional', 'init'),
+                          ('additional', 'dispensable')],
                          self.log)
                          self.log)
         self.assertEqual(1, len(plan))
         self.assertEqual(1, len(plan))
         self.assertTrue('component' in plan[0])
         self.assertTrue('component' in plan[0])
@@ -888,15 +895,29 @@ class ConfiguratorTest(BossUtils, unittest.TestCase):
         # Now remove the one component again
         # Now remove the one component again
         # We run the plan so the component is wired into internal structures
         # We run the plan so the component is wired into internal structures
         configurator._run_plan(plan)
         configurator._run_plan(plan)
-        self.log = []
-        plan = configurator._build_plan(self.__build_components(bigger),
-                                        self.__core)
-        self.assertEqual([], self.log)
-        self.assertEqual([{
-            'command': 'stop',
-            'name': 'additional',
-            'component': component
-        }], plan)
+        # We should have the 'additional' component in the configurator.
+        self.assertTrue(configurator.has_component(component))
+        for count in [0, 1]:    # repeat two times, see below
+            self.log = []
+            plan = configurator._build_plan(self.__build_components(bigger),
+                                            self.__core)
+            self.assertEqual([], self.log)
+            self.assertEqual([{
+                        'command': 'stop',
+                        'name': 'additional',
+                        'component': component
+                        }], plan)
+
+            if count is 0:
+                # We then emulate unexpected failure of the component (but
+                # before it restarts).  It shouldn't confuse the
+                # configurator in the second phase of the test
+                component.failed(0)
+        # Run the plan, confirm the specified component is gone.
+        configurator._run_plan(plan)
+        self.assertFalse(configurator.has_component(component))
+        # There shouldn't be any other object that has the same name
+        self.assertFalse('additional' in configurator._components)
 
 
         # We want to switch a component. So, prepare the configurator so it
         # We want to switch a component. So, prepare the configurator so it
         # holds one
         # holds one

+ 6 - 2
src/lib/util/locks.h

@@ -42,13 +42,17 @@ class upgradable_mutex {
 template <typename T>
 template <typename T>
 class sharable_lock {
 class sharable_lock {
 public:
 public:
-    sharable_lock(T) { }
+    sharable_lock(T) {}
 };
 };
 
 
 template <typename T>
 template <typename T>
 class scoped_lock {
 class scoped_lock {
 public:
 public:
-    scoped_lock(T) { }
+    scoped_lock(T) {}
+
+    // We need to define this explicitly.  Some versions of clang++ would
+    // complain about this otherwise.  See Trac ticket #2340
+    ~scoped_lock() {}
 
 
     void lock() {}
     void lock() {}
     void unlock() {}
     void unlock() {}