Parcourir la source

Merge branch 'master' into trac2155_2

Conflicts:
	doc/Doxyfile
	src/bin/auth/Makefile.am
	src/bin/auth/benchmarks/Makefile.am
	src/bin/auth/statistics.h
	src/bin/auth/tests/Makefile.am
	src/bin/auth/tests/auth_srv_unittest.cc
Yoshitaka Aharen il y a 12 ans
Parent
commit
61d7c3959e
100 fichiers modifiés avec 4921 ajouts et 1109 suppressions
  1. 135 4
      ChangeLog
  2. 4 0
      INSTALL
  3. 135 93
      configure.ac
  4. 3 2
      doc/Doxyfile
  5. 0 95
      doc/devel/02-dhcp.dox
  6. 9 0
      doc/devel/mainpage.dox
  7. 89 10
      doc/guide/bind10-guide.xml
  8. 0 0
      examples/AUTHORS
  9. 13 0
      examples/COPYING
  10. 0 0
      examples/ChangeLog
  11. 9 0
      examples/INSTALL
  12. 4 0
      examples/Makefile.am
  13. 0 0
      examples/NEWS
  14. 32 0
      examples/README
  15. 28 0
      examples/configure.ac
  16. 0 0
      examples/host/.gitignore
  17. 6 0
      examples/host/Makefile.am
  18. 0 0
      examples/host/README
  19. 0 0
      examples/host/b10-host.xml
  20. 0 0
      examples/host/host.cc
  21. 64 0
      examples/m4/ax_boost_include.m4
  22. 122 0
      examples/m4/ax_isc_bind10.m4
  23. 1 1
      src/bin/Makefile.am
  24. 3 1
      src/bin/auth/Makefile.am
  25. 4 4
      src/bin/auth/auth_config.h
  26. 6 0
      src/bin/auth/auth_log.cc
  27. 8 8
      src/bin/auth/auth_log.h
  28. 95 0
      src/bin/auth/auth_messages.mes
  29. 20 44
      src/bin/auth/auth_srv.cc
  30. 23 32
      src/bin/auth/auth_srv.h
  31. 2 0
      src/bin/auth/benchmarks/Makefile.am
  32. 23 19
      src/bin/auth/benchmarks/query_bench.cc
  33. 6 4
      src/bin/auth/command.cc
  34. 3 3
      src/bin/auth/command.h
  35. 3 3
      src/bin/auth/common.h
  36. 586 0
      src/bin/auth/datasrc_clients_mgr.h
  37. 24 0
      src/bin/auth/datasrc_config.cc
  38. 82 0
      src/bin/auth/datasrc_config.h
  39. 0 223
      src/bin/auth/datasrc_configurator.h
  40. 57 21
      src/bin/auth/main.cc
  41. 3 3
      src/bin/auth/statistics.h
  42. 6 1
      src/bin/auth/tests/Makefile.am
  43. 66 70
      src/bin/auth/tests/auth_srv_unittest.cc
  44. 84 55
      src/bin/auth/tests/command_unittest.cc
  45. 523 0
      src/bin/auth/tests/datasrc_clients_builder_unittest.cc
  46. 205 0
      src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
  47. 102 101
      src/bin/auth/tests/datasrc_configurator_unittest.cc
  48. 3 3
      src/bin/auth/tests/datasrc_util.h
  49. 8 9
      src/bin/auth/tests/query_unittest.cc
  50. 95 0
      src/bin/auth/tests/test_datasrc_clients_mgr.cc
  51. 223 0
      src/bin/auth/tests/test_datasrc_clients_mgr.h
  52. 34 7
      src/bin/bind10/bind10_messages.mes
  53. 37 24
      src/bin/bind10/bind10_src.py.in
  54. 69 2
      src/bin/bind10/tests/bind10_test.py.in
  55. 96 35
      src/bin/bindctl/bindcmd.py
  56. 30 19
      src/bin/bindctl/bindctl_main.py.in
  57. 18 18
      src/bin/bindctl/cmdparse.py
  58. 140 70
      src/bin/bindctl/tests/bindctl_test.py
  59. 1 1
      src/bin/cfgmgr/Makefile.am
  60. 11 0
      src/bin/cfgmgr/local_plugins/Makefile.am
  61. 1 1
      src/bin/cfgmgr/plugins/Makefile.am
  62. 2 2
      src/bin/cfgmgr/plugins/datasrc.spec.pre.in
  63. 8 1
      src/bin/dbutil/dbutil.py.in
  64. 2 0
      src/bin/dbutil/tests/Makefile.am
  65. 24 24
      src/bin/dbutil/tests/dbutil_test.sh.in
  66. 1 0
      src/bin/dbutil/tests/testdata/Makefile.am
  67. BIN
      src/bin/dbutil/tests/testdata/v2_1.sqlite3
  68. 3 3
      src/bin/dhcp4/dhcp4_log.h
  69. 1 0
      src/bin/dhcp4/tests/Makefile.am
  70. 2 0
      src/bin/dhcp6/Makefile.am
  71. 797 0
      src/bin/dhcp6/config_parser.cc
  72. 147 0
      src/bin/dhcp6/config_parser.h
  73. 25 5
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  74. 79 0
      src/bin/dhcp6/dhcp6.dox
  75. 90 2
      src/bin/dhcp6/dhcp6.spec
  76. 3 3
      src/bin/dhcp6/dhcp6_log.h
  77. 20 0
      src/bin/dhcp6/dhcp6_messages.mes
  78. 7 0
      src/bin/dhcp6/dhcp6_srv.cc
  79. 4 0
      src/bin/dhcp6/tests/Makefile.am
  80. 243 0
      src/bin/dhcp6/tests/config_parser_unittest.cc
  81. 0 37
      src/bin/host/Makefile.am
  82. 3 3
      src/bin/resolver/resolver.h
  83. 3 3
      src/bin/resolver/resolver_log.h
  84. 1 1
      src/bin/resolver/resolver_messages.mes
  85. 3 3
      src/bin/resolver/response_scrubber.h
  86. 3 3
      src/bin/sockcreator/sockcreator.h
  87. 2 1
      src/bin/sysinfo/.gitignore
  88. 1 0
      src/bin/sysinfo/Makefile.am
  89. 38 0
      src/bin/sysinfo/run_sysinfo.sh.in
  90. 2 4
      src/bin/sysinfo/sysinfo.py.in
  91. 7 0
      src/bin/tests/process_rename_test.py.in
  92. 15 0
      src/cppcheck-suppress.lst
  93. 13 10
      src/lib/acl/dns.cc
  94. 3 3
      src/lib/acl/dnsname_check.h
  95. 3 3
      src/lib/acl/ip_check.h
  96. 3 3
      src/lib/acl/tests/sockaddr.h
  97. 3 3
      src/lib/asiodns/asiodns.h
  98. 3 3
      src/lib/asiodns/dns_answer.h
  99. 3 3
      src/lib/asiodns/dns_lookup.h
  100. 0 0
      src/lib/asiodns/dns_server.h

+ 135 - 4
ChangeLog

@@ -1,3 +1,134 @@
+497.	[bug]		jinmei
+	Fixed several issues in isc-sysinfo:
+	- make sure it doesn't report a negative value for free memory
+	  size (this happened on FreeBSD, but can possibly occur on other
+	  BSD variants)
+	- correctly identifies the SMP support in kernel on FreeBSD
+	- print more human readable uptime as well as the time in seconds
+	(Trac #2297, git 59a449f506948e2371ffa87dcd19059388bd1657)
+
+496.	[func]		tomek
+	DHCPv6 Allocation Engine implemented. It allows address allocation
+	from the configured subnets/pools. It currently features a single
+	allocator: IterativeAllocator, which assigns addresses iteratively.
+	Other allocators (hashed, random) are planned.
+	(Trac #2324, git 8aa188a10298e3a55b725db36502a99d2a8d638a)
+
+495.	[func]		team
+	b10-auth now handles reconfiguration of data sources in
+	background using a separate thread.  This means even if the new
+	configuration includes a large amount of data to be loaded into
+	memory (very large zones and/or a very large number of zones),
+	the reconfiguration doesn't block query handling.
+	(Multiple Trac tickets up to #2211)
+
+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 tries 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
+	All public header files for libb10-dns++ are now installed.
+	Template configure.ac and utility AC macros for external projects
+	using the library are provided under the "examples" directory.
+	The src/bin/host was moved as part of the examples (and not
+	installed with other BIND 10 programs any more).
+	(Trac #1870, git 4973e638d354d8b56dcadf71123ef23c15662021)
+
+485.	[bug]		jelte
+	Several bugs have been fixed in bindctl; tab-completion now works
+	within configuration lists, the problem where sometimes the
+	completion added a part twice has been solved, and it no longer
+	suggests the confusing value 'argument' as a completion-hint for
+	configuration items. Additionally, bindctl no longer crashes upon
+	input like 'config remove Boss'.
+	(Trac #2254, git 9047de5e8f973e12e536f7180738e6b515439448)
+
+484.	[func]		tomek
+	A new library (libb10-dhcpsrv) has been created. At present, it
+	only holds the code for the DHCP Configuration Manager. Currently
+	this object only supports basic configuration storage for the DHCPv6
+	server,	but that capability will be expanded.
+	(Trac #2238, git 6f29861b92742da34be9ae76968e82222b5bfd7d)
+
+bind10-devel-20120927 released on September 27, 2012
+
+483.	[func]		marcin
+	libdhcp++: Added new parameter to define sub-second timeout
+	for DHCP packet reception. The total timeout is now specified
+	by two parameters:  first specifies integral number of
+	seconds, second (which defaults to 0) specifies fractional
+	seconds with microsecond resolution.
+	(Trac #2231, git 15560cac16e4c52129322e3cb1787e0f47cf7850)
+
 482.	[func]		team
 482.	[func]		team
 	Memory footprint of the in-memory data source has been
 	Memory footprint of the in-memory data source has been
 	substantially improved.  For example, b10-auth now requires much
 	substantially improved.  For example, b10-auth now requires much
@@ -9,11 +140,11 @@
 	of the memory image.  Also, loading zones in memory still suspends
 	of the memory image.  Also, loading zones in memory still suspends
 	query processing, so manual reloading or reloading after incoming
 	query processing, so manual reloading or reloading after incoming
 	transfer may cause service disruption for huge zones.
 	transfer may cause service disruption for huge zones.
-	(Multiple Trac tickets)
+	(Multiple Trac tickets, Summarized in Trac #2101)
 
 
 481.	[bug]		vorner
 481.	[bug]		vorner
 	The abbreviated form of IP addresses in ACLs is accepted
 	The abbreviated form of IP addresses in ACLs is accepted
-	(eg. "from": ["127.0.01", "::1"] now works).
+	(eg. "from": ["127.0.0.1", "::1"] now works).
 	(Trac #2191, git 48b6e91386b46eed383126ad98dddfafc9f7e75e)
 	(Trac #2191, git 48b6e91386b46eed383126ad98dddfafc9f7e75e)
 
 
 480.	[doc]		vorner
 480.	[doc]		vorner
@@ -45,10 +176,10 @@
 	(Trac #2190, git e0ffa11d49ab949ee5a4ffe7682b0e6906667baa)
 	(Trac #2190, git e0ffa11d49ab949ee5a4ffe7682b0e6906667baa)
 
 
 476.	[bug]		vorner
 476.	[bug]		vorner
-	The XfrIn now accepts transfers with some TSIG signatures omitted, as
+	The Xfrin now accepts transfers with some TSIG signatures omitted, as
 	allowed per RFC2845, section 4.4. This solves a compatibility
 	allowed per RFC2845, section 4.4. This solves a compatibility
 	issues with Knot and NSD.
 	issues with Knot and NSD.
-	(Trac #1375, git 7ca65cb9ec528118f370142d7e7b792fcc31c9cf)
+	(Trac #1357, git 7ca65cb9ec528118f370142d7e7b792fcc31c9cf)
 
 
 475.	[func]		naokikambe
 475.	[func]		naokikambe
 	Added Xfrout statistics counters: notifyoutv4, notifyoutv6,
 	Added Xfrout statistics counters: notifyoutv4, notifyoutv6,

+ 4 - 0
INSTALL

@@ -7,3 +7,7 @@ To then build from source:
 
 
 For detailed installation directions, see the guide
 For detailed installation directions, see the guide
 at doc/guide/bind10-guide.txt or doc/guide/bind10-guide.html.
 at doc/guide/bind10-guide.txt or doc/guide/bind10-guide.html.
+
+You can find user-contributed OS-specific build/installation
+instructions on the BIND 10 wiki:
+http://bind10.isc.org/wiki/SystemSpecificNotes

+ 135 - 93
configure.ac

@@ -4,7 +4,7 @@
 AC_PREREQ([2.59])
 AC_PREREQ([2.59])
 AC_INIT(bind10-devel, 20120817, bind10-dev@isc.org)
 AC_INIT(bind10-devel, 20120817, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AC_CONFIG_SRCDIR(README)
-AM_INIT_AUTOMAKE
+AM_INIT_AUTOMAKE([foreign])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4macros])
 AC_CONFIG_MACRO_DIR([m4macros])
@@ -12,6 +12,20 @@ AC_CONFIG_MACRO_DIR([m4macros])
 # Checks for programs.
 # Checks for programs.
 AC_PROG_CXX
 AC_PROG_CXX
 
 
+# Enable low-performing debugging facilities? This option optionally
+# enables some debugging aids that perform slowly and hence aren't built
+# by default.
+AC_ARG_ENABLE([debug],
+  AS_HELP_STRING([--enable-debug],
+    [enable debugging (default is no)]),
+  [case "${enableval}" in
+    yes) debug_enabled=yes ;;
+    no)  debug_enabled=no ;;
+    *)   AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
+  esac],[debug_enabled=no])
+AM_CONDITIONAL([DEBUG_ENABLED], [test x$debug_enabled = xyes])
+AM_COND_IF([DEBUG_ENABLED], [AC_DEFINE([ENABLE_DEBUG], [1], [Enable low-performing debugging facilities?])])
+
 # Libtool configuration
 # Libtool configuration
 #
 #
 
 
@@ -70,6 +84,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],
@@ -103,6 +217,10 @@ case "$host" in
 	CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 	CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 	# "now" binding is necessary to prevent deadlocks in C++ static initialization code
 	# "now" binding is necessary to prevent deadlocks in C++ static initialization code
 	LDFLAGS="$LDFLAGS -z now"
 	LDFLAGS="$LDFLAGS -z now"
+	# Destroying locked mutexes, condition variables being waited
+	# on, etc. are undefined behavior on Solaris, so we set it as
+	# such here.
+	AC_DEFINE([HAS_UNDEFINED_PTHREAD_BEHAVIOR], [1], [Does this platform have some undefined pthreads behavior?])
 	;;
 	;;
 *-apple-darwin*)
 *-apple-darwin*)
 	# Starting with OSX 10.7 (Lion) we must choose which IPv6 API to use
 	# Starting with OSX 10.7 (Lion) we must choose which IPv6 API to use
@@ -132,7 +250,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 +374,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 +404,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"
@@ -1055,6 +1085,13 @@ AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Chec
 AC_PATH_PROG(VALGRIND, valgrind, no)
 AC_PATH_PROG(VALGRIND, valgrind, no)
 AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
 AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
 
 
+# Also check for valgrind headers
+# We could consider adding them to the source code tree, as this
+# is the encouraged method of using them; they are BSD-licensed.
+# However, until we find that this is a problem, we just use
+# the system-provided ones, if available
+AC_CHECK_HEADERS(valgrind/valgrind.h, [AC_DEFINE([HAVE_VALGRIND_HEADERS], [1], [Check valgrind headers])])
+
 found_valgrind="not found"
 found_valgrind="not found"
 if test "x$VALGRIND" != "xno"; then
 if test "x$VALGRIND" != "xno"; then
    found_valgrind="found"
    found_valgrind="found"
@@ -1093,13 +1130,13 @@ AC_CONFIG_FILES([Makefile
                  src/bin/bindctl/Makefile
                  src/bin/bindctl/Makefile
                  src/bin/bindctl/tests/Makefile
                  src/bin/bindctl/tests/Makefile
                  src/bin/cfgmgr/Makefile
                  src/bin/cfgmgr/Makefile
+                 src/bin/cfgmgr/local_plugins/Makefile
                  src/bin/cfgmgr/plugins/Makefile
                  src/bin/cfgmgr/plugins/Makefile
                  src/bin/cfgmgr/plugins/tests/Makefile
                  src/bin/cfgmgr/plugins/tests/Makefile
                  src/bin/cfgmgr/tests/Makefile
                  src/bin/cfgmgr/tests/Makefile
                  src/bin/dbutil/Makefile
                  src/bin/dbutil/Makefile
                  src/bin/dbutil/tests/Makefile
                  src/bin/dbutil/tests/Makefile
                  src/bin/dbutil/tests/testdata/Makefile
                  src/bin/dbutil/tests/testdata/Makefile
-                 src/bin/host/Makefile
                  src/bin/loadzone/Makefile
                  src/bin/loadzone/Makefile
                  src/bin/loadzone/tests/correct/Makefile
                  src/bin/loadzone/tests/correct/Makefile
                  src/bin/loadzone/tests/error/Makefile
                  src/bin/loadzone/tests/error/Makefile
@@ -1215,6 +1252,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/server_common/tests/Makefile
                  src/lib/server_common/tests/Makefile
                  src/lib/util/Makefile
                  src/lib/util/Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/io/Makefile
+                 src/lib/util/threads/Makefile
+                 src/lib/util/threads/tests/Makefile
                  src/lib/util/unittests/Makefile
                  src/lib/util/unittests/Makefile
                  src/lib/util/python/Makefile
                  src/lib/util/python/Makefile
                  src/lib/util/pyunittests/Makefile
                  src/lib/util/pyunittests/Makefile
@@ -1260,6 +1299,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/run_b10-zonemgr.sh
            src/bin/zonemgr/run_b10-zonemgr.sh
            src/bin/sysinfo/sysinfo.py
            src/bin/sysinfo/sysinfo.py
+           src/bin/sysinfo/run_sysinfo.sh
            src/bin/stats/stats.py
            src/bin/stats/stats.py
            src/bin/stats/stats_httpd.py
            src/bin/stats/stats_httpd.py
            src/bin/bind10/bind10_src.py
            src/bin/bind10/bind10_src.py
@@ -1338,6 +1378,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/bin/loadzone/run_loadzone.sh
            chmod +x src/bin/loadzone/run_loadzone.sh
            chmod +x src/bin/loadzone/tests/correct/correct_test.sh
            chmod +x src/bin/loadzone/tests/correct/correct_test.sh
            chmod +x src/bin/loadzone/tests/error/error_test.sh
            chmod +x src/bin/loadzone/tests/error/error_test.sh
+           chmod +x src/bin/sysinfo/run_sysinfo.sh
            chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            chmod +x src/bin/msgq/run_msgq.sh
            chmod +x src/bin/msgq/run_msgq.sh
            chmod +x src/bin/msgq/tests/msgq_test
            chmod +x src/bin/msgq/tests/msgq_test
@@ -1401,6 +1442,7 @@ Features:
   $enable_features
   $enable_features
 
 
 Developer:
 Developer:
+  Enable Debugging: $debug_enabled
   Google Tests: $enable_gtest
   Google Tests: $enable_gtest
   Valgrind: $found_valgrind
   Valgrind: $found_valgrind
   C++ Code Coverage: $USE_LCOV
   C++ Code Coverage: $USE_LCOV

+ 3 - 2
doc/Doxyfile

@@ -579,8 +579,9 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
-    ../src/lib/resolve ../src/lib/acl ../src/lib/statistics ../src/lib/dhcp \
-    ../src/bin/dhcp6 ../src/bin/dhcp4 ../tests/tools/perfdhcp devel
+    ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \
+    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
+    ../tests/tools/perfdhcp devel
 
 
 # This tag can be used to specify the character encoding of the source files
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

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

@@ -57,99 +57,4 @@
  * 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++
- *
- * @section libdhcpIntro Libdhcp++ Library Introduction
- *
- * libdhcp++ is an all-purpose DHCP-manipulation library, written in
- * C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
- * options parsing and ssembly, interface detection (currently on
- * Linux systems only) and socket operations. Following classes are
- * implemented:
- *
- * - isc::dhcp::Pkt4 - represents DHCPv4 packet.
- * - isc::dhcp::Pkt6 - represents DHCPv6 packet.
- *
- * There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
- * smart pointer and are using boost::shared_ptr. There are not const
- * versions defined, as we assume that hooks can modify any aspect of
- * the packet at almost any stage of processing.
- *
- * Both packets use collection of Option objects to represent DHCPv4
- * and DHCPv6 options. The base class -- Option -- can be used to
- * represent generic option that contains collection of
- * bytes. Depending on if the option is instantiated as v4 or v6
- * option, it will adjust its header (DHCPv4 options use 1 octet for
- * type and 1 octet for length, while DHCPv6 options use 2 bytes for
- * each).
- *
- * There are many specialized classes that are intended to handle options with
- * specific content:
- * - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
- * - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
- * - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
- *                     contains IPv6 address with extra parameters);
- * - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
- *
- * All options can store sub-options (i.e. options that are stored within option
- * rather than in a message directly). This functionality is commonly used in
- * DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
- * isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
- * for that purpose.
- *
- * @section libdhcpIfaceMgr Interface Manager
- *
- * Interface Manager (or IfaceMgr) is an abstraction layer about low-level
- * network operations. In particlar, it provides information about existing
- * network interfaces See isc::dhcp::IfaceMgr::Iface class and
- * isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
- *
- * Currently there is interface detection is implemented in Linux only. There
- * are plans to implement such support for other OSes, but they remain low
- * priority for now.
- *
- * Generic parts of the code are isc::dhcp::IfaceMgr class in
- * src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
- * files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
- * additional code will be developed.
- *
- * For systems that interface detection is not supported on, there is a stub
- * mechanism implemented. It assumes that interface name is read from a text
- * file. This is a temporary solution and will be removed as soon as proper
- * interface detection is implemented. It is not going to be developed further.
- * To use this feature, store interfaces.txt file. It uses a simple syntax.
- * Each line represents an interface name, followed by IPv4 or IPv6 address
- * that follows it. This is usually link-local IPv6 address that the server
- * should bind to. In theory this mechanism also supports IPv4, but it was
- * never tested. The code currently supports only a single interface defined
- * that way.
- *
- * Another useful methods are dedicated to transmission
- * (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
- * (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
- * Note that receive4() and receive6() methods may return NULL, e.g.
- * when timeout is reached or if dhcp daemon receives a signal.
  */
  */

+ 9 - 0
doc/devel/mainpage.dox

@@ -15,15 +15,24 @@
  * <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
+ * - @subpage libdhcpsrv
+ *   - @subpage leasemgr
+ *   - @subpage cfgmgr
+ *   - @subpage allocengine
  * - @subpage perfdhcpInternals
  * - @subpage perfdhcpInternals
  *
  *
  * @section misc Miscellaneous topics
  * @section misc Miscellaneous topics

+ 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

NEWS → examples/AUTHORS


+ 13 - 0
examples/COPYING

@@ -0,0 +1,13 @@
+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.

TODO → examples/ChangeLog


+ 9 - 0
examples/INSTALL

@@ -0,0 +1,9 @@
+If using git (not the tarball), build the "configure" file:
+    autoreconf --install
+
+To then build from source:
+    ./configure
+    make
+
+You may have to specify the location of BIND 10 header files and
+library objects.  See configure options by ./configure --help.

+ 4 - 0
examples/Makefile.am

@@ -0,0 +1,4 @@
+SUBDIRS = host
+
+# Make sure macros under m4 will be included
+ACLOCAL_AMFLAGS = -I m4

+ 0 - 0
examples/NEWS


+ 32 - 0
examples/README

@@ -0,0 +1,32 @@
+This is the top directory for sample programs that can be developed
+using public BIND 10 libraries outside of the BIND 10 project.  It's
+intended to be built with installed BIND 10 header files and library
+objects, so it's not a target of the main build tree, and does not
+refer to any other part of the BIND 10 source tree that contains
+this directory.
+
+On the top (sub) directory (where this README file is stored), we
+provide a sample configure.ac and Makefile.am files for GNU automake
+environments with helper autoconf macros to detect the availability and
+location of BIND 10 header files and library objects.
+
+You can use the configure.ac and Makefile.am files with macros under
+the "m4" subdirectory as a template for your own project.  The key is
+to call the AX_ISC_BIND10 function (as the sample configure.ac does)
+from your configure.ac.  Then it will check the availability of
+necessary stuff and set some corresponding AC variables.  You can then
+use the resulting variables in your Makefile.in or Makefile.ac.
+
+If you use automake, don't forget adding the following line to the top
+level Makefile.am:
+
+ACLOCAL_AMFLAGS = -I m4
+
+This is necessary to incorporate the helper macro definitions.
+
+If you don't use automake but autoconf, make sure to add the following
+to the configure.ac file:
+
+sinclude(m4/ax_boost_include.m4)
+sinclude(m4/ax_isc_bind10.m4)
+(and same for other m4 files as they are added under m4/)

+ 28 - 0
examples/configure.ac

@@ -0,0 +1,28 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.59])
+AC_INIT(bind10-examples, 20120817, bind10-dev@isc.org)
+AC_CONFIG_SRCDIR([README])
+AM_INIT_AUTOMAKE
+AC_CONFIG_HEADERS([config.h])
+
+# Checks for programs.
+AC_PROG_CXX
+AC_LANG([C++])
+
+# Checks for BIND 10 headers and libraries
+AX_ISC_BIND10
+
+# For the example host program, we require the BIND 10 DNS library
+if test "x$BIND10_DNS_LIB" = "x"; then
+   AC_MSG_ERROR([unable to find BIND 10 DNS library needed to build 'host'])
+fi
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_HEADER_STDBOOL
+
+AC_CONFIG_FILES([Makefile
+                 host/Makefile])
+
+AC_OUTPUT

src/bin/host/.gitignore → examples/host/.gitignore


+ 6 - 0
examples/host/Makefile.am

@@ -0,0 +1,6 @@
+AM_CPPFLAGS = $(BOOST_CPPFLAGS) $(BIND10_CPPFLAGS)
+
+bin_PROGRAMS = b10-host
+b10_host_SOURCES = host.cc
+b10_host_LDFLAGS = ${BIND10_LDFLAGS}
+b10_host_LDADD = ${BIND10_DNS_LIB}

src/bin/host/README → examples/host/README


src/bin/host/b10-host.xml → examples/host/b10-host.xml


src/bin/host/host.cc → examples/host/host.cc


+ 64 - 0
examples/m4/ax_boost_include.m4

@@ -0,0 +1,64 @@
+dnl @synopsis AX_BOOST_INCLUDE
+dnl
+dnl Test for the Boost C++ header files
+dnl
+dnl If no path to the installed boost header files is given via the
+dnl --with-boost-include option,  the macro searchs under
+dnl /usr/local /usr/pkg /opt /opt/local directories.
+dnl
+dnl This macro calls:
+dnl
+dnl   AC_SUBST(BOOST_CPPFLAGS)
+dnl
+
+AC_DEFUN([AX_BOOST_INCLUDE], [
+AC_LANG_SAVE
+AC_LANG([C++])
+
+#
+# Configure Boost header path
+#
+# If explicitly specified, use it.
+AC_ARG_WITH([boost-include],
+  AS_HELP_STRING([--with-boost-include=PATH],
+    [specify exact directory for Boost headers]),
+    [boost_include_path="$withval"])
+# If not specified, try some common paths.
+if test -z "$with_boost_include"; then
+	boostdirs="/usr/local /usr/pkg /opt /opt/local"
+	for d in $boostdirs
+	do
+		if test -f $d/include/boost/shared_ptr.hpp; then
+			boost_include_path=$d/include
+			break
+		fi
+	done
+fi
+CPPFLAGS_SAVES="$CPPFLAGS"
+if test "${boost_include_path}" ; then
+	BOOST_CPPFLAGS="-I${boost_include_path}"
+	CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+fi
+# Make sure some commonly used headers are available
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/bind.hpp boost/function.hpp],,
+  AC_MSG_ERROR([Missing required Boost header files.]))
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly.  In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
+AC_SUBST(BOOST_CPPFLAGS)
+
+AC_LANG_RESTORE
+])dnl AX_BOOST_INCLUDE

+ 122 - 0
examples/m4/ax_isc_bind10.m4

@@ -0,0 +1,122 @@
+dnl @synopsis AX_BIND10
+dnl
+dnl @summary figure out how to build C++ programs using ISC BIND 10 libraries
+dnl
+dnl If no path to the installed BIND 10 header files or libraries is given
+dnl via the --with-bind10-include  or --with-bind10-lib option, the macro
+dnl searchs under /usr/local/{include, lib}, /usr/pkg/{include, lib},
+dnl /opt/{include, lib}, /opt/local/{include, lib} directories, respectively.
+dnl
+dnl This macro calls:
+dnl
+dnl   AC_SUBST(BIND10_CPPFLAGS)
+dnl   AC_SUBST(BIND10_LDFLAGS)
+dnl   AC_SUBST(BIND10_COMMON_LIB)
+dnl   AC_SUBST(BIND10_DNS_LIB)
+dnl
+dnl If this macro finds CPPFLAGS, LDFLAGS or COMMON_LIB unavailable, it treats
+dnl that as a fatal error.
+dnl Checks for other BIND 10 module libraries are option, as not all
+dnl applications need all libraries.  The main configure.ac can handle any
+dnl missing library as fatal by checking whether the corresponding
+dnl BIND10_xxx_LIB is defined.
+
+AC_DEFUN([AX_ISC_BIND10], [
+AC_REQUIRE([AX_BOOST_INCLUDE])
+AC_LANG_SAVE
+AC_LANG([C++])
+
+# Check for BIND10 common headers
+
+AC_ARG_WITH(bind10-include,
+  AS_HELP_STRING([--with-bind10-include=PATH],
+  [specify a path to BIND 10 header files]),
+    bind10_inc_path="$withval", bind10_inc_path="no")
+# If not specified, try some common paths.
+if test "$bind10_inc_path" = "no"; then
+   for d in /usr/local /usr/pkg /opt /opt/local
+   do
+	if test -f $d/include/util/buffer.h; then
+	   bind10_inc_path=$d
+	   break
+	fi
+   done
+fi
+CPPFLAGS_SAVES="$CPPFLAGS"
+if test "${bind10_inc_path}" != "no"; then
+   BIND10_CPPFLAGS="-I${bind10_inc_path}"
+   CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS"
+fi
+AC_CHECK_HEADERS([util/buffer.h],,
+  AC_MSG_ERROR([Missing a commonly used BIND 10 header files]))
+CPPFLAGS="$CPPFLAGS_SAVES"
+AC_SUBST(BIND10_CPPFLAGS)
+
+# Check for BIND10 libraries
+CPPFLAGS_SAVED="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS"
+
+AC_ARG_WITH(bind10-lib,
+  AS_HELP_STRING([--with-bind10-lib=PATH],
+  [specify a path to BIND 10 library files]),
+    bind10_lib_path="$withval", bind10_lib_path="no")
+if test $bind10_lib_path != "no"; then
+   bind10_lib_dirs=$bind10_lib_path
+else
+   # If not specified, try some common paths.
+   bind10_lib_dirs="/usr/local/lib /usr/pkg/lib /opt/lib /opt/local/lib"
+fi
+
+# make sure we have buildable libraries
+AC_MSG_CHECKING([for BIND 10 common library])
+BIND10_COMMON_LIB="-lb10-util -lb10-exceptions"
+LDFLAGS="$LDFLAGS $BIND10_LDFLAGS"
+LIBS="$LIBS $BIND10_COMMON_LIB"
+for d in $bind10_lib_dirs
+do
+  LDFLAGS_SAVED="$LDFLAGS"
+  LDFLAGS="$LDFLAGS -L$d"
+  AC_TRY_LINK([
+#include <util/buffer.h>
+],[
+isc::util::OutputBuffer buffer(0);
+], [BIND10_LDFLAGS="-L${d}"])
+  if test "x$BIND10_LDFLAGS" != "x"; then
+     break
+  fi
+  LDFLAGS="$LDFLAGS_SAVED"
+done
+if test "x$BIND10_LDFLAGS" != "x"; then
+  AC_MSG_RESULT(yes)
+else
+  AC_MSG_RESULT(no)
+  AC_MSG_ERROR([unable to find required BIND 10 libraries])
+fi
+
+# restore LIBS once at this point
+LIBS="$LIBS_SAVES"
+
+AC_SUBST(BIND10_LDFLAGS)
+AC_SUBST(BIND10_COMMON_LIB)
+
+# Check per-module BIND 10 libraries
+
+# DNS library
+AC_MSG_CHECKING([for BIND 10 DNS library])
+LIBS="$LIBS $BIND10_COMMON_LIB -lb10-dns++"
+AC_TRY_LINK([
+#include <dns/rrtype.h>
+],[
+isc::dns::RRType rrtype(1);
+], [BIND10_DNS_LIB="-lb10-dns++"
+    AC_MSG_RESULT(yes)],
+   [AC_MSG_RESULT(no)])
+LIBS="$LIBS_SAVES"
+AC_SUBST(BIND10_DNS_LIB)
+
+# Restore other flags
+CPPFLAGS="$CPPFLAGS_SAVED"
+LDFLAGS="$LDFLAGS_SAVES"
+
+AC_LANG_RESTORE
+])dnl AX_ISC_BIND10

+ 1 - 1
src/bin/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq host cmdctl auth xfrin \
+SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq cmdctl auth xfrin \
 	xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 \
 	xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 \
 	dbutil sysinfo
 	dbutil sysinfo
 
 

+ 3 - 1
src/bin/auth/Makefile.am

@@ -55,7 +55,8 @@ b10_auth_SOURCES += auth_config.cc auth_config.h
 b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += statistics.cc statistics.h statistics_items.h
 b10_auth_SOURCES += statistics.cc statistics.h statistics_items.h
-b10_auth_SOURCES += datasrc_configurator.h
+b10_auth_SOURCES += datasrc_clients_mgr.h
+b10_auth_SOURCES += datasrc_config.h datasrc_config.cc
 b10_auth_SOURCES += main.cc
 b10_auth_SOURCES += main.cc
 
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
@@ -73,6 +74,7 @@ b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_auth_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_auth_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libb10-xfr.la
 b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libb10-xfr.la
 b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
 b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
+b10_auth_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 b10_auth_LDADD += $(SQLITE_LIBS)
 b10_auth_LDADD += $(SQLITE_LIBS)
 
 
 # TODO: config.h.in is wrong because doesn't honor pkgdatadir
 # TODO: config.h.in is wrong because doesn't honor pkgdatadir

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

@@ -18,8 +18,8 @@
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 
 
-#ifndef __CONFIG_H
-#define __CONFIG_H 1
+#ifndef CONFIG_H
+#define CONFIG_H 1
 
 
 class AuthSrv;
 class AuthSrv;
 
 
@@ -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
@@ -195,7 +195,7 @@ void configureAuthServer(AuthSrv& server,
 AuthConfigParser* createAuthConfigParser(AuthSrv& server,
 AuthConfigParser* createAuthConfigParser(AuthSrv& server,
                                          const std::string& config_id);
                                          const std::string& config_id);
 
 
-#endif // __CONFIG_H
+#endif // CONFIG_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 6 - 0
src/bin/auth/auth_log.cc

@@ -21,6 +21,12 @@ namespace auth {
 
 
 isc::log::Logger auth_logger("auth");
 isc::log::Logger auth_logger("auth");
 
 
+const int DBG_AUTH_START = DBGLVL_START_SHUT;
+const int DBG_AUTH_SHUT = DBGLVL_START_SHUT;
+const int DBG_AUTH_OPS = DBGLVL_COMMAND;
+const int DBG_AUTH_DETAIL = DBGLVL_TRACE_BASIC;
+const int DBG_AUTH_MESSAGES = DBGLVL_TRACE_DETAIL_DATA;
+
 } // namespace auth
 } // namespace auth
 } // namespace isc
 } // namespace isc
 
 

+ 8 - 8
src/bin/auth/auth_log.h

@@ -12,8 +12,8 @@
 // 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 __AUTH_LOG__H
-#define __AUTH_LOG__H
+#ifndef AUTH_LOG_H
+#define AUTH_LOG_H
 
 
 #include <log/macros.h>
 #include <log/macros.h>
 #include <auth/auth_messages.h>
 #include <auth/auth_messages.h>
@@ -28,21 +28,21 @@ namespace auth {
 /// output.
 /// output.
 
 
 // Debug messages indicating normal startup are logged at this debug level.
 // Debug messages indicating normal startup are logged at this debug level.
-const int DBG_AUTH_START = DBGLVL_START_SHUT;
+extern const int DBG_AUTH_START;
 // Debug messages upon shutdown
 // Debug messages upon shutdown
-const int DBG_AUTH_SHUT = DBGLVL_START_SHUT;
+extern const int DBG_AUTH_SHUT;
 
 
 // Debug level used to log setting information (such as configuration changes).
 // Debug level used to log setting information (such as configuration changes).
-const int DBG_AUTH_OPS = DBGLVL_COMMAND;
+extern const int DBG_AUTH_OPS;
 
 
 // Trace detailed operations, including errors raised when processing invalid
 // Trace detailed operations, including errors raised when processing invalid
 // packets.  (These are not logged at severities of WARN or higher for fear
 // packets.  (These are not logged at severities of WARN or higher for fear
 // that a set of deliberately invalid packets set to the authoritative server
 // that a set of deliberately invalid packets set to the authoritative server
 // could overwhelm the logging.)
 // could overwhelm the logging.)
-const int DBG_AUTH_DETAIL = DBGLVL_TRACE_BASIC;
+extern const int DBG_AUTH_DETAIL;
 
 
 // This level is used to log the contents of packets received and sent.
 // This level is used to log the contents of packets received and sent.
-const int DBG_AUTH_MESSAGES = DBGLVL_TRACE_DETAIL_DATA;
+extern const int DBG_AUTH_MESSAGES;
 
 
 /// Define the logger for the "auth" module part of b10-auth.  We could define
 /// Define the logger for the "auth" module part of b10-auth.  We could define
 /// a logger in each file, but we would want to define a common name to avoid
 /// a logger in each file, but we would want to define a common name to avoid
@@ -53,4 +53,4 @@ extern isc::log::Logger auth_logger;
 } // namespace nsas
 } // namespace nsas
 } // namespace isc
 } // namespace isc
 
 
-#endif // __AUTH_LOG__H
+#endif // AUTH_LOG_H

+ 95 - 0
src/bin/auth/auth_messages.mes

@@ -57,6 +57,101 @@ At attempt to update the configuration the server with information
 from the configuration database has failed, the reason being given in
 from the configuration database has failed, the reason being given in
 the message.
 the message.
 
 
+% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND data source builder received command: %1
+A debug message, showing when the separate thread for maintaining data
+source clients receives a command from the manager.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR command execution failure: %1
+The separate thread for maintaining data source clients failed to complete a
+command given by the main thread.  In most cases this is some kind of
+configuration or temporary error such as an attempt to load a non-existent
+zone or a temporary DB connection failure.  So the event is just logged and
+the thread keeps running.  In some rare cases, however, this may indicate an
+internal bug and it may be better to restart the entire program, so the log
+message should be carefully examined.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_FAILED data source builder thread stopped due to an exception: %1
+The separate thread for maintaining data source clients has been
+terminated due to some uncaught exception.  When this happens, the
+thread immediately terminates the entire process because the manager
+cannot always catch this condition in a timely fashion and it would be
+worse to keep running with such a half-broken state.  This is really
+an unexpected event and should generally indicate an internal bug.
+It's advisable to file a bug report when this message is logged (and
+b10-auth subsequently stops).
+
+% AUTH_DATASRC_CLIENTS_BUILDER_FAILED_UNEXPECTED data source builder thread stopped due to an unexpected exception
+This is similar to AUTH_DATASRC_CLIENTS_BUILDER_FAILED, but the
+exception type indicates it's not thrown either within the BIND 10
+implementation or other standard-compliant libraries.  This may rather
+indicate some run time failure than program errors.  As in the other
+failure case, the thread terminates the entire process immediately
+after logging this message.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE loaded zone %1/%2
+This debug message is issued when the separate thread for maintaining data
+source clients successfully loaded the named zone of the named class as a
+result of the 'loadzone' command.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR Error in data source configuration: %1
+The thread for maintaining data source clients has received a command to
+reconfigure, but the parameter data (the new configuration) contains an
+error. The most likely cause is that the datasource-specific configuration
+data is not what the data source expects. The system is still running with
+the data sources that were previously configured (i.e. as if the
+configuration has not changed), and the configuration data needs to be
+checked.
+The specific problem is printed in the log message.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_DATASRC_ERROR Error setting up data source: %1
+The thread for maintaining data source clients has received a command to
+reconfigure, but a data source failed to set up. This may be a problem with
+the data that is configured (e.g. unreadable files, inconsistent data,
+parser problems, database connection problems, etc.), but it could be a bug
+in the data source implementation as well. The system is still running with
+the data sources that were previously configured (i.e. as if the
+configuration has not changed).
+The specific problem is printed in the log message.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR Internal error setting up data source: %1
+The thread for maintaining data source clients has received a command to
+reconfigure, but raised an exception while setting up data sources. This is
+most likely an internal error in a data source, or a bug in the data source
+or the system itself, but it is probably a good idea to verify the
+configuration first. The system is still running with the data sources that
+were previously configured (i.e. as if the configuration has not changed).
+The specific problem is printed in the log message.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_STARTED data source reconfiguration started
+The thread for maintaining data source clients has received a command to
+reconfigure, and has now started this process.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully
+The thread for maintaining data source clients has finished reconfiguring
+the data source clients, and is now running with the new configuration.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started
+A separate thread for maintaining data source clients has been started.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_STOPPED data source builder thread stopped
+The separate thread for maintaining data source clients has been stopped.
+
+% AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR error on waiting for data source builder thread: %1
+This indicates that the separate thread for maintaining data source
+clients had been terminated due to an uncaught exception, and the
+manager notices that at its own termination.  This is not an expected
+event, because the thread is implemented so it catches all exceptions
+internally.  So, if this message is logged it's most likely some internal
+bug, and it would be nice to file a bug report.
+
+% AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR Unexpected error on waiting for data source builder thread
+Some exception happens while waiting for the termination of the
+separate thread for maintaining data source clients.  This shouldn't
+happen in normal conditions; it should be either fatal system level
+errors such as severe memory shortage or some internal bug.  If that
+happens, and if it's not in the middle of terminating b10-auth, it's
+probably better to stop and restart it.
+
 % AUTH_DATA_SOURCE data source database file: %1
 % AUTH_DATA_SOURCE data source database file: %1
 This is a debug message produced by the authoritative server when it accesses a
 This is a debug message produced by the authoritative server when it accesses a
 datebase data source, listing the file that is being accessed.
 datebase data source, listing the file that is being accessed.

+ 20 - 44
src/bin/auth/auth_srv.cc

@@ -52,6 +52,7 @@
 #include <auth/query.h>
 #include <auth/query.h>
 #include <auth/statistics.h>
 #include <auth/statistics.h>
 #include <auth/auth_log.h>
 #include <auth/auth_log.h>
+#include <auth/datasrc_clients_mgr.h>
 
 
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
@@ -68,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 +267,10 @@ 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_;
-
-    boost::shared_ptr<ConfigurableClientList> getClientList(const RRClass&
-                                                            rrclass)
-    {
-        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>());
-        } else {
-            return (it->second);
-        }
-    }
+    /// The data source client list manager
+    auth::DataSrcClientsMgr datasrc_clients_mgr_;
 
 
     /// Bind the ModuleSpec object in config_session_ with
     /// Bind the ModuleSpec object in config_session_ with
     /// isc:config::ModuleSpec::validateStatistics.
     /// isc:config::ModuleSpec::validateStatistics.
@@ -481,6 +471,11 @@ AuthSrv::getIOService() {
     return (impl_->io_service_);
     return (impl_->io_service_);
 }
 }
 
 
+isc::auth::DataSrcClientsMgr&
+AuthSrv::getDataSrcClientsMgr() {
+    return (impl_->datasrc_clients_mgr_);
+}
+
 void
 void
 AuthSrv::setXfrinSession(AbstractSession* xfrin_session) {
 AuthSrv::setXfrinSession(AbstractSession* xfrin_session) {
     impl_->xfrin_session_ = xfrin_session;
     impl_->xfrin_session_ = xfrin_session;
@@ -656,11 +651,15 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
         local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
         local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
         message.setEDNS(local_edns);
         message.setEDNS(local_edns);
     }
     }
+    // Get access to data source client list through the holder and keep the
+    // holder until the processing and rendering is done to avoid inter-thread
+    // race.
+    auth::DataSrcClientsMgr::Holder datasrc_holder(datasrc_clients_mgr_);
 
 
     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(datasrc_holder.findClientList(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();
@@ -687,6 +686,8 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
               .arg(renderer_.getLength()).arg(message);
               .arg(renderer_.getLength()).arg(message);
     return (true);
     return (true);
+    // The message can contain some data from the locked resource. But outside
+    // this method, we touch only the RCode of it, so it should be safe.
 }
 }
 
 
 bool
 bool
@@ -892,7 +893,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;
 }
 }
 
 
@@ -912,31 +913,6 @@ AuthSrv::destroyDDNSForwarder() {
 }
 }
 
 
 void
 void
-AuthSrv::setClientList(const RRClass& rrclass,
-                       const boost::shared_ptr<ConfigurableClientList>& list) {
-    if (list) {
-        impl_->client_lists_[rrclass] = list;
-    } else {
-        impl_->client_lists_.erase(rrclass);
-    }
-}
-boost::shared_ptr<ConfigurableClientList>
-AuthSrv::getClientList(const RRClass& rrclass) {
-    return (impl_->getClientList(rrclass));
-}
-
-vector<RRClass>
-AuthSrv::getClientListClasses() const {
-    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);
-}
-
-void
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
     dnss_->setTCPRecvTimeout(timeout);
     dnss_->setTCPRecvTimeout(timeout);
 }
 }

+ 23 - 32
src/bin/auth/auth_srv.h

@@ -12,13 +12,15 @@
 // 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 __AUTH_SRV_H
-#define __AUTH_SRV_H 1
-
-#include <string>
+#ifndef AUTH_SRV_H
+#define AUTH_SRV_H 1
 
 
 #include <config/ccsession.h>
 #include <config/ccsession.h>
+
 #include <datasrc/factory.h>
 #include <datasrc/factory.h>
+#include <datasrc/client_list.h>
+#include <datasrc/datasrc_config.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>
@@ -33,13 +35,23 @@
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 #include <server_common/portconfig.h>
 #include <server_common/portconfig.h>
+
 #include <auth/statistics.h>
 #include <auth/statistics.h>
+#include <auth/datasrc_clients_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace util {
 namespace util {
 namespace io {
 namespace io {
 class BaseSocketSessionForwarder;
 class BaseSocketSessionForwarder;
 }
 }
+namespace thread {
+class Mutex;
+}
 }
 }
 namespace datasrc {
 namespace datasrc {
 class ConfigurableClientList;
 class ConfigurableClientList;
@@ -184,6 +196,11 @@ public:
     /// \brief Return pointer to the Checkin callback function
     /// \brief Return pointer to the Checkin callback function
     isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
     isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
 
 
+    /// \brief Return data source clients manager.
+    ///
+    /// \throw None
+    isc::auth::DataSrcClientsMgr& getDataSrcClientsMgr();
+
     /// \brief Set the communication session with a separate process for
     /// \brief Set the communication session with a separate process for
     /// outgoing zone transfers.
     /// outgoing zone transfers.
     ///
     ///
@@ -249,36 +266,10 @@ 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.
-    ///
-    /// Replaces the internally used client list with a new one. Other
-    /// classes are not changed.
-    ///
-    /// \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);
-
-    /// \brief Returns the currently used client list for the class.
-    ///
-    /// \param rrclass The class for which to get the list.
-    /// \return The list, or NULL if no list is set for the class.
-    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;
-
     /// \brief Sets the timeout for incoming TCP connections
     /// \brief Sets the timeout for incoming TCP connections
     ///
     ///
     /// Incoming TCP connections that have not sent their data
     /// Incoming TCP connections that have not sent their data
-    /// withing this time are dropped.
+    /// within this time are dropped.
     ///
     ///
     /// \param timeout The timeout (in milliseconds). If se to
     /// \param timeout The timeout (in milliseconds). If se to
     /// zero, no timeouts are used, and the connection will remain
     /// zero, no timeouts are used, and the connection will remain
@@ -293,7 +284,7 @@ private:
     isc::asiodns::DNSServiceBase* dnss_;
     isc::asiodns::DNSServiceBase* dnss_;
 };
 };
 
 
-#endif // __AUTH_SRV_H
+#endif // AUTH_SRV_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 2 - 0
src/bin/auth/benchmarks/Makefile.am

@@ -17,6 +17,7 @@ query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
 query_bench_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 query_bench_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
+query_bench_SOURCES += ../datasrc_config.h ../datasrc_config.cc
 
 
 nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
 nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
 
 
@@ -33,5 +34,6 @@ query_bench_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 query_bench_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
 query_bench_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
+query_bench_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 query_bench_LDADD += $(SQLITE_LIBS)
 query_bench_LDADD += $(SQLITE_LIBS)
 
 

+ 23 - 19
src/bin/auth/benchmarks/query_bench.cc

@@ -18,6 +18,7 @@
 #include <bench/benchmark_util.h>
 #include <bench/benchmark_util.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.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>
@@ -30,7 +31,8 @@
 
 
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/auth_config.h>
-#include <auth/datasrc_configurator.h>
+#include <auth/datasrc_config.h>
+#include <auth/datasrc_clients_mgr.h>
 #include <auth/query.h>
 #include <auth/query.h>
 
 
 #include <asiodns/asiodns.h>
 #include <asiodns/asiodns.h>
@@ -125,13 +127,15 @@ public:
                           OutputBuffer& buffer) :
                           OutputBuffer& buffer) :
         QueryBenchMark(queries, query_message, buffer)
         QueryBenchMark(queries, query_message, buffer)
     {
     {
-        DataSourceConfigurator::testReconfigure(
-            server_.get(),
-            Element::fromJSON("{\"IN\":"
-                              "  [{\"type\": \"sqlite3\","
-                              "    \"params\": {"
-                              "      \"database_file\": \"" +
-                              string(datasrc_file) + "\"}}]}"));
+        // Note: setDataSrcClientLists() may be deprecated, but until then
+        // we use it because we want to be synchronized with the server.
+        server_->getDataSrcClientsMgr().setDataSrcClientLists(
+            configureDataSource(
+                Element::fromJSON("{\"IN\":"
+                                  "  [{\"type\": \"sqlite3\","
+                                  "    \"params\": {"
+                                  "      \"database_file\": \"" +
+                                  string(datasrc_file) + "\"}}]}")));
     }
     }
 };
 };
 
 
@@ -139,19 +143,19 @@ class MemoryQueryBenchMark  : public QueryBenchMark {
 public:
 public:
     MemoryQueryBenchMark(const char* const zone_file,
     MemoryQueryBenchMark(const char* const zone_file,
                          const char* const zone_origin,
                          const char* const zone_origin,
-                          const BenchQueries& queries,
-                          Message& query_message,
-                          OutputBuffer& buffer) :
+                         const BenchQueries& queries,
+                         Message& query_message,
+                         OutputBuffer& buffer) :
         QueryBenchMark(queries, query_message, buffer)
         QueryBenchMark(queries, query_message, buffer)
     {
     {
-        DataSourceConfigurator::testReconfigure(
-            server_.get(),
-            Element::fromJSON("{\"IN\":"
-                              "  [{\"type\": \"MasterFiles\","
-                              "    \"cache-enable\": true, "
-                              "    \"params\": {\"" +
-                              string(zone_origin) + "\": \"" +
-                              string(zone_file) + "\"}}]}"));
+        server_->getDataSrcClientsMgr().setDataSrcClientLists(
+            configureDataSource(
+                Element::fromJSON("{\"IN\":"
+                                  "  [{\"type\": \"MasterFiles\","
+                                  "    \"cache-enable\": true, "
+                                  "    \"params\": {\"" +
+                                  string(zone_origin) + "\": \"" +
+                                  string(zone_file) + "\"}}]}")));
     }
     }
 };
 };
 
 

+ 6 - 4
src/bin/auth/command.cc

@@ -15,6 +15,7 @@
 #include <auth/command.h>
 #include <auth/command.h>
 #include <auth/auth_log.h>
 #include <auth/auth_log.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
+#include <auth/datasrc_clients_mgr.h>
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 #include <datasrc/client_list.h>
 #include <datasrc/client_list.h>
@@ -187,10 +188,11 @@ public:
         if (!origin_elem) {
         if (!origin_elem) {
             isc_throw(AuthCommandError, "Zone origin is missing");
             isc_throw(AuthCommandError, "Zone origin is missing");
         }
         }
-        Name origin(origin_elem->stringValue());
+        const Name origin(origin_elem->stringValue());
 
 
-        const boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-            list(server.getClientList(zone_class));
+        DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
+        boost::shared_ptr<ConfigurableClientList> list =
+            holder.findClientList(zone_class);
 
 
         if (!list) {
         if (!list) {
             isc_throw(AuthCommandError, "There's no client list for "
             isc_throw(AuthCommandError, "There's no client list for "
@@ -198,7 +200,7 @@ public:
         }
         }
 
 
         switch (list->reload(origin)) {
         switch (list->reload(origin)) {
-            case ConfigurableClientList::ZONE_RELOADED:
+            case ConfigurableClientList::ZONE_SUCCESS:
                 // Everything worked fine.
                 // Everything worked fine.
                 LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
                 LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
                     .arg(zone_class).arg(origin);
                     .arg(zone_class).arg(origin);

+ 3 - 3
src/bin/auth/command.h

@@ -16,8 +16,8 @@
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 
 
-#ifndef __COMMAND_H
-#define __COMMAND_H 1
+#ifndef COMMAND_H
+#define COMMAND_H 1
 
 
 class AuthSrv;
 class AuthSrv;
 
 
@@ -54,7 +54,7 @@ isc::data::ConstElementPtr
 execAuthServerCommand(AuthSrv& server, const std::string& command_id,
 execAuthServerCommand(AuthSrv& server, const std::string& command_id,
                       isc::data::ConstElementPtr args);
                       isc::data::ConstElementPtr args);
 
 
-#endif // __COMMAND_H
+#endif // COMMAND_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 3 - 3
src/bin/auth/common.h

@@ -12,8 +12,8 @@
 // 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 __COMMON_H
-#define __COMMON_H 1
+#ifndef COMMON_H
+#define COMMON_H 1
 
 
 #include <stdexcept>
 #include <stdexcept>
 #include <string>
 #include <string>
@@ -62,7 +62,7 @@ extern const char* const AUTH_NAME;
 /// This is sent to interested modules (currently only b10-ddns)
 /// This is sent to interested modules (currently only b10-ddns)
 extern const char* const AUTH_STARTED_NOTIFICATION;
 extern const char* const AUTH_STARTED_NOTIFICATION;
 
 
-#endif // __COMMON_H
+#endif // COMMON_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 586 - 0
src/bin/auth/datasrc_clients_mgr.h

@@ -0,0 +1,586 @@
+// 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.
+
+#ifndef DATASRC_CLIENTS_MGR_H
+#define DATASRC_CLIENTS_MGR_H 1
+
+#include <util/threads/thread.h>
+#include <util/threads/sync.h>
+
+#include <log/logger_support.h>
+#include <log/log_dbglevels.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/data_source.h>
+#include <datasrc/client_list.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <auth/auth_log.h>
+#include <auth/datasrc_config.h>
+
+#include <boost/array.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <exception>
+#include <cassert>
+#include <list>
+#include <utility>
+
+namespace isc {
+namespace auth {
+
+namespace datasrc_clientmgr_internal {
+// This namespace is essentially private for DataSrcClientsMgr(Base) and
+// DataSrcClientsBuilder(Base).  This is exposed in the public header
+// only because these classes are templated (for testing purposes) and
+// class internal has to be defined here.
+
+/// \brief ID of commands from the DataSrcClientsMgr to DataSrcClientsBuilder.
+enum CommandID {
+    NOOP,         ///< Do nothing.  Only useful for tests; no argument
+    RECONFIGURE,  ///< Reconfigure the datasource client lists,
+                  ///  the argument to the command is the full new
+                  ///  datasources configuration.
+    LOADZONE,     ///< Load a new version of zone into a memory,
+                  ///  the argument to the command is a map containing 'class'
+                  ///  and 'origin' elements, both should have been validated.
+    SHUTDOWN,     ///< Shutdown the builder; no argument
+    NUM_COMMANDS
+};
+
+/// \brief The data type passed from DataSrcClientsMgr to
+/// DataSrcClientsBuilder.
+///
+/// The first element of the pair is the command ID, and the second element
+/// is its argument.  If the command doesn't take an argument it should be
+/// a null pointer.
+typedef std::pair<CommandID, data::ConstElementPtr> Command;
+} // namespace datasrc_clientmgr_internal
+
+/// \brief Frontend to the manager object for data source clients.
+///
+/// This class provides interfaces for configuring and updating a set of
+/// data source clients "in the background".  The user of this class can
+/// assume any operation on this class can be done effectively non-blocking,
+/// not suspending any delay-sensitive operations such as DNS query
+/// processing.  The only exception is the time when this class object
+/// is destroyed (normally as a result of an implicit call to the destructor);
+/// in the current implementation it can take time depending on what is
+/// running "in the background" at the time of the call.
+///
+/// Internally, an object of this class invokes a separate thread to perform
+/// time consuming operations such as loading large zone data into memory,
+/// but such details are completely hidden from the user of this class.
+///
+/// This class is templated only so that we can test the class without
+/// involving actual threads or mutex.  Normal applications will only
+/// need one specific specialization that has a typedef of
+/// \c DataSrcClientsMgr.
+template <typename ThreadType, typename BuilderType, typename MutexType,
+          typename CondVarType>
+class DataSrcClientsMgrBase : boost::noncopyable {
+private:
+    typedef std::map<dns::RRClass,
+                     boost::shared_ptr<datasrc::ConfigurableClientList> >
+    ClientListsMap;
+
+public:
+    /// \brief Thread-safe accessor to the data source client lists.
+    ///
+    /// This class provides a simple wrapper for searching the client lists
+    /// stored in the DataSrcClientsMgr in a thread-safe manner.
+    /// It ensures the result of \c getClientList() can be used without
+    /// causing a race condition with other threads that can possibly use
+    /// the same manager throughout the lifetime of the holder object.
+    ///
+    /// This also means the holder object is expected to have a short lifetime.
+    /// The application shouldn't try to keep it unnecessarily long.
+    /// It's normally expected to create the holder object on the stack
+    /// of a small scope and automatically let it be destroyed at the end
+    /// of the scope.
+    class Holder {
+    public:
+        Holder(DataSrcClientsMgrBase& mgr) :
+            mgr_(mgr), locker_(mgr_.map_mutex_)
+        {}
+
+        /// \brief Find a data source client list of a specified RR class.
+        ///
+        /// It returns a pointer to the list stored in the manager if found,
+        /// otherwise it returns NULL.  The manager keeps the ownership of
+        /// the pointed object.  Also, it's not safe to get access to the
+        /// object beyond the scope of the holder object.
+        ///
+        /// \note Since the ownership isn't transferred the return value
+        /// could be a bare pointer (and it's probably better in several
+        /// points).  Unfortunately, some unit tests currently don't work
+        /// unless this method effectively shares the ownership with the
+        /// tests.  That's the only reason why we return a shared pointer
+        /// for now.  We should eventually fix it and change the return value
+        /// type (see Trac ticket #2395).  Other applications must not
+        /// assume the ownership is actually shared.
+        boost::shared_ptr<datasrc::ConfigurableClientList> findClientList(
+            const dns::RRClass& rrclass)
+        {
+            const ClientListsMap::const_iterator
+                it = mgr_.clients_map_->find(rrclass);
+            if (it == mgr_.clients_map_->end()) {
+                return (boost::shared_ptr<datasrc::ConfigurableClientList>());
+            } else {
+                return (it->second);
+            }
+        }
+    private:
+        DataSrcClientsMgrBase& mgr_;
+        typename MutexType::Locker locker_;
+    };
+
+    /// \brief Constructor.
+    ///
+    /// It internally invokes a separate thread and waits for further
+    /// operations from the user application.
+    ///
+    /// This method is basically exception free except in case of really
+    /// rare system-level errors.  When that happens the only reasonable
+    /// action that the application can take would be to terminate the program
+    /// in practice.
+    ///
+    /// \throw std::bad_alloc internal memory allocation failure.
+    /// \throw isc::Unexpected general unexpected system errors.
+    DataSrcClientsMgrBase() :
+        clients_map_(new ClientListsMap),
+        builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_,
+                 &map_mutex_),
+        builder_thread_(boost::bind(&BuilderType::run, &builder_))
+    {}
+
+    /// \brief The destructor.
+    ///
+    /// It tells the internal thread to stop and waits for it completion.
+    /// In the current implementation, it can block for some unpredictably
+    /// long period depending on what the thread is doing at that time
+    /// (in future we may want to implement a rapid way of killing the thread
+    /// and/or provide a separate interface for waiting so that the application
+    /// can choose the timing).
+    ///
+    /// The waiting operation can result in an exception, but this method
+    /// catches any of them so this method itself is exception free.
+    ~DataSrcClientsMgrBase() {
+        // We share class member variables with the builder, which will be
+        // invalidated after the call to the destructor, so we need to make
+        // sure the builder thread is terminated.  Depending on the timing
+        // this could take a long time; if we don't want that to happen in
+        // this context, we may want to introduce a separate 'shutdown()'
+        // method.
+        // Also, since we don't want to propagate exceptions from a destructor,
+        // we catch any possible ones.  In fact the only really expected one
+        // is Thread::UncaughtException when the builder thread died due to
+        // an exception.  We specifically log it and just ignore others.
+        try {
+            sendCommand(datasrc_clientmgr_internal::SHUTDOWN,
+                        data::ConstElementPtr());
+            builder_thread_.wait();
+        } catch (const util::thread::Thread::UncaughtException& ex) {
+            // technically, logging this could throw, which will be propagated.
+            // But such an exception would be a fatal one anyway, so we
+            // simply let it go through.
+            LOG_ERROR(auth_logger, AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR).
+                arg(ex.what());
+        } catch (...) {
+            LOG_ERROR(auth_logger,
+                      AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR);
+        }
+
+        cleanup();              // see below
+    }
+
+    /// \brief Handle new full configuration for data source clients.
+    ///
+    /// This method simply passes the new configuration to the builder
+    /// and immediately returns.  This method is basically exception free
+    /// as long as the caller passes a non NULL value for \c config_arg;
+    /// it doesn't validate the argument further.
+    ///
+    /// \brief isc::InvalidParameter config_arg is NULL.
+    /// \brief std::bad_alloc
+    ///
+    /// \param config_arg The new data source configuration.  Must not be NULL.
+    void reconfigure(data::ConstElementPtr config_arg) {
+        if (!config_arg) {
+            isc_throw(InvalidParameter, "Invalid null config argument");
+        }
+        sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg);
+        reconfigureHook();      // for test's customization
+    }
+
+    /// \brief Set the underlying data source client lists to new lists.
+    ///
+    /// This is provided only for some existing tests until we support a
+    /// cleaner way to use faked data source clients.  Non test code or
+    /// newer tests must not use this.
+    void setDataSrcClientLists(datasrc::ClientListMapPtr new_lists) {
+        typename MutexType::Locker locker(map_mutex_);
+        clients_map_ = new_lists;
+    }
+
+private:
+    // This is expected to be called at the end of the destructor.  It
+    // actually does nothing, but provides a customization point for
+    // specialized class for tests so that the tests can inspect the last
+    // state of the class.
+    void cleanup() {}
+
+    // same as cleanup(), for reconfigure().
+    void reconfigureHook() {}
+
+    void sendCommand(datasrc_clientmgr_internal::CommandID command,
+                     data::ConstElementPtr arg)
+    {
+        // The lock will be held until the end of this method.  Only
+        // push_back has to be protected, but we can avoid having an extra
+        // block this way.
+        typename MutexType::Locker locker(queue_mutex_);
+        command_queue_.push_back(
+            datasrc_clientmgr_internal::Command(command, arg));
+        cond_.signal();
+    }
+
+    //
+    // The following are shared with the builder.
+    //
+    // The list is used as a one-way queue: back-in, front-out
+    std::list<datasrc_clientmgr_internal::Command> command_queue_;
+    CondVarType cond_;          // condition variable for queue operations
+    MutexType queue_mutex_;     // mutex to protect the queue
+    datasrc::ClientListMapPtr clients_map_;
+                                // map of actual data source client objects
+    MutexType map_mutex_;       // mutex to protect the clients map
+
+    BuilderType builder_;
+    ThreadType builder_thread_; // for safety this should be placed last
+};
+
+namespace datasrc_clientmgr_internal {
+
+/// \brief A class that maintains a set of data source clients.
+///
+/// An object of this class is supposed to run on a dedicated thread, whose
+/// main function is a call to its \c run() method.  It runs in a loop
+/// waiting for commands from the manager and handles each command (including
+/// reloading a new version of zone data into memory or fully reconfiguration
+/// of specific set of data source clients).  When it receives a SHUTDOWN
+/// command, it exits from the loop, which will terminate the thread.
+///
+/// While this class is defined in a publicly visible namespace, it's
+/// essentially private to \c DataSrcClientsMgr.  Except for tests,
+/// applications should not directly access this class.
+///
+/// This class is templated so that we can test it without involving actual
+/// threads or locks.
+template <typename MutexType, typename CondVarType>
+class DataSrcClientsBuilderBase : boost::noncopyable {
+private:
+    typedef std::map<dns::RRClass,
+                     boost::shared_ptr<datasrc::ConfigurableClientList> >
+    ClientListsMap;
+
+public:
+    /// \brief Internal errors in handling commands.
+    ///
+    /// This exception is expected to be caught within the
+    /// \c DataSrcClientsBuilder implementation, but is defined as public
+    /// so tests can be checked it.
+    class InternalCommandError : public isc::Exception {
+    public:
+        InternalCommandError(const char* file, size_t line, const char* what) :
+            isc::Exception(file, line, what) {}
+    };
+
+    /// \brief Constructor.
+    ///
+    /// It simply sets up a local copy of shared data with the manager.
+    ///
+    /// Note: this will take actual set (map) of data source clients and
+    /// a mutex object for it in #2210 or #2212.
+    ///
+    /// \throw None
+    DataSrcClientsBuilderBase(std::list<Command>* command_queue,
+                              CondVarType* cond, MutexType* queue_mutex,
+                              datasrc::ClientListMapPtr* clients_map,
+                              MutexType* map_mutex
+        ) :
+        command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex),
+        clients_map_(clients_map), map_mutex_(map_mutex)
+    {}
+
+    /// \brief The main loop.
+    void run();
+
+    /// \brief Handle one command from the manager.
+    ///
+    /// This is a dedicated subroutine of run() and is essentially private,
+    /// but is defined as a separate public method so we can test each
+    /// command test individually.  In any case, this class itself is
+    /// generally considered private.
+    ///
+    /// \return true if the builder should keep running; false otherwise.
+    bool handleCommand(const Command& command);
+
+private:
+    // NOOP command handler.  We use this so tests can override it; the default
+    // implementation really does nothing.
+    void doNoop() {}
+
+    void doReconfigure(const data::ConstElementPtr& config) {
+        if (config) {
+            LOG_INFO(auth_logger,
+                     AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_STARTED);
+            try {
+                // Define new_clients_map outside of the block that
+                // has the lock scope; this way, after the swap,
+                // the lock is guaranteed to be released before
+                // the old data is destroyed, minimizing the lock
+                // duration.
+                datasrc::ClientListMapPtr new_clients_map =
+                    configureDataSource(config);
+                {
+                    typename MutexType::Locker locker(*map_mutex_);
+                    new_clients_map.swap(*clients_map_);
+                } // lock is released by leaving scope
+                LOG_INFO(auth_logger,
+                         AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS);
+            } catch (const datasrc::ConfigurableClientList::ConfigurationError&
+                     config_error) {
+                LOG_ERROR(auth_logger,
+                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR).
+                    arg(config_error.what());
+            } catch (const datasrc::DataSourceError& ds_error) {
+                LOG_ERROR(auth_logger,
+                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_DATASRC_ERROR).
+                    arg(ds_error.what());
+            } catch (const isc::Exception& isc_error) {
+                LOG_ERROR(auth_logger,
+                    AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR).
+                    arg(isc_error.what());
+            }
+            // other exceptions are propagated, see
+            // http://bind10.isc.org/ticket/2210#comment:13
+
+            // old clients_map_ data is released by leaving scope
+        }
+    }
+
+    void doLoadZone(const isc::data::ConstElementPtr& arg);
+    boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
+        datasrc::ConfigurableClientList& client_list,
+        const dns::RRClass& rrclass, const dns::Name& origin);
+
+    // The following are shared with the manager
+    std::list<Command>* command_queue_;
+    CondVarType* cond_;
+    MutexType* queue_mutex_;
+    datasrc::ClientListMapPtr* clients_map_;
+    MutexType* map_mutex_;
+};
+
+// Shortcut typedef for normal use
+typedef DataSrcClientsBuilderBase<util::thread::Mutex, util::thread::CondVar>
+DataSrcClientsBuilder;
+
+template <typename MutexType, typename CondVarType>
+void
+DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
+    LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STARTED);
+
+    try {
+        bool keep_running = true;
+        while (keep_running) {
+            std::list<Command> current_commands;
+            {
+                // Move all new commands to local queue under the protection of
+                // queue_mutex_.
+                typename MutexType::Locker locker(*queue_mutex_);
+                while (command_queue_->empty()) {
+                    cond_->wait(*queue_mutex_);
+                }
+                current_commands.swap(*command_queue_);
+            } // the lock is released here.
+
+            while (keep_running && !current_commands.empty()) {
+                try {
+                    keep_running = handleCommand(current_commands.front());;
+                } catch (const InternalCommandError& e) {
+                    LOG_ERROR(auth_logger,
+                              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
+                        arg(e.what());
+                }
+                current_commands.pop_front();
+            }
+        }
+
+        LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STOPPED);
+    } catch (const std::exception& ex) {
+        // We explicitly catch exceptions so we can log it as soon as possible.
+        LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED).
+            arg(ex.what());
+        std::terminate();
+    } catch (...) {
+        LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED_UNEXPECTED);
+        std::terminate();
+    }
+}
+
+template <typename MutexType, typename CondVarType>
+bool
+DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
+    const Command& command)
+{
+    const CommandID cid = command.first;
+    if (cid >= NUM_COMMANDS) {
+        // This shouldn't happen except for a bug within this file.
+        isc_throw(Unexpected, "internal bug: invalid command, ID: " << cid);
+    }
+
+    const boost::array<const char*, NUM_COMMANDS> command_desc = {
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
+    };
+    LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
+              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
+    switch (command.first) {
+    case RECONFIGURE:
+        doReconfigure(command.second);
+        break;
+    case LOADZONE:
+        doLoadZone(command.second);
+        break;
+    case SHUTDOWN:
+        return (false);
+    case NOOP:
+        doNoop();
+        break;
+    case NUM_COMMANDS:
+        assert(false);          // we rejected this case above
+    }
+    return (true);
+}
+
+template <typename MutexType, typename CondVarType>
+void
+DataSrcClientsBuilderBase<MutexType, CondVarType>::doLoadZone(
+    const isc::data::ConstElementPtr& arg)
+{
+    // We assume some basic level validation as this method can only be
+    // called via the manager in practice.  manager is expected to do the
+    // minimal validation.
+    assert(arg);
+    assert(arg->get("class"));
+    assert(arg->get("origin"));
+
+    const dns::RRClass rrclass(arg->get("class")->stringValue());
+    const dns::Name origin(arg->get("origin")->stringValue());
+    ClientListsMap::iterator found = (*clients_map_)->find(rrclass);
+    if (found == (*clients_map_)->end()) {
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": not configured for the class");
+    }
+
+    boost::shared_ptr<datasrc::ConfigurableClientList> client_list =
+        found->second;
+    assert(client_list);
+
+    try {
+        boost::shared_ptr<datasrc::memory::ZoneWriter> zwriter =
+            getZoneWriter(*client_list, rrclass, origin);
+
+        zwriter->load(); // this can take time but doesn't cause a race
+        {   // install() can cause a race and must be in a critical section
+            typename MutexType::Locker locker(*map_mutex_);
+            zwriter->install();
+        }
+        LOG_DEBUG(auth_logger, DBG_AUTH_OPS,
+                  AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE)
+            .arg(origin).arg(rrclass);
+
+        // same as load(). We could let the destructor do it, but do it
+        // ourselves explicitly just in case.
+        zwriter->cleanup();
+    } catch (const InternalCommandError& ex) {
+        throw;     // this comes from getZoneWriter.  just let it go through.
+    } catch (const isc::Exception& ex) {
+        // We catch our internal exceptions (which will be just ignored) and
+        // propagated others (which should generally be considered fatal and
+        // will make the thread terminate)
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": error occurred in reload: " <<
+                  ex.what());
+    }
+}
+
+// A dedicated subroutine of doLoadZone().  Separated just for keeping the
+// main method concise.
+template <typename MutexType, typename CondVarType>
+boost::shared_ptr<datasrc::memory::ZoneWriter>
+DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
+    datasrc::ConfigurableClientList& client_list,
+    const dns::RRClass& rrclass, const dns::Name& origin)
+{
+    const datasrc::ConfigurableClientList::ZoneWriterPair writerpair =
+        client_list.getCachedZoneWriter(origin);
+
+    switch (writerpair.first) {
+    case datasrc::ConfigurableClientList::ZONE_SUCCESS:
+        assert(writerpair.second);
+        return (writerpair.second);
+    case datasrc::ConfigurableClientList::ZONE_NOT_FOUND:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not found in any configured "
+                  "data source.");
+    case datasrc::ConfigurableClientList::ZONE_NOT_CACHED:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not served from memory");
+    case datasrc::ConfigurableClientList::CACHE_DISABLED:
+        // This is an internal error. Auth server must have the cache
+        // enabled.
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": internal failure, in-memory cache "
+                  "is somehow disabled");
+    }
+
+    // all cases above should either return or throw, but some compilers
+    // still need a return statement
+    return (boost::shared_ptr<datasrc::memory::ZoneWriter>());
+}
+} // namespace datasrc_clientmgr_internal
+
+/// \brief Shortcut type for normal data source clients manager.
+///
+/// In fact, for non test applications this is the only type of this kind
+/// to be considered.
+typedef DataSrcClientsMgrBase<
+    util::thread::Thread,
+    datasrc_clientmgr_internal::DataSrcClientsBuilder,
+    util::thread::Mutex, util::thread::CondVar> DataSrcClientsMgr;
+} // namespace auth
+} // namespace isc
+
+#endif  // DATASRC_CLIENTS_MGR_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 24 - 0
src/bin/auth/datasrc_config.cc

@@ -0,0 +1,24 @@
+// 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 <cc/data.h>
+#include "datasrc_config.h"
+
+// This is a trivial specialization for the commonly used version.
+// Defined in .cc to avoid accidental creation of multiple copies.
+isc::datasrc::ClientListMapPtr
+configureDataSource(const isc::data::ConstElementPtr& config) {
+    return (configureDataSourceGeneric<
+            isc::datasrc::ConfigurableClientList>(config));
+}

+ 82 - 0
src/bin/auth/datasrc_config.h

@@ -0,0 +1,82 @@
+// 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.
+
+#ifndef AUTH_DATASRC_CONFIG_H
+#define AUTH_DATASRC_CONFIG_H
+
+#include <cc/data.h>
+#include <datasrc/client_list.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <utility>
+#include <map>
+
+/// \brief Configure data source client lists
+///
+/// This will hook into the data_sources module configuration and it will
+/// 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.
+/// You don't need to pay attention to it, use the configureDataSource
+/// specialization instead.
+///
+/// \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
+///     as an update from the config manager.
+/// \return A map from RR classes to configured lists.
+/// \throw ConfigurationError if the config element is not in the expected
+///        format (A map of 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 std::map<std::string, isc::data::ConstElementPtr> Map;
+    typedef std::map<isc::dns::RRClass, ListPtr> ListMap;
+
+    boost::shared_ptr<ListMap> new_lists(new ListMap);
+
+    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
+///     use with authoritative server implementation.
+isc::datasrc::ClientListMapPtr
+configureDataSource(const isc::data::ConstElementPtr& config);
+
+#endif  // AUTH_DATASRC_CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 0 - 223
src/bin/auth/datasrc_configurator.h

@@ -1,223 +0,0 @@
-// 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.
-
-#ifndef DATASRC_CONFIGURATOR_H
-#define DATASRC_CONFIGURATOR_H
-
-#include "auth_srv.h"
-
-#include <datasrc/client_list.h>
-#include <config/ccsession.h>
-#include <cc/data.h>
-
-#include <set>
-
-/// \brief A class to configure the authoritative server's data source lists
-///
-/// 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.
-///
-/// The class is slightly unusual. Due to some technical limitations, the hook
-/// needs to be static method. Therefore it is not possible to create instances
-/// of the class.
-///
-/// Also, the class is a template. This is simply because of easier testing.
-/// You don't need to pay attention to it, use the DataSourceConfigurator
-/// type alias instead.
-template<class Server, class List>
-class DataSourceConfiguratorGeneric {
-private:
-    /// \brief Disallow creation of instances
-    DataSourceConfiguratorGeneric();
-    /// \brief Internal method to hook into the ModuleCCSession
-    ///
-    /// It simply calls reconfigure.
-    static void reconfigureInternal(const std::string&,
-                                    isc::data::ConstElementPtr config,
-                                    const isc::config::ConfigData&)
-    {
-        if (config->contains("classes")) {
-            reconfigure(config->get("classes"));
-        }
-    }
-    static Server* server_;
-    static isc::config::ModuleCCSession* session_;
-    typedef boost::shared_ptr<List> ListPtr;
-public:
-    /// \brief Initializes the class.
-    ///
-    /// This configures which session and server should be used.
-    /// It hooks to the session now and downloads the configuration.
-    /// It is synchronous (it may block for some time).
-    ///
-    /// Note that you need to call cleanup before the server or
-    /// session dies, otherwise it might access them after they
-    /// are destroyed.
-    ///
-    /// \param session The session to hook into and to access the configuration
-    ///     through.
-    /// \param server It is the server to configure.
-    /// \throw isc::InvalidOperation if this is called when already initialized.
-    /// \throw isc::InvalidParameter if any of the parameters is NULL
-    /// \throw isc::config::ModuleCCError if the remote configuration is not
-    ///     available for some reason.
-    static void init(isc::config::ModuleCCSession* session,
-                     Server* server)
-    {
-        if (session == NULL) {
-            isc_throw(isc::InvalidParameter, "The session must not be NULL");
-        }
-        if (server == NULL) {
-            isc_throw(isc::InvalidParameter, "The server must not be NULL");
-        }
-        if (server_ != NULL) {
-            isc_throw(isc::InvalidOperation,
-                      "The configurator is already initialized");
-        }
-        server_ = server;
-        session_ = session;
-        session->addRemoteConfig("data_sources", reconfigureInternal, false);
-    }
-    /// \brief Deinitializes the class.
-    ///
-    /// This detaches from the session and removes the server from internal
-    /// storage. The current configuration in the server is preserved.
-    ///
-    /// This can be called even if it is not initialized currently. You
-    /// can initialize it again after this.
-    static void cleanup() {
-        if (session_ != NULL) {
-            session_->removeRemoteConfig("data_sources");
-        }
-        session_ = NULL;
-        server_ = NULL;
-    }
-    /// \brief Reads new configuration and replaces the old one.
-    ///
-    /// It instructs the server to replace the lists with new ones as needed.
-    /// You don't need to call it directly (but you could, though the benefit
-    /// is unknown and it would be questionable at least). It is called
-    /// automatically on normal updates.
-    ///
-    /// \param config The configuration value to parse. It is in the form
-    ///     as an update from the config manager.
-    /// \throw InvalidOperation if it is called when not initialized.
-    static void reconfigure(const isc::data::ConstElementPtr& config) {
-        if (server_ == NULL) {
-            isc_throw(isc::InvalidOperation,
-                      "Can't reconfigure while not initialized by init()");
-        }
-        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;
-        // 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) {
-                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;
-        }
-    }
-    /// \brief Version of reconfigure for easier testing.
-    ///
-    /// This method can be used to reconfigure a server without first
-    /// initializing the configurator. This does not need a session.
-    /// Otherwise, it acts the same as reconfigure.
-    ///
-    /// This is not meant for production code. Do not use there.
-    ///
-    /// \param server The server to configure.
-    /// \param config The config to use.
-    /// \throw isc::InvalidOperation if the configurator is initialized.
-    /// \throw anything that reconfigure does.
-    static void testReconfigure(Server* server,
-                                const isc::data::ConstElementPtr& config)
-    {
-        if (server_ != NULL) {
-            isc_throw(isc::InvalidOperation, "Currently initialized.");
-        }
-        try {
-            server_ = server;
-            reconfigure(config);
-            server_ = NULL;
-        } catch (...) {
-            server_ = NULL;
-            throw;
-        }
-    }
-};
-
-template<class Server, class List>
-isc::config::ModuleCCSession*
-DataSourceConfiguratorGeneric<Server, List>::session_(NULL);
-
-template<class Server, class List>
-Server* DataSourceConfiguratorGeneric<Server, List>::server_(NULL);
-
-/// \brief Concrete version of DataSourceConfiguratorGeneric for the
-///     use in authoritative server.
-typedef DataSourceConfiguratorGeneric<AuthSrv,
-        isc::datasrc::ConfigurableClientList>
-    DataSourceConfigurator;
-
-#endif

+ 57 - 21
src/bin/auth/main.cc

@@ -14,17 +14,6 @@
 
 
 #include <config.h>
 #include <config.h>
 
 
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <cassert>
-#include <iostream>
-
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
@@ -45,13 +34,28 @@
 #include <auth/command.h>
 #include <auth/command.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_log.h>
 #include <auth/auth_log.h>
-#include <auth/datasrc_configurator.h>
+#include <auth/datasrc_config.h>
+#include <auth/datasrc_clients_mgr.h>
+
 #include <asiodns/asiodns.h>
 #include <asiodns/asiodns.h>
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
 #include <server_common/keyring.h>
 #include <server_common/keyring.h>
 #include <server_common/socket_request.h>
 #include <server_common/socket_request.h>
 
 
+#include <boost/bind.hpp>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <cassert>
+#include <iostream>
+
 using namespace std;
 using namespace std;
 using namespace isc::asiodns;
 using namespace isc::asiodns;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
@@ -70,7 +74,7 @@ namespace {
 /* need global var for config/command handlers.
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
  * class itself? */
-AuthSrv *auth_server;
+AuthSrv* auth_server;
 
 
 ConstElementPtr
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
 my_config_handler(ConstElementPtr new_config) {
@@ -84,6 +88,33 @@ my_command_handler(const string& command, ConstElementPtr args) {
 }
 }
 
 
 void
 void
+datasrcConfigHandler(AuthSrv* server, bool* first_time,
+                     ModuleCCSession* config_session, const std::string&,
+                     isc::data::ConstElementPtr config,
+                     const isc::config::ConfigData&)
+{
+    assert(server != NULL);
+
+    // Note: remote config handler is requested to be exception free.
+    // While the code below is not 100% exception free, such an exception
+    // is really fatal and the server should actually stop.  So we don't
+    // bother to catch them; the exception would be propagated to the
+    // top level of the server and terminate it.
+
+    if (*first_time) {
+        // HACK: The default is not passed to the handler in the first
+        // callback. This one will get the default (or, current value).
+        // Further updates will work the usual way.
+        assert(config_session != NULL);
+        *first_time = false;
+        server->getDataSrcClientsMgr().reconfigure(
+            config_session->getRemoteConfigValue("data_sources", "classes"));
+    } else if (config->contains("classes")) {
+        server->getDataSrcClientsMgr().reconfigure(config->get("classes"));
+    }
+}
+
+void
 usage() {
 usage() {
     cerr << "Usage:  b10-auth [-v]"
     cerr << "Usage:  b10-auth [-v]"
          << endl;
          << endl;
@@ -191,13 +222,15 @@ main(int argc, char* argv[]) {
         isc::server_common::initKeyring(*config_session);
         isc::server_common::initKeyring(*config_session);
         auth_server->setTSIGKeyRing(&isc::server_common::keyring);
         auth_server->setTSIGKeyRing(&isc::server_common::keyring);
 
 
-        // Start the data source configuration
-        DataSourceConfigurator::init(config_session, auth_server);
-        // HACK: The default is not passed to the handler. This one will
-        // get the default (or, current value). Further updates will work
-        // the usual way.
-        DataSourceConfigurator::reconfigure(
-            config_session->getRemoteConfigValue("data_sources", "classes"));
+        // Start the data source configuration.  We pass first_time and
+        // config_session for the hack described in datasrcConfigHandler.
+        bool first_time = true;
+        config_session->addRemoteConfig("data_sources",
+                                        boost::bind(datasrcConfigHandler,
+                                                    auth_server, &first_time,
+                                                    config_session,
+                                                    _1, _2, _3),
+                                        false);
 
 
         // Now start asynchronous read.
         // Now start asynchronous read.
         config_session->start();
         config_session->start();
@@ -221,7 +254,10 @@ main(int argc, char* argv[]) {
         xfrin_session->disconnect();
         xfrin_session->disconnect();
     }
     }
 
 
-    DataSourceConfigurator::cleanup();
+    // If we haven't registered callback for data sources, this will be just
+    // no-op.
+    config_session->removeRemoteConfig("data_sources");
+
     delete xfrin_session;
     delete xfrin_session;
     delete config_session;
     delete config_session;
     delete cc_session;
     delete cc_session;

+ 3 - 3
src/bin/auth/statistics.h

@@ -12,8 +12,8 @@
 // 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 __STATISTICS_H
-#define __STATISTICS_H 1
+#ifndef STATISTICS_H
+#define STATISTICS_H 1
 
 
 #include <cc/session.h>
 #include <cc/session.h>
 #include <cc/data.h>
 #include <cc/data.h>
@@ -223,7 +223,7 @@ public:
 } // namespace auth
 } // namespace auth
 } // namespace isc
 } // namespace isc
 
 
-#endif // __STATISTICS_H
+#endif // STATISTICS_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 6 - 1
src/bin/auth/tests/Makefile.am

@@ -42,6 +42,7 @@ run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
 run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../common.h ../common.cc
 run_unittests_SOURCES += ../common.h ../common.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 run_unittests_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
+run_unittests_SOURCES += ../datasrc_config.h ../datasrc_config.cc
 run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
 run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
@@ -50,7 +51,10 @@ run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
-run_unittests_SOURCES += datasrc_configurator_unittest.cc
+run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
+run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
+run_unittests_SOURCES += datasrc_clients_mgr_unittest.cc
+run_unittests_SOURCES += datasrc_config_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 
 
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
@@ -72,6 +76,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-commo
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
 run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 run_unittests_LDADD += $(GTEST_LDADD)
 run_unittests_LDADD += $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD += $(SQLITE_LIBS)
 
 

+ 66 - 70
src/bin/auth/tests/auth_srv_unittest.cc

@@ -15,7 +15,6 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <util/io/sockaddr_util.h>
 #include <util/io/sockaddr_util.h>
-#include <util/memory_segment_local.h>
 
 
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
@@ -37,7 +36,7 @@
 #include <auth/common.h>
 #include <auth/common.h>
 #include <auth/statistics.h>
 #include <auth/statistics.h>
 #include <auth/statistics_items.h>
 #include <auth/statistics_items.h>
-#include <auth/datasrc_configurator.h>
+#include <auth/datasrc_config.h>
 
 
 #include <util/unittests/mock_socketsession.h>
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
@@ -63,16 +62,19 @@
 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;
 using namespace isc::dns::rdata;
 using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::xfr;
 using namespace isc::xfr;
+using namespace isc::auth;
 using namespace isc::asiodns;
 using namespace isc::asiodns;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::testutils;
 using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using namespace isc::server_common::portconfig;
+using isc::datasrc::memory::ZoneTableSegment;
 using isc::UnitTestUtil;
 using isc::UnitTestUtil;
 using boost::scoped_ptr;
 using boost::scoped_ptr;
 using isc::auth::statistics::Counters;
 using isc::auth::statistics::Counters;
@@ -91,6 +93,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() :
@@ -766,17 +771,25 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
 }
 }
 
 
 void
 void
-updateDatabase(AuthSrv* server, const char* params) {
+installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
+    // For now, we use explicit swap than reconfigure() because the latter
+    // involves a separate thread and cannot guarantee the new config is
+    // available for the subsequent test.
+    server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
+}
+
+void
+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) +
         "}]}"));
         "}]}"));
-    DataSourceConfigurator::testReconfigure(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 }
 }
 
 
 void
 void
-updateInMemory(AuthSrv* server, const char* origin, const char* filename) {
+updateInMemory(AuthSrv& server, const char* origin, const char* filename) {
     const ConstElementPtr config(Element::fromJSON("{"
     const ConstElementPtr config(Element::fromJSON("{"
         "\"IN\": [{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"type\": \"MasterFiles\","
@@ -789,17 +802,17 @@ updateInMemory(AuthSrv* server, const char* origin, const char* filename) {
         "   \"type\": \"static\","
         "   \"type\": \"static\","
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "}]}"));
         "}]}"));
-    DataSourceConfigurator::testReconfigure(server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 }
 }
 
 
 void
 void
-updateBuiltin(AuthSrv* server) {
+updateBuiltin(AuthSrv& server) {
     const ConstElementPtr config(Element::fromJSON("{"
     const ConstElementPtr config(Element::fromJSON("{"
         "\"CH\": [{"
         "\"CH\": [{"
         "   \"type\": \"static\","
         "   \"type\": \"static\","
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "   \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
         "}]}"));
         "}]}"));
-    DataSourceConfigurator::testReconfigure(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
@@ -810,7 +823,7 @@ TEST_F(AuthSrvTest, DISABLED_TSIGSigned) { // Needs builtin
 TEST_F(AuthSrvTest, TSIGSigned) {
 TEST_F(AuthSrvTest, TSIGSigned) {
 #endif
 #endif
     // Prepare key, the client message, etc
     // Prepare key, the client message, etc
-    updateBuiltin(&server);
+    updateBuiltin(server);
     const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
     const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
     TSIGContext context(key);
     TSIGContext context(key);
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
@@ -858,7 +871,7 @@ TEST_F(AuthSrvTest, DISABLED_builtInQueryViaDNSServer) {
 #else
 #else
 TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
 TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
 #endif
 #endif
-    updateBuiltin(&server);
+    updateBuiltin(server);
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("VERSION.BIND."),
                                        default_qid, Name("VERSION.BIND."),
                                        RRClass::CH(), RRType::TXT());
                                        RRClass::CH(), RRType::TXT());
@@ -890,7 +903,7 @@ TEST_F(AuthSrvTest, DISABLED_builtInQuery) {
 #else
 #else
 TEST_F(AuthSrvTest, builtInQuery) {
 TEST_F(AuthSrvTest, builtInQuery) {
 #endif
 #endif
-    updateBuiltin(&server);
+    updateBuiltin(server);
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("VERSION.BIND."),
                                        default_qid, Name("VERSION.BIND."),
                                        RRClass::CH(), RRType::TXT());
                                        RRClass::CH(), RRType::TXT());
@@ -911,7 +924,7 @@ TEST_F(AuthSrvTest, DISABLED_iqueryViaDNSServer) { // Needs builtin
 #else
 #else
 TEST_F(AuthSrvTest, iqueryViaDNSServer) { // Needs builtin
 TEST_F(AuthSrvTest, iqueryViaDNSServer) { // Needs builtin
 #endif
 #endif
-    updateBuiltin(&server);
+    updateBuiltin(server);
     createDataFromFile("iquery_fromWire.wire");
     createDataFromFile("iquery_fromWire.wire");
     (*server.getDNSLookupProvider())(*io_message, parse_message,
     (*server.getDNSLookupProvider())(*io_message, parse_message,
                                      response_message,
                                      response_message,
@@ -933,7 +946,7 @@ TEST_F(AuthSrvTest, DISABLED_updateConfig) {
 #else
 #else
 TEST_F(AuthSrvTest, updateConfig) {
 TEST_F(AuthSrvTest, updateConfig) {
 #endif
 #endif
-    updateDatabase(&server, CONFIG_TESTDB);
+    updateDatabase(server, CONFIG_TESTDB);
 
 
     // query for existent data in the installed data source.  The resulting
     // query for existent data in the installed data source.  The resulting
     // response should have the AA flag on, and have an RR in each answer
     // response should have the AA flag on, and have an RR in each answer
@@ -951,7 +964,7 @@ TEST_F(AuthSrvTest, DISABLED_datasourceFail) {
 #else
 #else
 TEST_F(AuthSrvTest, datasourceFail) {
 TEST_F(AuthSrvTest, datasourceFail) {
 #endif
 #endif
-    updateDatabase(&server, CONFIG_TESTDB);
+    updateDatabase(server, CONFIG_TESTDB);
 
 
     // This query will hit a corrupted entry of the data source (the zoneload
     // This query will hit a corrupted entry of the data source (the zoneload
     // tool and the data source itself naively accept it).  This will result
     // tool and the data source itself naively accept it).  This will result
@@ -971,10 +984,10 @@ TEST_F(AuthSrvTest, DISABLED_updateConfigFail) {
 TEST_F(AuthSrvTest, updateConfigFail) {
 TEST_F(AuthSrvTest, updateConfigFail) {
 #endif
 #endif
     // First, load a valid data source.
     // First, load a valid data source.
-    updateDatabase(&server, CONFIG_TESTDB);
+    updateDatabase(server, CONFIG_TESTDB);
 
 
     // Next, try to update it with a non-existent one.  This should fail.
     // Next, try to update it with a non-existent one.  This should fail.
-    EXPECT_THROW(updateDatabase(&server, BADCONFIG_TESTDB),
+    EXPECT_THROW(updateDatabase(server, BADCONFIG_TESTDB),
                  isc::datasrc::DataSourceError);
                  isc::datasrc::DataSourceError);
 
 
     // The original data source should still exist.
     // The original data source should still exist.
@@ -997,7 +1010,7 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
         "   \"params\": {},"
         "   \"params\": {},"
         "   \"cache-enable\": true"
         "   \"cache-enable\": true"
         "}]}"));
         "}]}"));
-    DataSourceConfigurator::testReconfigure(&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.
@@ -1018,7 +1031,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
     // query handler class, and confirm it returns no error and a non empty
     // query handler class, and confirm it returns no error and a non empty
     // answer section.  Detailed examination on the response content
     // answer section.  Detailed examination on the response content
     // for various types of queries are tested in the query tests.
     // for various types of queries are tested in the query tests.
-    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1037,7 +1050,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
     // Similar to the previous test, but the query has the DO bit on.
     // Similar to the previous test, but the query has the DO bit on.
     // The response should contain RRSIGs, and should have more RRs than
     // The response should contain RRSIGs, and should have more RRs than
     // the previous case.
     // the previous case.
-    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
 
     createDataFromFile("nsec3query_fromWire.wire");
     createDataFromFile("nsec3query_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1057,7 +1070,7 @@ TEST_F(AuthSrvTest,
     )
     )
 {
 {
     // Set up the in-memory
     // Set up the in-memory
-    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
 
     // This shouldn't affect the result of class CH query
     // This shouldn't affect the result of class CH query
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -1407,7 +1420,7 @@ public:
         if (fake_rrset_ && fake_rrset_->getName() == name &&
         if (fake_rrset_ && fake_rrset_->getName() == name &&
             fake_rrset_->getType() == type)
             fake_rrset_->getType() == type)
         {
         {
-            return (ZoneFinderContextPtr(new ZoneFinder::Context(
+            return (ZoneFinderContextPtr(new ZoneFinder::GenericContext(
                                              *this, options,
                                              *this, options,
                                              ResultContext(SUCCESS,
                                              ResultContext(SUCCESS,
                                                            fake_rrset_))));
                                                            fake_rrset_))));
@@ -1509,7 +1522,9 @@ public:
              real_list, ThrowWhen throw_when, bool isc_exception,
              real_list, ThrowWhen throw_when, bool isc_exception,
              ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
              ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
         ConfigurableClientList(RRClass::IN()),
         ConfigurableClientList(RRClass::IN()),
-        real_(real_list)
+        real_(real_list),
+        config_(Element::fromJSON("{}")),
+        ztable_segment_(ZoneTableSegment::create(*config_, RRClass::IN()))
     {
     {
         BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
         BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
              const isc::datasrc::DataSourceClientPtr
              const isc::datasrc::DataSourceClientPtr
@@ -1521,13 +1536,14 @@ public:
              data_sources_.push_back(
              data_sources_.push_back(
                  DataSourceInfo(client.get(),
                  DataSourceInfo(client.get(),
                                 isc::datasrc::DataSourceClientContainerPtr(),
                                 isc::datasrc::DataSourceClientContainerPtr(),
-                                false, RRClass::IN(), mem_sgmt_));
+                                false, RRClass::IN(), ztable_segment_));
         }
         }
     }
     }
 private:
 private:
     const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
     const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
+    const ConstElementPtr config_;
+    boost::shared_ptr<ZoneTableSegment> ztable_segment_;
     vector<isc::datasrc::DataSourceClientPtr> clients_;
     vector<isc::datasrc::DataSourceClientPtr> clients_;
-    MemorySegmentLocal mem_sgmt_;
 };
 };
 
 
 } // end anonymous namespace for throwing proxy classes
 } // end anonymous namespace for throwing proxy classes
@@ -1545,11 +1561,17 @@ TEST_F(AuthSrvTest,
     )
     )
 {
 {
     // Set real inmem client to proxy
     // Set real inmem client to proxy
-    updateInMemory(&server, "example.", CONFIG_INMEMORY_EXAMPLE);
-    boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        list(new FakeList(server.getClientList(RRClass::IN()), THROW_NEVER,
-                          false));
-    server.setClientList(RRClass::IN(), list);
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
+    DataSrcClientsMgr& mgr = server.getDataSrcClientsMgr();
+    {
+        DataSrcClientsMgr::Holder holder(mgr);
+        list.reset(new FakeList(holder.findClientList(RRClass::IN()),
+                                THROW_NEVER, false));
+    }
+    ClientListMapPtr lists(new std::map<RRClass, ListPtr>);
+    lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+    server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
 
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1567,15 +1589,21 @@ TEST_F(AuthSrvTest,
 // If non null rrset is given, it will be passed to the proxy so it can
 // If non null rrset is given, it will be passed to the proxy so it can
 // return some faked response.
 // return some faked response.
 void
 void
-setupThrow(AuthSrv* server, ThrowWhen throw_when, bool isc_exception,
+setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception,
            ConstRRsetPtr rrset = ConstRRsetPtr())
            ConstRRsetPtr rrset = ConstRRsetPtr())
 {
 {
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
     updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
 
 
-    boost::shared_ptr<isc::datasrc::ConfigurableClientList>
-        list(new FakeList(server->getClientList(RRClass::IN()), throw_when,
-                          isc_exception, rrset));
-    server->setClientList(RRClass::IN(), list);
+    boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
+    DataSrcClientsMgr& mgr = server.getDataSrcClientsMgr();
+    {           // we need to limit the scope so swap is outside of it
+        DataSrcClientsMgr::Holder holder(mgr);
+        list.reset(new FakeList(holder.findClientList(RRClass::IN()),
+                                throw_when, isc_exception, rrset));
+    }
+    ClientListMapPtr lists(new std::map<RRClass, ListPtr>);
+    lists->insert(pair<RRClass, ListPtr>(RRClass::IN(), list));
+    mgr.setDataSrcClientLists(lists);
 }
 }
 
 
 TEST_F(AuthSrvTest,
 TEST_F(AuthSrvTest,
@@ -1598,11 +1626,11 @@ TEST_F(AuthSrvTest,
                                              RRClass::IN(), RRType::TXT());
                                              RRClass::IN(), RRType::TXT());
     for (ThrowWhen* when(throws); *when != THROW_NEVER; ++when) {
     for (ThrowWhen* when(throws); *when != THROW_NEVER; ++when) {
         createRequestPacket(request_message, IPPROTO_UDP);
         createRequestPacket(request_message, IPPROTO_UDP);
-        setupThrow(&server, *when, true);
+        setupThrow(server, *when, true);
         processAndCheckSERVFAIL();
         processAndCheckSERVFAIL();
         // To be sure, check same for non-isc-exceptions
         // To be sure, check same for non-isc-exceptions
         createRequestPacket(request_message, IPPROTO_UDP);
         createRequestPacket(request_message, IPPROTO_UDP);
-        setupThrow(&server, *when, false);
+        setupThrow(server, *when, false);
         processAndCheckSERVFAIL();
         processAndCheckSERVFAIL();
     }
     }
 }
 }
@@ -1618,7 +1646,7 @@ TEST_F(AuthSrvTest,
     )
     )
 {
 {
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
-    setupThrow(&server, THROW_AT_GET_CLASS, true);
+    setupThrow(server, THROW_AT_GET_CLASS, true);
 
 
     // getClass is not called so it should just answer
     // getClass is not called so it should just answer
     server.processMessage(*io_message, *parse_message, *response_obuffer,
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1642,7 +1670,7 @@ TEST_F(AuthSrvTest,
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
                                         RRClass::IN(), RRType::TXT(),
                                         RRClass::IN(), RRType::TXT(),
                                         RRTTL(0)));
                                         RRTTL(0)));
-    setupThrow(&server, THROW_NEVER, true, empty_rrset);
+    setupThrow(server, THROW_NEVER, true, empty_rrset);
 
 
     // Repeat the query processing two times.  Due to the faked RRset,
     // Repeat the query processing two times.  Due to the faked RRset,
     // toWire() should throw, and it should result in SERVFAIL.
     // toWire() should throw, and it should result in SERVFAIL.
@@ -1882,36 +1910,4 @@ TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) {
                 Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
                 Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
 }
 
 
-// Check the client list accessors
-TEST_F(AuthSrvTest, clientList) {
-    // 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()));
-    // 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]);
-    // 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()));
-}
-
 }
 }

+ 84 - 55
src/bin/auth/tests/command_unittest.cc

@@ -17,9 +17,8 @@
 #include "datasrc_util.h"
 #include "datasrc_util.h"
 
 
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
-#include <auth/auth_config.h>
 #include <auth/command.h>
 #include <auth/command.h>
-#include <auth/datasrc_configurator.h>
+#include <auth/datasrc_config.h>
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
@@ -56,6 +55,7 @@ using namespace isc::datasrc;
 using namespace isc::config;
 using namespace isc::config;
 using namespace isc::util::unittests;
 using namespace isc::util::unittests;
 using namespace isc::testutils;
 using namespace isc::testutils;
+using namespace isc::auth;
 using namespace isc::auth::unittest;
 using namespace isc::auth::unittest;
 
 
 namespace {
 namespace {
@@ -174,18 +174,26 @@ 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) {
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
-              find(Name("ns.test1.example")).finder_->
-              find(Name("ns.test1.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
-              find(Name("ns.test1.example")).finder_->
-              find(Name("ns.test1.example"), RRType::AAAA())->code);
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
-              find(Name("ns.test2.example")).finder_->
-              find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET, server.getClientList(RRClass::IN())->
-              find(Name("ns.test2.example")).finder_->
-              find(Name("ns.test2.example"), RRType::AAAA())->code);
+    const RRClass rrclass(RRClass::IN());
+
+    DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              holder.findClientList(rrclass)->find(Name("ns.test1.example"))
+              .finder_->find(Name("ns.test1.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              holder.findClientList(rrclass)->find(Name("ns.test1.example")).
+              finder_->find(Name("ns.test1.example"), RRType::AAAA())->code);
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              holder.findClientList(rrclass)->find(Name("ns.test2.example")).
+              finder_->find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              holder.findClientList(rrclass)->find(Name("ns.test2.example")).
+              finder_->find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+void
+installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
+    server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
 }
 }
 
 
 void
 void
@@ -207,26 +215,31 @@ configureZones(AuthSrv& server) {
         "   \"cache-enable\": true"
         "   \"cache-enable\": true"
         "}]}"));
         "}]}"));
 
 
-    DataSourceConfigurator::testReconfigure(&server, config);
+    installDataSrcClientLists(server, configureDataSource(config));
 
 
     zoneChecks(server);
     zoneChecks(server);
 }
 }
 
 
 void
 void
 newZoneChecks(AuthSrv& server) {
 newZoneChecks(AuthSrv& server) {
-    EXPECT_EQ(ZoneFinder::SUCCESS, server.getClientList(RRClass::IN())->
+    const RRClass rrclass(RRClass::IN());
+
+    DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
+    EXPECT_EQ(ZoneFinder::SUCCESS, holder.findClientList(rrclass)->
               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, holder.findClientList(rrclass)->
               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, holder.findClientList(rrclass)->
               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,
+              holder.findClientList(rrclass)->
               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);
 }
 }
@@ -269,32 +282,38 @@ TEST_F(AuthCommandTest,
         "    \"cache-enable\": true,"
         "    \"cache-enable\": true,"
         "    \"cache-zones\": [\"example.org\"]"
         "    \"cache-zones\": [\"example.org\"]"
         "}]}"));
         "}]}"));
-    DataSourceConfigurator::testReconfigure(&server_, config);
-
-    // Check that the A record at www.example.org does not exist
-    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
-              find(Name("example.org")).finder_->
-              find(Name("www.example.org"), RRType::A())->code);
-
-    // Add the record to the underlying sqlite database, by loading
-    // it as a separate datasource, and updating it
-    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
-                                                "\"database_file\": \""
-                                                + test_db + "\"}");
-    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
-    ZoneUpdaterPtr sql_updater =
-        sql_ds.getInstance().getUpdater(Name("example.org"), false);
-    RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
-                             RRType::A(), RRTTL(60)));
-    rrset->addRdata(rdata::createRdata(rrset->getType(),
-                                       rrset->getClass(),
-                                       "192.0.2.1"));
-    sql_updater->addRRset(*rrset);
-    sql_updater->commit();
-
-    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getClientList(RRClass::IN())->
-              find(Name("example.org")).finder_->
-              find(Name("www.example.org"), RRType::A())->code);
+    installDataSrcClientLists(server_, configureDataSource(config));
+
+    {
+        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
+
+        // Check that the A record at www.example.org does not exist
+        EXPECT_EQ(ZoneFinder::NXDOMAIN,
+                  holder.findClientList(RRClass::IN())->
+                  find(Name("example.org")).finder_->
+                  find(Name("www.example.org"), RRType::A())->code);
+
+        // Add the record to the underlying sqlite database, by loading
+        // it as a separate datasource, and updating it
+        ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
+                                                    "\"database_file\": \""
+                                                    + test_db + "\"}");
+        DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
+        ZoneUpdaterPtr sql_updater =
+            sql_ds.getInstance().getUpdater(Name("example.org"), false);
+        RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
+                                 RRType::A(), RRTTL(60)));
+        rrset->addRdata(rdata::createRdata(rrset->getType(),
+                                           rrset->getClass(),
+                                           "192.0.2.1"));
+        sql_updater->addRRset(*rrset);
+        sql_updater->commit();
+
+        EXPECT_EQ(ZoneFinder::NXDOMAIN,
+                  holder.findClientList(RRClass::IN())->
+                  find(Name("example.org")).finder_->
+                  find(Name("www.example.org"), RRType::A())->code);
+    }
 
 
     // Now send the command to reload it
     // Now send the command to reload it
     result_ = execAuthServerCommand(server_, "loadzone",
     result_ = execAuthServerCommand(server_, "loadzone",
@@ -302,20 +321,28 @@ TEST_F(AuthCommandTest,
                                         "{\"origin\": \"example.org\"}"));
                                         "{\"origin\": \"example.org\"}"));
     checkAnswer(0, "Successful load");
     checkAnswer(0, "Successful load");
 
 
-    // And now it should be present too.
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
-              find(Name("example.org")).finder_->
-              find(Name("www.example.org"), RRType::A())->code);
+    {
+        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
+        // And now it should be present too.
+        EXPECT_EQ(ZoneFinder::SUCCESS,
+                  holder.findClientList(RRClass::IN())->
+                  find(Name("example.org")).finder_->
+                  find(Name("www.example.org"), RRType::A())->code);
+    }
 
 
     // Some error cases. First, the zone has no configuration. (note .com here)
     // Some error cases. First, the zone has no configuration. (note .com here)
     result_ = execAuthServerCommand(server_, "loadzone",
     result_ = execAuthServerCommand(server_, "loadzone",
         Element::fromJSON("{\"origin\": \"example.com\"}"));
         Element::fromJSON("{\"origin\": \"example.com\"}"));
     checkAnswer(1, "example.com");
     checkAnswer(1, "example.com");
 
 
-    // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getClientList(RRClass::IN())->
-              find(Name("example.org")).finder_->
-              find(Name("example.org"), RRType::SOA())->code);
+    {
+        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
+        // The previous zone is not hurt in any way
+        EXPECT_EQ(ZoneFinder::SUCCESS,
+                  holder.findClientList(RRClass::IN())->
+                  find(Name("example.org")).finder_->
+                  find(Name("example.org"), RRType::SOA())->code);
+    }
 
 
     const ConstElementPtr config2(Element::fromJSON("{"
     const ConstElementPtr config2(Element::fromJSON("{"
         "\"IN\": [{"
         "\"IN\": [{"
@@ -324,15 +351,17 @@ TEST_F(AuthCommandTest,
         "    \"cache-enable\": true,"
         "    \"cache-enable\": true,"
         "    \"cache-zones\": [\"example.com\"]"
         "    \"cache-zones\": [\"example.com\"]"
         "}]}"));
         "}]}"));
-    EXPECT_THROW(DataSourceConfigurator::testReconfigure(&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");
 
 
+    DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
     // 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,
+              holder.findClientList(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);
 }
 }

+ 523 - 0
src/bin/auth/tests/datasrc_clients_builder_unittest.cc

@@ -0,0 +1,523 @@
+// 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 <util/unittests/check_valgrind.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+
+#include <auth/datasrc_clients_mgr.h>
+#include <auth/datasrc_config.h>
+
+#include <testutils/dnsmessage_test.h>
+
+#include "test_datasrc_clients_mgr.h"
+#include "datasrc_util.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/function.hpp>
+
+#include <cstdlib>
+#include <string>
+#include <sstream>
+
+using isc::data::ConstElementPtr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::auth::datasrc_clientmgr_internal;
+using namespace isc::auth::unittest;
+using namespace isc::testutils;
+
+namespace {
+class DataSrcClientsBuilderTest : public ::testing::Test {
+protected:
+    DataSrcClientsBuilderTest() :
+        clients_map(new std::map<RRClass,
+                    boost::shared_ptr<ConfigurableClientList> >),
+        builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
+        cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
+        shutdown_cmd(SHUTDOWN, ConstElementPtr()),
+        noop_cmd(NOOP, ConstElementPtr())
+    {}
+
+    void configureZones();      // used for loadzone related tests
+
+    ClientListMapPtr clients_map; // configured clients
+    std::list<Command> command_queue; // test command queue
+    std::list<Command> delayed_command_queue; // commands available after wait
+    TestDataSrcClientsBuilder builder;
+    TestCondVar cond;
+    TestMutex queue_mutex;
+    TestMutex map_mutex;
+    const RRClass rrclass;
+    const Command shutdown_cmd;
+    const Command noop_cmd;
+};
+
+TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
+    // A simplest case, just to check the basic behavior.
+    command_queue.push_back(shutdown_cmd);
+    builder.run();
+    EXPECT_TRUE(command_queue.empty());
+    EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty
+    EXPECT_EQ(1, queue_mutex.lock_count);
+    EXPECT_EQ(1, queue_mutex.unlock_count);
+}
+
+TEST_F(DataSrcClientsBuilderTest, runMultiCommands) {
+    // Two NOOP commands followed by SHUTDOWN.  We should see two doNoop()
+    // calls.
+    command_queue.push_back(noop_cmd);
+    command_queue.push_back(noop_cmd);
+    command_queue.push_back(shutdown_cmd);
+    builder.run();
+    EXPECT_TRUE(command_queue.empty());
+    EXPECT_EQ(1, queue_mutex.lock_count);
+    EXPECT_EQ(1, queue_mutex.unlock_count);
+    EXPECT_EQ(2, queue_mutex.noop_count);
+}
+
+TEST_F(DataSrcClientsBuilderTest, exception) {
+    // Let the noop command handler throw exceptions and see if we can see
+    // them.  Right now, we simply abort to prevent the system from running
+    // with half-broken state.  Eventually we should introduce a better
+    // error handling.
+    if (!isc::util::unittests::runningOnValgrind()) {
+        command_queue.push_back(noop_cmd);
+        queue_mutex.throw_from_noop = TestMutex::EXCLASS;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+
+        command_queue.push_back(noop_cmd);
+        queue_mutex.throw_from_noop = TestMutex::INTEGER;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    }
+
+    command_queue.push_back(noop_cmd);
+    command_queue.push_back(shutdown_cmd); // we need to stop the loop
+    queue_mutex.throw_from_noop = TestMutex::INTERNAL;
+    builder.run();
+}
+
+TEST_F(DataSrcClientsBuilderTest, condWait) {
+    // command_queue is originally empty, so it will require waiting on
+    // condvar.  specialized wait() will make the delayed command available.
+    delayed_command_queue.push_back(shutdown_cmd);
+    builder.run();
+
+    // There should be one call to wait()
+    EXPECT_EQ(1, cond.wait_count);
+    // wait() effectively involves one more set of lock/unlock, so we have
+    // two in total
+    EXPECT_EQ(2, queue_mutex.lock_count);
+    EXPECT_EQ(2, queue_mutex.unlock_count);
+}
+
+TEST_F(DataSrcClientsBuilderTest, reconfigure) {
+    // Full testing of different configurations is not here, but we
+    // do check a few cases of correct and erroneous input, to verify
+    // the error handling
+
+    // A command structure we'll modify to send different commands
+    Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
+
+    // Initially, no clients should be there
+    EXPECT_TRUE(clients_map->empty());
+
+    // A config that doesn't do much except be accepted
+    ConstElementPtr good_config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {},"
+        "   \"cache-enable\": true"
+        "}]"
+        "}"
+    );
+
+    // A configuration that is 'correct' in the top-level, but contains
+    // bad data for the type it specifies
+    ConstElementPtr bad_config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": { \"foo\": [ 1, 2, 3, 4  ]},"
+        "   \"cache-enable\": true"
+        "}]"
+        "}"
+    );
+
+    reconfig_cmd.second = good_config;
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_EQ(1, clients_map->size());
+    EXPECT_EQ(1, map_mutex.lock_count);
+
+    // Store the nonempty clients map we now have
+    ClientListMapPtr working_config_clients(clients_map);
+
+    // If a 'bad' command argument got here, the config validation should
+    // have failed already, but still, the handler should return true,
+    // and the clients_map should not be updated.
+    reconfig_cmd.second = Element::create("{ \"foo\": \"bar\" }");
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_EQ(working_config_clients, clients_map);
+    // Building failed, so map mutex should not have been locked again
+    EXPECT_EQ(1, map_mutex.lock_count);
+
+    // The same for a configuration that has bad data for the type it
+    // specifies
+    reconfig_cmd.second = bad_config;
+    builder.handleCommand(reconfig_cmd);
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_EQ(working_config_clients, clients_map);
+    // Building failed, so map mutex should not have been locked again
+    EXPECT_EQ(1, map_mutex.lock_count);
+
+    // The same goes for an empty parameter (it should at least be
+    // an empty map)
+    reconfig_cmd.second = ConstElementPtr();
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_EQ(working_config_clients, clients_map);
+    EXPECT_EQ(1, map_mutex.lock_count);
+
+    // Reconfigure again with the same good clients, the result should
+    // be a different map than the original, but not an empty one.
+    reconfig_cmd.second = good_config;
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_NE(working_config_clients, clients_map);
+    EXPECT_EQ(1, clients_map->size());
+    EXPECT_EQ(2, map_mutex.lock_count);
+
+    // And finally, try an empty config to disable all datasource clients
+    reconfig_cmd.second = Element::createMap();
+    EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
+    EXPECT_EQ(0, clients_map->size());
+    EXPECT_EQ(3, map_mutex.lock_count);
+
+    // Also check if it has been cleanly unlocked every time
+    EXPECT_EQ(3, map_mutex.unlock_count);
+}
+
+TEST_F(DataSrcClientsBuilderTest, shutdown) {
+    EXPECT_FALSE(builder.handleCommand(shutdown_cmd));
+}
+
+TEST_F(DataSrcClientsBuilderTest, badCommand) {
+    // out-of-range command ID
+    EXPECT_THROW(builder.handleCommand(Command(NUM_COMMANDS,
+                                               ConstElementPtr())),
+                 isc::Unexpected);
+}
+
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the given data source client lists with a memory data source
+// containing two zones, and checks the zones are correctly loaded.
+void
+zoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+// Another helper that checks after completing loadzone command.
+void
+newZoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    // now test1.example should have ns/AAAA
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+
+    // test2.example shouldn't change
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+void
+DataSrcClientsBuilderTest::configureZones() {
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
+                             TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const ConstElementPtr config(
+        Element::fromJSON(
+            "{"
+            "\"IN\": [{"
+            "   \"type\": \"MasterFiles\","
+            "   \"params\": {"
+            "       \"test1.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
+            "       \"test2.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
+            "   },"
+            "   \"cache-enable\": true"
+            "}]}"));
+    clients_map = configureDataSource(config);
+    zoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZone) {
+    // pre test condition checks
+    EXPECT_EQ(0, map_mutex.lock_count);
+    EXPECT_EQ(0, map_mutex.unlock_count);
+
+    configureZones();
+
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test1-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test2-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    EXPECT_EQ(1, map_mutex.lock_count); // we should have acquired the lock
+    EXPECT_EQ(1, map_mutex.unlock_count); // and released it.
+
+    newZoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneSQLite3
+#else
+       loadZoneSQLite3
+#endif
+    )
+{
+    // Prepare the database first
+    const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+    std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
+    // This describes the data source in the configuration
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config);
+
+    // Check that the A record at www.example.org does not exist
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Add the record to the underlying sqlite database, by loading
+    // it as a separate datasource, and updating it
+    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
+                                                "\"database_file\": \""
+                                                + test_db + "\"}");
+    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
+    ZoneUpdaterPtr sql_updater =
+        sql_ds.getInstance().getUpdater(Name("example.org"), false);
+    sql_updater->addRRset(
+        *textToRRset("www.example.org. 60 IN A 192.0.2.1"));
+    sql_updater->commit();
+
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Now send the command to reload it
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"example.org\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    // And now it should be present too.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // An error case: the zone has no configuration. (note .com here)
+    const Command nozone_cmd(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.com\"}"));
+    EXPECT_THROW(builder.handleCommand(nozone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("example.org"), RRType::SOA())->code);
+
+    // attempt of reloading a zone but in-memory cache is disabled.
+    const ConstElementPtr config2(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": false,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config2);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+
+    // basically impossible case: in-memory cache is completely disabled.
+    // In this implementation of manager-builder, this should never happen,
+    // but it catches it like other configuration error and keeps going.
+    clients_map->clear();
+    boost::shared_ptr<ConfigurableClientList> nocache_list(
+        new ConfigurableClientList(rrclass));
+    nocache_list->configure(
+        Element::fromJSON(
+            "[{\"type\": \"sqlite3\","
+            "  \"params\": {\"database_file\": \"" + test_db + "\"},"
+            "  \"cache-enable\": true,"
+            "  \"cache-zones\": [\"example.org\"]"
+            "}]"), false);           // false = disable cache
+    (*clients_map)[rrclass] = nocache_list;
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
+    configureZones();
+
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR
+                             "/test1-broken.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    // there's an error in the new zone file.  reload will be rejected.
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+    configureZones();
+
+    // install the zone file as unreadable
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
+                             "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) {
+    // try to execute load command without configuring the zone beforehand.
+    // it should fail.
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", "
+                                 " \"origin\": \"test1.example\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
+    configureZones();
+
+    if (!isc::util::unittests::runningOnValgrind()) {
+        // null arg: this causes assertion failure
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE, ElementPtr()));
+            }, "");
+    }
+
+    // zone class is bogus (note that this shouldn't happen except in tests)
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": \"no_such_class\"}"))),
+                 InvalidRRClass);
+
+    // not a string
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": 1}"))),
+                 isc::data::TypeError);
+
+    // class or origin is missing: result in assertion failure
+    if (!isc::util::unittests::runningOnValgrind()) {
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(
+                    Command(LOADZONE,
+                            Element::fromJSON(
+                                "{\"origin\": \"test1.example\"}")));
+            }, "");
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE,
+                                              Element::fromJSON(
+                                                  "{\"class\": \"IN\"}")));
+            }, "");
+    }
+
+    // zone doesn't exist in the data source
+    EXPECT_THROW(
+        builder.handleCommand(
+            Command(LOADZONE,
+                    Element::fromJSON(
+                        "{\"class\": \"IN\", \"origin\": \"xx\"}"))),
+        TestDataSrcClientsBuilder::InternalCommandError);
+
+    // origin is bogus
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", \"origin\": \"...\"}"))),
+                 EmptyLabel);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": 10, \"class\": 1}"))),
+                 isc::data::TypeError);
+}
+
+} // unnamed namespace

+ 205 - 0
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc

@@ -0,0 +1,205 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/client_list.h>
+
+#include <auth/datasrc_clients_mgr.h>
+#include "test_datasrc_clients_mgr.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/function.hpp>
+
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::auth;
+using namespace isc::auth::datasrc_clientmgr_internal;
+
+namespace {
+void
+shutdownCheck() {
+    // Check for common points on shutdown.  The manager should have acquired
+    // the lock, put a SHUTDOWN command to the queue, and should have signaled
+    // the builder.
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::queue_mutex->lock_count);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::cond->signal_count);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+    const Command& cmd = FakeDataSrcClientsBuilder::command_queue->front();
+    EXPECT_EQ(SHUTDOWN, cmd.first);
+    EXPECT_FALSE(cmd.second);   // no argument
+
+    // Finally, the manager should wait for the thread to terminate.
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::thread_waited);
+}
+
+// Commonly used pattern of checking member variables shared between the
+// manager and builder.
+void
+checkSharedMembers(size_t expected_queue_lock_count,
+                   size_t expected_queue_unlock_count,
+                   size_t expected_map_lock_count,
+                   size_t expected_map_unlock_count,
+                   size_t expected_cond_signal_count,
+                   size_t expected_command_queue_size)
+{
+    EXPECT_EQ(expected_queue_lock_count,
+              FakeDataSrcClientsBuilder::queue_mutex->lock_count);
+    EXPECT_EQ(expected_queue_unlock_count,
+              FakeDataSrcClientsBuilder::queue_mutex->unlock_count);
+    EXPECT_EQ(expected_map_lock_count,
+              FakeDataSrcClientsBuilder::map_mutex->lock_count);
+    EXPECT_EQ(expected_map_unlock_count,
+              FakeDataSrcClientsBuilder::map_mutex->unlock_count);
+    EXPECT_EQ(expected_cond_signal_count,
+              FakeDataSrcClientsBuilder::cond->signal_count);
+    EXPECT_EQ(expected_command_queue_size,
+              FakeDataSrcClientsBuilder::command_queue->size());
+}
+
+TEST(DataSrcClientsMgrTest, start) {
+    // When we create a manager, builder's run() method should be called.
+    FakeDataSrcClientsBuilder::started = false;
+    {
+        TestDataSrcClientsMgr mgr;
+        EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+        EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+        // Check pre-destroy conditions
+        EXPECT_EQ(0, FakeDataSrcClientsBuilder::cond->signal_count);
+        EXPECT_FALSE(FakeDataSrcClientsBuilder::thread_waited);
+    } // mgr and builder have been destroyed by this point.
+
+    // We stopped the manager implicitly (without shutdown()).  The manager
+    // will internally notify it
+    shutdownCheck();
+}
+
+TEST(DataSrcClientsMgrTest, shutdownWithUncaughtException) {
+    // Emulating the case when the builder exists on exception.  shutdown()
+    // will encounter UncaughtException exception and catch it.
+    EXPECT_NO_THROW({
+            TestDataSrcClientsMgr mgr;
+            FakeDataSrcClientsBuilder::thread_throw_on_wait =
+                FakeDataSrcClientsBuilder::THROW_UNCAUGHT_EX;
+        });
+}
+
+TEST(DataSrcClientsMgrTest, shutdownWithException) {
+    EXPECT_NO_THROW({
+            TestDataSrcClientsMgr mgr;
+            FakeDataSrcClientsBuilder::thread_throw_on_wait =
+                FakeDataSrcClientsBuilder::THROW_OTHER;
+        });
+}
+
+TEST(DataSrcClientsMgrTest, reconfigure) {
+    TestDataSrcClientsMgr mgr;
+
+    // Check pre-command condition
+    checkSharedMembers(0, 0, 0, 0, 0, 0);
+
+    // A valid reconfigure argument
+    ConstElementPtr reconfigure_arg = Element::fromJSON(
+        "{""\"IN\": [{\"type\": \"MasterFiles\", \"params\": {},"
+        "             \"cache-enable\": true}]}");
+
+    // On reconfigure(), it just send the RECONFIGURE command to the builder
+    // thread with the given argument intact.
+    mgr.reconfigure(reconfigure_arg);
+
+    // The manager should have acquired the queue lock, send RECONFIGURE
+    // command with the arg, wake up the builder thread by signal.  It doesn't
+    // touch or refer to the map, so it shouldn't acquire the map lock.
+    checkSharedMembers(1, 1, 0, 0, 1, 1);
+    const Command& cmd1 = FakeDataSrcClientsBuilder::command_queue->front();
+    EXPECT_EQ(RECONFIGURE, cmd1.first);
+    EXPECT_EQ(reconfigure_arg, cmd1.second);
+
+    // Non-null, but semantically invalid argument.  The manager doesn't do
+    // this check, so it should result in the same effect.
+    FakeDataSrcClientsBuilder::command_queue->clear();
+    reconfigure_arg = isc::data::Element::create("{ \"foo\": \"bar\" }");
+    mgr.reconfigure(reconfigure_arg);
+    checkSharedMembers(2, 2, 0, 0, 2, 1);
+    const Command& cmd2 = FakeDataSrcClientsBuilder::command_queue->front();
+    EXPECT_EQ(RECONFIGURE, cmd2.first);
+    EXPECT_EQ(reconfigure_arg, cmd2.second);
+
+    // Passing NULL argument is immediately rejected
+    EXPECT_THROW(mgr.reconfigure(ConstElementPtr()), isc::InvalidParameter);
+    checkSharedMembers(2, 2, 0, 0, 2, 1); // no state change
+}
+
+TEST(DataSrcClientsMgrTest, holder) {
+    TestDataSrcClientsMgr mgr;
+
+    {
+        // Initially it's empty, so findClientList() will always return NULL
+        TestDataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_FALSE(holder.findClientList(RRClass::IN()));
+        EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        // map should be protected here
+        EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
+        EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
+    }
+    // map lock has been released
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
+
+    // Put something in, that should become visible.
+    ConstElementPtr reconfigure_arg = Element::fromJSON(
+        "{\"IN\": [{\"type\": \"MasterFiles\", \"params\": {},"
+        "           \"cache-enable\": true}],"
+        " \"CH\": [{\"type\": \"MasterFiles\", \"params\": {},"
+        "           \"cache-enable\": true}]}");
+    mgr.reconfigure(reconfigure_arg);
+    {
+        TestDataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_TRUE(holder.findClientList(RRClass::IN()));
+        EXPECT_TRUE(holder.findClientList(RRClass::CH()));
+    }
+    // We need to clear command queue by hand
+    FakeDataSrcClientsBuilder::command_queue->clear();
+
+    // Replace the lists with new lists containing only one list.
+    // The CH will disappear again.
+    reconfigure_arg = Element::fromJSON(
+        "{\"IN\": [{\"type\": \"MasterFiles\", \"params\": {},"
+        "           \"cache-enable\": true}]}");
+    mgr.reconfigure(reconfigure_arg);
+    {
+        TestDataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_TRUE(holder.findClientList(RRClass::IN()));
+        EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+    }
+
+    // Duplicate lock acquisition is prohibited (only test mgr can detect
+    // this reliably, so this test may not be that useful)
+    TestDataSrcClientsMgr::Holder holder1(mgr);
+    EXPECT_THROW(TestDataSrcClientsMgr::Holder holder2(mgr), isc::Unexpected);
+}
+
+TEST(DataSrcClientsMgrTest, realThread) {
+    // Using the non-test definition with a real thread.  Just checking
+    // no disruption happens.
+    DataSrcClientsMgr mgr;
+}
+
+} // unnamed namespace

+ 102 - 101
src/bin/auth/tests/datasrc_configurator_unittest.cc

@@ -12,15 +12,18 @@
 // 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.
 
 
-#include <auth/datasrc_configurator.h>
+#include <auth/datasrc_config.h>
 
 
 #include <config/tests/fake_session.h>
 #include <config/tests/fake_session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
-#include <memory>
+
+#include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
+#include <memory>
+
 using namespace isc;
 using namespace isc;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::config;
@@ -31,7 +34,7 @@ using namespace boost;
 
 
 namespace {
 namespace {
 
 
-class DatasrcConfiguratorTest;
+class DatasrcConfigTest;
 
 
 class FakeList {
 class FakeList {
 public:
 public:
@@ -56,33 +59,46 @@ private:
 
 
 typedef shared_ptr<FakeList> ListPtr;
 typedef shared_ptr<FakeList> ListPtr;
 
 
-// We use the test fixture as both parameters, this makes it possible
-// to easily fake all needed methods and look that they were called.
-typedef DataSourceConfiguratorGeneric<DatasrcConfiguratorTest,
-        FakeList> Configurator;
+// Forward declaration.  We need precise definition of DatasrcConfigTest
+// to complete this function.
+void
+testConfigureDataSource(DatasrcConfigTest& test,
+                        const isc::data::ConstElementPtr& config);
 
 
-class DatasrcConfiguratorTest : public ::testing::Test {
-public:
-    // These pretend to be the server
-    ListPtr getClientList(const RRClass& rrclass) {
-        log_ += "get " + rrclass.toText() + "\n";
-        return (lists_[rrclass]);
+void
+datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&,
+                     isc::data::ConstElementPtr config,
+                     const isc::config::ConfigData&)
+{
+    if (config->contains("classes")) {
+        testConfigureDataSource(*fake_server, config->get("classes"));
     }
     }
-    void setClientList(const RRClass& rrclass, const ListPtr& list) {
-        log_ += "set " + rrclass.toText() + " " +
-            (list ? list->getConf() : "") + "\n";
-        lists_[rrclass] = list;
-    }
-    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);
+}
+
+class DatasrcConfigTest : public ::testing::Test {
+public:
+    void setDataSrcClientLists(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);
     }
     }
+
 protected:
 protected:
-    DatasrcConfiguratorTest() :
+    DatasrcConfigTest() :
         session(ElementPtr(new ListElement), ElementPtr(new ListElement),
         session(ElementPtr(new ListElement), ElementPtr(new ListElement),
                 ElementPtr(new ListElement)),
                 ElementPtr(new ListElement)),
         specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec")
         specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec")
@@ -95,25 +111,24 @@ protected:
                                        false));
                                        false));
     }
     }
     void TearDown() {
     void TearDown() {
-        // Make sure no matter what we did, it is cleaned up.
-        Configurator::cleanup();
+        // Make sure no matter what we did, it is cleaned up.  Also check
+        // we really have subscribed to the configuration, and after removing
+        // it we actually cancel it.
+        EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+        mccs->removeRemoteConfig("data_sources");
+        EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
     }
     }
-    void init(const ElementPtr& config = ElementPtr()) {
+    void SetUp() {
         session.getMessages()->
         session.getMessages()->
             add(createAnswer(0,
             add(createAnswer(0,
                              moduleSpecFromFile(string(PLUGIN_DATA_PATH) +
                              moduleSpecFromFile(string(PLUGIN_DATA_PATH) +
                                                 "/datasrc.spec").
                                                 "/datasrc.spec").
                              getFullSpec()));
                              getFullSpec()));
-        if (config) {
-            session.getMessages()->add(createAnswer(0, config));
-        } else {
-            session.getMessages()->
-                add(createAnswer(0, ElementPtr(new MapElement)));
-        }
-        Configurator::init(mccs.get(), this);
-    }
-    void SetUp() {
-        init();
+        session.getMessages()->add(createAnswer(0,
+                                                ElementPtr(new MapElement)));
+        mccs->addRemoteConfig("data_sources",
+                              boost::bind(datasrcConfigHandler,
+                                          this, _1, _2, _3), false);
     }
     }
     ElementPtr buildConfig(const string& config) const {
     ElementPtr buildConfig(const string& config) const {
         const ElementPtr internal(Element::fromJSON(config));
         const ElementPtr internal(Element::fromJSON(config));
@@ -122,14 +137,13 @@ protected:
         return (external);
         return (external);
     }
     }
     void initializeINList() {
     void initializeINList() {
-        const ElementPtr
+        const ConstElementPtr
             config(buildConfig("{\"IN\": [{\"type\": \"xxx\"}]}"));
             config(buildConfig("{\"IN\": [{\"type\": \"xxx\"}]}"));
-        session.addMessage(createCommand("config_update", config), "data_sources",
-                           "*");
+        session.addMessage(createCommand("config_update", config),
+                           "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;
@@ -139,35 +153,27 @@ protected:
     string log_;
     string log_;
 };
 };
 
 
-// Check the initialization (and cleanup)
-TEST_F(DatasrcConfiguratorTest, initialization) {
-    // It can't be initialized again
-    EXPECT_THROW(init(), InvalidOperation);
-    EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
-    // Deinitialize to make the tests reasonable
-    Configurator::cleanup();
-    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
-    // We can't reconfigure now (not even manually)
-    EXPECT_THROW(Configurator::reconfigure(ElementPtr(new MapElement())),
-                 InvalidOperation);
-    // If one of them is NULL, it does not work
-    EXPECT_THROW(Configurator::init(NULL, this), InvalidParameter);
-    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
-    EXPECT_THROW(Configurator::init(mccs.get(), NULL), InvalidParameter);
-    EXPECT_FALSE(session.haveSubscription("data_sources", "*"));
-    // But we can initialize it again now
-    EXPECT_NO_THROW(init());
-    EXPECT_TRUE(session.haveSubscription("data_sources", "*"));
+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.setDataSrcClientLists(lists);
 }
 }
 
 
 // Push there a configuration with a single list.
 // Push there a configuration with a single list.
-TEST_F(DatasrcConfiguratorTest, createList) {
+TEST_F(DatasrcConfigTest, createList) {
     initializeINList();
     initializeINList();
 }
 }
 
 
-TEST_F(DatasrcConfiguratorTest, modifyList) {
-    // First, initialize the list
+TEST_F(DatasrcConfigTest, modifyList) {
+    // 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,15 +181,13 @@ TEST_F(DatasrcConfiguratorTest, 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());
 }
 }
 
 
 // Check we can have multiple lists at once
 // Check we can have multiple lists at once
-TEST_F(DatasrcConfiguratorTest, multiple) {
+TEST_F(DatasrcConfigTest, multiple) {
     const ElementPtr
     const ElementPtr
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
                                  "\"CH\": [{\"type\": \"xxx\"}]}"));
                                  "\"CH\": [{\"type\": \"xxx\"}]}"));
@@ -191,7 +195,7 @@ TEST_F(DatasrcConfiguratorTest, 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());
@@ -203,7 +207,7 @@ TEST_F(DatasrcConfiguratorTest, multiple) {
 //
 //
 // It's almost like above, but we initialize first with single-list
 // It's almost like above, but we initialize first with single-list
 // config.
 // config.
-TEST_F(DatasrcConfiguratorTest, updateAdd) {
+TEST_F(DatasrcConfigTest, updateAdd) {
     initializeINList();
     initializeINList();
     const ElementPtr
     const ElementPtr
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
         config(buildConfig("{\"IN\": [{\"type\": \"yyy\"}], "
@@ -212,16 +216,14 @@ TEST_F(DatasrcConfiguratorTest, 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());
 }
 }
 
 
 // We delete a class list in this test.
 // We delete a class list in this test.
-TEST_F(DatasrcConfiguratorTest, updateDelete) {
+TEST_F(DatasrcConfigTest, updateDelete) {
     initializeINList();
     initializeINList();
     const ElementPtr
     const ElementPtr
         config(buildConfig("{}"));
         config(buildConfig("{}"));
@@ -229,18 +231,18 @@ TEST_F(DatasrcConfiguratorTest, 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(DatasrcConfiguratorTest, 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,41 +258,40 @@ TEST_F(DatasrcConfiguratorTest, rollbackAddition) {
     EXPECT_FALSE(lists_[RRClass::CH()]);
     EXPECT_FALSE(lists_[RRClass::CH()]);
 }
 }
 
 
-// Check that we can rollback a deletion if something else fails
-TEST_F(DatasrcConfiguratorTest, 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
         config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
         config1(Element::fromJSON("{\"IN\": [{\"type\": \"yyy\"}], "
                                   "\"CH\": [{\"type\": \"xxx\"}]}"));
                                   "\"CH\": [{\"type\": \"xxx\"}]}"));
-    Configurator::reconfigure(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.
-    EXPECT_THROW(Configurator::reconfigure(config2), TypeError);
+    // This would delete CH. However, the new config is broken, so it won't
+    // actually apply.
+    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(DatasrcConfiguratorTest, 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\"}]}"));
-    Configurator::reconfigure(config1);
-    // Now, the CH happens first. But nevertheless, it should be
-    // restored to the previoeus version.
+    testConfigureDataSource(*this, config1);
+    // 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\"}]}"));
-    EXPECT_THROW(Configurator::reconfigure(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());
 }
 }

+ 3 - 3
src/bin/auth/tests/datasrc_util.h

@@ -12,8 +12,8 @@
 // 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 __AUTH_DATA_SOURCE_UTIL_H
-#define __AUTH_DATA_SOURCE_UTIL_H 1
+#ifndef AUTH_DATA_SOURCE_UTIL_H
+#define AUTH_DATA_SOURCE_UTIL_H 1
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
@@ -51,7 +51,7 @@ createSQLite3DB(dns::RRClass zclass, const dns::Name& zname,
 } // end of auth
 } // end of auth
 } // end of isc
 } // end of isc
 
 
-#endif  // __AUTH_DATA_SOURCE_UTIL_H
+#endif  // AUTH_DATA_SOURCE_UTIL_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 8 - 9
src/bin/auth/tests/query_unittest.cc

@@ -442,10 +442,9 @@ public:
                        ConstRRsetPtr rrset)
                        ConstRRsetPtr rrset)
     {
     {
         nsec_name_ = nsec_name;
         nsec_name_ = nsec_name;
-        nsec_context_.reset(new Context(*this,
-                                        FIND_DEFAULT, // a fake value
-                                        ResultContext(code, rrset,
-                                                      RESULT_NSEC_SIGNED)));
+        nsec_context_.reset(
+            new GenericContext(*this, FIND_DEFAULT, // a fake value
+                               ResultContext(code, rrset, RESULT_NSEC_SIGNED)));
     }
     }
 
 
     // Once called, the findNSEC3 will return the provided result for the next
     // Once called, the findNSEC3 will return the provided result for the next
@@ -487,8 +486,8 @@ protected:
     {
     {
         ConstRRsetPtr rp = stripRRsigs(rrset, options);
         ConstRRsetPtr rp = stripRRsigs(rrset, options);
         return (ZoneFinderContextPtr(
         return (ZoneFinderContextPtr(
-                    new Context(*this, options,
-                                ResultContext(code, rp, flags))));
+                    new GenericContext(*this, options,
+                                       ResultContext(code, rp, flags))));
     }
     }
 
 
 private:
 private:
@@ -604,9 +603,9 @@ MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
                 target.push_back(stripRRsigs(found_rrset->second, options));
                 target.push_back(stripRRsigs(found_rrset->second, options));
             }
             }
             return (ZoneFinderContextPtr(
             return (ZoneFinderContextPtr(
-                        new Context(*this, options,
-                                    ResultContext(SUCCESS, RRsetPtr()),
-                                    target)));
+                        new GenericContext(*this, options,
+                                           ResultContext(SUCCESS, RRsetPtr()),
+                                           target)));
         }
         }
     }
     }
 
 

+ 95 - 0
src/bin/auth/tests/test_datasrc_clients_mgr.cc

@@ -0,0 +1,95 @@
+// 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 <exceptions/exceptions.h>
+#include <auth/datasrc_config.h>
+
+#include "test_datasrc_clients_mgr.h"
+
+#include <cassert>
+
+namespace isc {
+namespace auth {
+namespace datasrc_clientmgr_internal {
+
+// Define static DataSrcClientsBuilder member variables.
+bool FakeDataSrcClientsBuilder::started = false;
+std::list<Command>* FakeDataSrcClientsBuilder::command_queue = NULL;
+std::list<Command> FakeDataSrcClientsBuilder::command_queue_copy;
+TestCondVar* FakeDataSrcClientsBuilder::cond = NULL;
+TestCondVar FakeDataSrcClientsBuilder::cond_copy;
+TestMutex* FakeDataSrcClientsBuilder::queue_mutex = NULL;
+isc::datasrc::ClientListMapPtr*
+    FakeDataSrcClientsBuilder::clients_map = NULL;
+TestMutex* FakeDataSrcClientsBuilder::map_mutex = NULL;
+TestMutex FakeDataSrcClientsBuilder::queue_mutex_copy;
+bool FakeDataSrcClientsBuilder::thread_waited = false;
+FakeDataSrcClientsBuilder::ExceptionFromWait
+FakeDataSrcClientsBuilder::thread_throw_on_wait =
+    FakeDataSrcClientsBuilder::NOTHROW;
+
+template<>
+void
+TestDataSrcClientsBuilder::doNoop() {
+    ++queue_mutex_->noop_count;
+    switch (queue_mutex_->throw_from_noop) {
+    case TestMutex::NONE:
+        break;                  // no throw
+    case TestMutex::EXCLASS:
+        isc_throw(Exception, "test exception");
+    case TestMutex::INTEGER:
+        throw 42;
+    case TestMutex::INTERNAL:
+        isc_throw(InternalCommandError, "internal error, should be ignored");
+    }
+}
+} // namespace datasrc_clientmgr_internal
+
+template<>
+void
+TestDataSrcClientsMgr::cleanup() {
+    using namespace datasrc_clientmgr_internal;
+    // Make copy of some of the manager's member variables and reset the
+    // corresponding pointers.  The currently pointed objects are in the
+    // manager object, which are going to be invalidated.
+
+    FakeDataSrcClientsBuilder::command_queue_copy = command_queue_;
+    FakeDataSrcClientsBuilder::command_queue =
+        &FakeDataSrcClientsBuilder::command_queue_copy;
+    FakeDataSrcClientsBuilder::queue_mutex_copy = queue_mutex_;
+    FakeDataSrcClientsBuilder::queue_mutex =
+        &FakeDataSrcClientsBuilder::queue_mutex_copy;
+    FakeDataSrcClientsBuilder::cond_copy = cond_;
+    FakeDataSrcClientsBuilder::cond =
+        &FakeDataSrcClientsBuilder::cond_copy;
+}
+
+template<>
+void
+TestDataSrcClientsMgr::reconfigureHook() {
+    using namespace datasrc_clientmgr_internal;
+
+    // Simply replace the local map, ignoring bogus config value.
+    assert(command_queue_.front().first == RECONFIGURE);
+    try {
+        clients_map_ = configureDataSource(command_queue_.front().second);
+    } catch (...) {}
+}
+
+} // namespace auth
+} // namespace isc
+
+// Local Variables:
+// mode: c++
+// End:

+ 223 - 0
src/bin/auth/tests/test_datasrc_clients_mgr.h

@@ -0,0 +1,223 @@
+// 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.
+
+#ifndef TEST_DATASRC_CLIENTS_MGR_H
+#define TEST_DATASRC_CLIENTS_MGR_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <auth/datasrc_clients_mgr.h>
+#include <datasrc/datasrc_config.h>
+
+#include <boost/function.hpp>
+
+#include <list>
+
+// In this file we provide specialization of thread, mutex, condition variable,
+// and DataSrcClientsBuilder for convenience of tests.  They don't use
+// actual threads or mutex, and allow tests to inspect some internal states
+// of the corresponding objects.
+//
+// In many cases, tests can use TestDataSrcClientsMgr (defined below) where
+// DataSrcClientsMgr is needed.
+
+// Below we extend the isc::auth::datasrc_clientmgr_internal namespace to
+// specialize the doNoop() method.
+namespace isc {
+namespace auth {
+namespace datasrc_clientmgr_internal {
+class TestMutex {
+public:
+    // for throw_from_noop.
+    // None: no throw from specialized doNoop()
+    // EXCLASS: throw some exception class object
+    // INTEGER: throw an integer
+    // INTERNAL: internal error (shouldn't terminate the thread)
+    enum ExceptionFromNoop { NONE, EXCLASS, INTEGER, INTERNAL };
+
+    TestMutex() : lock_count(0), unlock_count(0), noop_count(0),
+                  throw_from_noop(NONE)
+    {}
+    class Locker {
+    public:
+        Locker(TestMutex& mutex) : mutex_(mutex) {
+            if (mutex.lock_count != mutex.unlock_count) {
+                isc_throw(Unexpected,
+                          "attempt of duplicate lock acquisition");
+            }
+
+            ++mutex.lock_count;
+            if (mutex.lock_count > 100) { // 100 is an arbitrary choice
+                isc_throw(Unexpected,
+                          "too many test mutex count, likely a bug in test");
+            }
+        }
+        ~Locker() {
+            ++mutex_.unlock_count;
+        }
+    private:
+        TestMutex& mutex_;
+    };
+    size_t lock_count; // number of lock acquisitions; tests can check this
+    size_t unlock_count; // number of lock releases; tests can check this
+    size_t noop_count;          // allow doNoop() to modify this
+    ExceptionFromNoop throw_from_noop; // tests can set this to control doNoop
+};
+
+class TestCondVar {
+public:
+    TestCondVar() : wait_count(0), signal_count(0), command_queue_(NULL),
+                    delayed_command_queue_(NULL)
+    {}
+    TestCondVar(std::list<Command>& command_queue,
+                std::list<Command>& delayed_command_queue) :
+        wait_count(0),
+        signal_count(0),
+        command_queue_(&command_queue),
+        delayed_command_queue_(&delayed_command_queue)
+    {
+    }
+    void wait(TestMutex& mutex) {
+        // bookkeeping
+        ++mutex.unlock_count;
+        ++wait_count;
+        ++mutex.lock_count;
+
+        if (wait_count > 100) { // 100 is an arbitrary choice
+            isc_throw(Unexpected,
+                      "too many cond wait count, likely a bug in test");
+        }
+
+        // make the delayed commands available
+        command_queue_->splice(command_queue_->end(), *delayed_command_queue_);
+    }
+    void signal() {
+        ++signal_count;
+    }
+    size_t wait_count; // number of calls to wait(); tests can check this
+    size_t signal_count; // number of calls to signal(); tests can check this
+private:
+    std::list<Command>* command_queue_;
+    std::list<Command>* delayed_command_queue_;
+};
+
+// Convenient shortcut
+typedef DataSrcClientsBuilderBase<TestMutex, TestCondVar>
+TestDataSrcClientsBuilder;
+
+// We specialize this command handler for the convenience of tests.
+// It abuses our specialized Mutex to count the number of calls of this method.
+template<>
+void
+TestDataSrcClientsBuilder::doNoop();
+
+// A specialization of DataSrcClientsBuilder that allows tests to inspect
+// its internal states via static class variables.  Using static is suboptimal,
+// but DataSrcClientsMgr is highly encapsulated, this seems to be the best
+// possible compromise.
+class FakeDataSrcClientsBuilder {
+public:
+    // true iff a builder has started.
+    static bool started;
+
+    // These three correspond to the resource shared with the manager.
+    // xxx_copy will be set in the manager's destructor to record the
+    // final state of the manager.
+    static std::list<Command>* command_queue;
+    static TestCondVar* cond;
+    static TestMutex* queue_mutex;
+    static isc::datasrc::ClientListMapPtr* clients_map;
+    static TestMutex* map_mutex;
+    static std::list<Command> command_queue_copy;
+    static TestCondVar cond_copy;
+    static TestMutex queue_mutex_copy;
+
+    // true iff the manager waited on the thread running the builder.
+    static bool thread_waited;
+
+    // If set to true by a test, TestThread::wait() throws an exception
+    // exception.
+    enum ExceptionFromWait { NOTHROW, THROW_UNCAUGHT_EX, THROW_OTHER };
+    static ExceptionFromWait thread_throw_on_wait;
+
+    FakeDataSrcClientsBuilder(
+        std::list<Command>* command_queue,
+        TestCondVar* cond,
+        TestMutex* queue_mutex,
+        isc::datasrc::ClientListMapPtr* clients_map,
+        TestMutex* map_mutex)
+    {
+        FakeDataSrcClientsBuilder::started = false;
+        FakeDataSrcClientsBuilder::command_queue = command_queue;
+        FakeDataSrcClientsBuilder::cond = cond;
+        FakeDataSrcClientsBuilder::queue_mutex = queue_mutex;
+        FakeDataSrcClientsBuilder::clients_map = clients_map;
+        FakeDataSrcClientsBuilder::map_mutex = map_mutex;
+        FakeDataSrcClientsBuilder::thread_waited = false;
+        FakeDataSrcClientsBuilder::thread_throw_on_wait = NOTHROW;
+    }
+    void run() {
+        FakeDataSrcClientsBuilder::started = true;
+    }
+};
+
+// A fake thread class that doesn't really invoke thread but simply calls
+// the given main function (synchronously).  Tests can tweak the wait()
+// behavior via some static variables so it will throw some exceptions.
+class TestThread {
+public:
+    TestThread(const boost::function<void()>& main) {
+        main();
+    }
+    void wait() {
+        FakeDataSrcClientsBuilder::thread_waited = true;
+        switch (FakeDataSrcClientsBuilder::thread_throw_on_wait) {
+        case FakeDataSrcClientsBuilder::NOTHROW:
+            break;
+        case FakeDataSrcClientsBuilder::THROW_UNCAUGHT_EX:
+            isc_throw(util::thread::Thread::UncaughtException,
+                      "TestThread wait() saw an exception");
+        case FakeDataSrcClientsBuilder::THROW_OTHER:
+            isc_throw(Unexpected,
+                      "General emulated failure in TestThread wait()");
+        }
+    }
+};
+} // namespace datasrc_clientmgr_internal
+
+// Convenient shortcut
+typedef DataSrcClientsMgrBase<
+    datasrc_clientmgr_internal::TestThread,
+    datasrc_clientmgr_internal::FakeDataSrcClientsBuilder,
+    datasrc_clientmgr_internal::TestMutex,
+    datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgr;
+
+// A specialization of manager's "cleanup" called at the end of the
+// destructor.  We use this to record the final values of some of the class
+// member variables.
+template<>
+void
+TestDataSrcClientsMgr::cleanup();
+
+template<>
+void
+TestDataSrcClientsMgr::reconfigureHook();
+} // namespace auth
+} // namespace isc
+
+#endif  // TEST_DATASRC_CLIENTS_MGR_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 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

+ 96 - 35
src/bin/bindctl/bindcmd.py

@@ -22,7 +22,7 @@ import sys
 from cmd import Cmd
 from cmd import Cmd
 from bindctl.exception import *
 from bindctl.exception import *
 from bindctl.moduleinfo import *
 from bindctl.moduleinfo import *
-from bindctl.cmdparse import BindCmdParse
+from bindctl.cmdparse import BindCmdParser
 from bindctl import command_sets
 from bindctl import command_sets
 from xml.dom import minidom
 from xml.dom import minidom
 import isc
 import isc
@@ -48,20 +48,21 @@ except ImportError:
 # if we have readline support, use that, otherwise use normal stdio
 # if we have readline support, use that, otherwise use normal stdio
 try:
 try:
     import readline
     import readline
-    # This is a fix for the problem described in
-    # http://bind10.isc.org/ticket/1345
-    # If '-' is seen as a word-boundary, the final completion-step
-    # (as handled by the cmd module, and hence outside our reach) can
-    # mistakenly add data twice, resulting in wrong completion results
-    # The solution is to remove it.
-    delims = readline.get_completer_delims()
-    delims = delims.replace('-', '')
-    readline.set_completer_delims(delims)
+    # Only consider spaces as word boundaries; identifiers can contain
+    # '/' and '[]', and configuration item names can in theory use any
+    # printable  character. See the discussion in tickets #1345 and
+    # #2254 for more information.
+    readline.set_completer_delims(' ')
 
 
     my_readline = readline.get_line_buffer
     my_readline = readline.get_line_buffer
 except ImportError:
 except ImportError:
     my_readline = sys.stdin.readline
     my_readline = sys.stdin.readline
 
 
+# Used for tab-completion of 'identifiers' (i.e. config values)
+# If a command parameter has this name, the tab completion hints
+# are derived from config data
+CFGITEM_IDENTIFIER_PARAM = 'identifier'
+
 CSV_FILE_NAME = 'default_user.csv'
 CSV_FILE_NAME = 'default_user.csv'
 CONFIG_MODULE_NAME = 'config'
 CONFIG_MODULE_NAME = 'config'
 CONST_BINDCTL_HELP = """
 CONST_BINDCTL_HELP = """
@@ -463,41 +464,101 @@ class BindCmdInterpreter(Cmd):
 
 
         Cmd.onecmd(self, line)
         Cmd.onecmd(self, line)
 
 
-    def remove_prefix(self, list, prefix):
-        """Removes the prefix already entered, and all elements from the
-           list that don't match it"""
-        if prefix.startswith('/'):
-            prefix = prefix[1:]
-
-        new_list = []
-        for val in list:
-            if val.startswith(prefix):
-                new_val = val[len(prefix):]
-                if new_val.startswith("/"):
-                    new_val = new_val[1:]
-                new_list.append(new_val)
-        return new_list
+    def _get_identifier_startswith(self, id_text):
+        """Return the tab-completion hints for identifiers starting with
+           id_text.
+
+           Parameters:
+           id_text (string): the currently entered identifier part, which
+           is to be completed.
+        """
+        # Strip starting "/" from id_text
+        if id_text.startswith('/'):
+            id_text = id_text[1:]
+        # Get all items from the given module (up to the first /)
+        list = self.config_data.get_config_item_list(
+                        id_text.rpartition("/")[0], recurse=True)
+        # filter out all possibilities that don't match currently entered
+        # text part
+        hints = [val for val in list if val.startswith(id_text)]
+        return hints
+
+    def _cmd_has_identifier_param(self, cmd):
+        """
+        Returns True if the given (parsed) command is known and has a
+        parameter which points to a config data identifier
+
+        Parameters:
+        cmd (cmdparse.BindCmdParser): command context, including given params
+
+        """
+        if cmd.module not in self.modules:
+            return False
+        command = self.modules[cmd.module].get_command_with_name(cmd.command)
+        return command.has_param_with_name(CFGITEM_IDENTIFIER_PARAM)
 
 
     def complete(self, text, state):
     def complete(self, text, state):
-        if 0 == state:
+        """
+        Returns tab-completion hints. See the python documentation of the
+        readline and Cmd modules for more information.
+
+        The first time this is called (within one 'completer' action), it
+        has state 0, and a list of possible completions is made. This list
+        is stored; complete() will then be called with increasing values of
+        state, until it returns None. For each call it returns the state'th
+        element of the hints it collected in the first call.
+
+        The hints list contents depend on which part of the full command
+        line; if no module is given yet, it will list all modules. If a
+        module is given, but no command, it will complete with module
+        commands. If both have been given, it will create the hints based on
+        the command parameters.
+
+        If module and command have already been specified, and the command
+        has a parameter 'identifier', the configuration data is used to
+        create the hints list.
+
+        Parameters:
+        text (string): The text entered so far in the 'current' part of
+                       the command (module, command, parameters)
+        state (int): state used in the readline tab-completion logic;
+                     0 on first call, increasing by one until there are
+                     no (more) hints to return.
+
+        Returns the string value of the hints list with index 'state',
+        or None if no (more) hints are available.
+        """
+        if state == 0:
             self._update_all_modules_info()
             self._update_all_modules_info()
             text = text.strip()
             text = text.strip()
             hints = []
             hints = []
             cur_line = my_readline()
             cur_line = my_readline()
             try:
             try:
-                cmd = BindCmdParse(cur_line)
+                cmd = BindCmdParser(cur_line)
                 if not cmd.params and text:
                 if not cmd.params and text:
                     hints = self._get_command_startswith(cmd.module, text)
                     hints = self._get_command_startswith(cmd.module, text)
+                elif self._cmd_has_identifier_param(cmd):
+                    # If the command has an argument that is a configuration
+                    # identifier (currently, this is only a subset of
+                    # the config commands), then don't tab-complete with
+                    # hints derived from command parameters, but from
+                    # possible configuration identifiers.
+                    #
+                    # This solves the issue reported in #2254, where
+                    # there were hints such as 'argument' and 'identifier'.
+                    #
+                    # Since they are replaced, the tab-completion no longer
+                    # adds 'help' as an option (but it still works)
+                    #
+                    # Also, currently, tab-completion does not work
+                    # together with 'config go' (it does not take 'current
+                    # position' into account). But config go currently has
+                    # problems by itself, unrelated to completion.
+                    hints = self._get_identifier_startswith(text)
                 else:
                 else:
                     hints = self._get_param_startswith(cmd.module, cmd.command,
                     hints = self._get_param_startswith(cmd.module, cmd.command,
                                                        text)
                                                        text)
-                    if cmd.module == CONFIG_MODULE_NAME:
-                        # grm text has been stripped of slashes...
-                        my_text = self.location + "/" + cur_line.rpartition(" ")[2]
-                        list = self.config_data.get_config_item_list(my_text.rpartition("/")[0], True)
-                        hints.extend([val for val in list if val.startswith(my_text[1:])])
-                        # remove the common prefix from the hints so we don't get it twice
-                        hints = self.remove_prefix(hints, my_text.rpartition("/")[0])
+
             except CmdModuleNameFormatError:
             except CmdModuleNameFormatError:
                 if not text:
                 if not text:
                     hints = self.get_module_names()
                     hints = self.get_module_names()
@@ -562,7 +623,7 @@ class BindCmdInterpreter(Cmd):
 
 
     def _parse_cmd(self, line):
     def _parse_cmd(self, line):
         try:
         try:
-            cmd = BindCmdParse(line)
+            cmd = BindCmdParser(line)
             self._validate_cmd(cmd)
             self._validate_cmd(cmd)
             self._handle_cmd(cmd)
             self._handle_cmd(cmd)
         except (IOError, http.client.HTTPException) as err:
         except (IOError, http.client.HTTPException) as err:
@@ -794,7 +855,7 @@ class BindCmdInterpreter(Cmd):
                     else:
                     else:
                         print("Warning: ignoring unknown directive: " + line)
                         print("Warning: ignoring unknown directive: " + line)
                 else:
                 else:
-                    cmd = BindCmdParse(line)
+                    cmd = BindCmdParser(line)
                     self._validate_cmd(cmd)
                     self._validate_cmd(cmd)
                     self._handle_cmd(cmd)
                     self._handle_cmd(cmd)
         except (isc.config.ModuleCCSessionError,
         except (isc.config.ModuleCCSessionError,

+ 30 - 19
src/bin/bindctl/bindctl_main.py.in

@@ -42,16 +42,19 @@ def prepare_config_commands(tool):
     cmd = CommandInfo(name = "show", desc = "Show configuration.")
     cmd = CommandInfo(name = "show", desc = "Show configuration.")
     param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
     param = ParamInfo(name = "argument", type = "string", optional=True, desc = "If you specify the argument 'all' (before the identifier), recursively show all child elements for the given identifier.")
     cmd.add_param(param)
     cmd.add_param(param)
-    param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=True, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "show_json", desc = "Show full configuration in JSON format.")
-    param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+    cmd = CommandInfo(name="show_json",
+                      desc="Show full configuration in JSON format.")
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=True, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "add", desc =
+    cmd = CommandInfo(name="add", desc=
         "Add an entry to configuration list or a named set. "
         "Add an entry to configuration list or a named set. "
         "When adding to a list, the command has one optional argument, "
         "When adding to a list, the command has one optional argument, "
         "a value to add to the list. The value must be in correct JSON "
         "a value to add to the list. The value must be in correct JSON "
@@ -60,45 +63,53 @@ def prepare_config_commands(tool):
         "parameter value, similar to when adding to a list. "
         "parameter value, similar to when adding to a list. "
         "In either case, when no value is given, an entry will be "
         "In either case, when no value is given, an entry will be "
         "constructed with default values.")
         "constructed with default values.")
-    param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=True, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
-    param = ParamInfo(name = "value_or_name", type = "string", optional=True, desc = "Specifies a value to add to the list, or the name when adding to a named set. It must be in correct JSON format and complete.")
+    param = ParamInfo(name="value_or_name", type="string", optional=True,
+                      desc="Specifies a value to add to the list, or the name when adding to a named set. It must be in correct JSON format and complete.")
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
-    param = ParamInfo(name = "value_for_set", type = "string", optional=True, desc = "Specifies an optional value to add to the named map. It must be in correct JSON format and complete.")
+    param = ParamInfo(name="value_for_set", type="string", optional=True,
+                      desc="Specifies an optional value to add to the named map. It must be in correct JSON format and complete.")
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list or named set.")
-    param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+    cmd = CommandInfo(name="remove", desc="Remove entry from configuration list or named set.")
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=True, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
     param = ParamInfo(name = "value", type = "string", optional=True, desc = "When identifier is a list, specifies a value to remove from the list. It must be in correct JSON format and complete. When it is a named set, specifies the name to remove.")
     param = ParamInfo(name = "value", type = "string", optional=True, desc = "When identifier is a list, specifies a value to remove from the list. It must be in correct JSON format and complete. When it is a named set, specifies the name to remove.")
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "set", desc = "Set a configuration value.")
-    param = ParamInfo(name = "identifier", type = "string", optional=True, desc = DEFAULT_IDENTIFIER_DESC)
+    cmd = CommandInfo(name="set", desc="Set a configuration value.")
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=True, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
-    param = ParamInfo(name = "value", type = "string", optional=False, desc = "Specifies a value to set. It must be in correct JSON format and complete.")
+    param = ParamInfo(name="value", type="string", optional=False,
+                      desc="Specifies a value to set. It must be in correct JSON format and complete.")
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "unset", desc = "Unset a configuration value (i.e. revert to the default, if any).")
-    param = ParamInfo(name = "identifier", type = "string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+    cmd = CommandInfo(name="unset", desc="Unset a configuration value (i.e. revert to the default, if any).")
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=False, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "diff", desc = "Show all local changes that have not been committed.")
+    cmd = CommandInfo(name="diff", desc="Show all local changes that have not been committed.")
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "revert", desc = "Revert all local changes.")
+    cmd = CommandInfo(name="revert", desc="Revert all local changes.")
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "commit", desc = "Commit all local changes.")
+    cmd = CommandInfo(name="commit", desc="Commit all local changes.")
     module.add_command(cmd)
     module.add_command(cmd)
 
 
-    cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part.")
-    param = ParamInfo(name = "identifier", type="string", optional=False, desc = DEFAULT_IDENTIFIER_DESC)
+    cmd = CommandInfo(name="go", desc="Go to a specific configuration part.")
+    param = ParamInfo(name=CFGITEM_IDENTIFIER_PARAM, type="string",
+                      optional=False, desc=DEFAULT_IDENTIFIER_DESC)
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 

+ 18 - 18
src/bin/bindctl/cmdparse.py

@@ -25,7 +25,7 @@ except ImportError:
 
 
 param_name_str = "^\s*(?P<param_name>[\w]+)\s*=\s*"
 param_name_str = "^\s*(?P<param_name>[\w]+)\s*=\s*"
 
 
-# The value string can be a sequence without space or comma 
+# The value string can be a sequence without space or comma
 # characters, or a string surroundedby quotation marks(such marks
 # characters, or a string surroundedby quotation marks(such marks
 # can be part of string in an escaped form)
 # can be part of string in an escaped form)
 #param_value_str  = "(?P<param_value>[\"\'].+?(?<!\\\)[\"\']|[^\'\"][^, ]+)"
 #param_value_str  = "(?P<param_value>[\"\'].+?(?<!\\\)[\"\']|[^\'\"][^, ]+)"
@@ -34,8 +34,8 @@ param_value_with_quota_str  = "[\"\'](?P<param_value>.+?)(?<!\\\)[\"\']"
 next_params_str = "(?P<blank>\s*)(?P<comma>,?)(?P<next_params>.*)$"
 next_params_str = "(?P<blank>\s*)(?P<comma>,?)(?P<next_params>.*)$"
 
 
 
 
-PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str + 
-                                      param_value_with_quota_str + 
+PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str +
+                                      param_value_with_quota_str +
                                       next_params_str)
                                       next_params_str)
 PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
 PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
 # Used for module and command name
 # Used for module and command name
@@ -83,52 +83,52 @@ def _remove_list_and_map_whitespace(text):
                 if map_count == 0:
                 if map_count == 0:
                     result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))
                     result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))
                     start_pos = pos + 1
                     start_pos = pos + 1
-        
+
 
 
         pos = pos + 1
         pos = pos + 1
     if start_pos <= len(text):
     if start_pos <= len(text):
         result.append(text[start_pos:len(text)])
         result.append(text[start_pos:len(text)])
     return "".join(result)
     return "".join(result)
-    
-    
-class BindCmdParse:
+
+
+class BindCmdParser:
     """ This class will parse the command line user input into three parts:
     """ This class will parse the command line user input into three parts:
     module name, command, parameters
     module name, command, parameters
-    the first two parts are strings and parameter is one hash, 
+    the first two parts are strings and parameter is one hash,
     parameters part is optional
     parameters part is optional
-    
-    Example: zone reload, zone_name=example.com 
+
+    Example: zone reload, zone_name=example.com
     module == zone
     module == zone
     command == reload
     command == reload
     params == [zone_name = 'example.com']
     params == [zone_name = 'example.com']
     """
     """
-    
+
     def __init__(self, cmd):
     def __init__(self, cmd):
         self.params = OrderedDict()
         self.params = OrderedDict()
         self.module = ''
         self.module = ''
         self.command = ''
         self.command = ''
         self._parse_cmd(cmd)
         self._parse_cmd(cmd)
 
 
-    def _parse_cmd(self, text_str):    
+    def _parse_cmd(self, text_str):
         '''Parse command line. '''
         '''Parse command line. '''
         # Get module name
         # Get module name
         groups = NAME_PATTERN.match(text_str)
         groups = NAME_PATTERN.match(text_str)
         if not groups:
         if not groups:
             raise CmdModuleNameFormatError
             raise CmdModuleNameFormatError
-        
+
         self.module = groups.group('name')
         self.module = groups.group('name')
         cmd_str = groups.group('others')
         cmd_str = groups.group('others')
         if cmd_str:
         if cmd_str:
             if not groups.group('blank'):
             if not groups.group('blank'):
                 raise CmdModuleNameFormatError
                 raise CmdModuleNameFormatError
-        else:            
+        else:
             raise CmdMissCommandNameFormatError(self.module)
             raise CmdMissCommandNameFormatError(self.module)
-            
+
         # Get command name
         # Get command name
         groups = NAME_PATTERN.match(cmd_str)
         groups = NAME_PATTERN.match(cmd_str)
         if (not groups):
         if (not groups):
             raise CmdCommandNameFormatError(self.module)
             raise CmdCommandNameFormatError(self.module)
-        
+
         self.command = groups.group('name')
         self.command = groups.group('name')
         param_str = groups.group('others')
         param_str = groups.group('others')
         if param_str:
         if param_str:
@@ -143,7 +143,7 @@ class BindCmdParse:
     def _parse_params(self, param_text):
     def _parse_params(self, param_text):
         """convert a=b,c=d into one hash """
         """convert a=b,c=d into one hash """
         param_text = _remove_list_and_map_whitespace(param_text)
         param_text = _remove_list_and_map_whitespace(param_text)
-        
+
         # Check parameter name "help"
         # Check parameter name "help"
         param = NAME_PATTERN.match(param_text)
         param = NAME_PATTERN.match(param_text)
         if param and param.group('name') == "help":
         if param and param.group('name') == "help":
@@ -153,7 +153,7 @@ class BindCmdParse:
         while True:
         while True:
             if not param_text.strip():
             if not param_text.strip():
                 break
                 break
-                
+
             groups = PARAM_PATTERN.match(param_text) or \
             groups = PARAM_PATTERN.match(param_text) or \
                      PARAM_WITH_QUOTA_PATTERN.match(param_text)
                      PARAM_WITH_QUOTA_PATTERN.match(param_text)
             if not groups:
             if not groups:

+ 140 - 70
src/bin/bindctl/tests/bindctl_test.py

@@ -40,14 +40,14 @@ except ImportError:
 class TestCmdLex(unittest.TestCase):
 class TestCmdLex(unittest.TestCase):
 
 
     def my_assert_raise(self, exception_type, cmd_line):
     def my_assert_raise(self, exception_type, cmd_line):
-        self.assertRaises(exception_type, cmdparse.BindCmdParse, cmd_line)
+        self.assertRaises(exception_type, cmdparse.BindCmdParser, cmd_line)
 
 
 
 
     def testCommandWithoutParameter(self):
     def testCommandWithoutParameter(self):
-        cmd = cmdparse.BindCmdParse("zone add")
-        assert cmd.module == "zone"
-        assert cmd.command == "add"
-        self.assertEqual(len(cmd.params), 0)
+        cmd_parser = cmdparse.BindCmdParser("zone add")
+        assert cmd_parser.module == "zone"
+        assert cmd_parser.command == "add"
+        self.assertEqual(len(cmd_parser.params), 0)
 
 
 
 
     def testCommandWithParameters(self):
     def testCommandWithParameters(self):
@@ -56,45 +56,51 @@ class TestCmdLex(unittest.TestCase):
                  "zone add zone_name = 'cnnic.cn\", file ='cnnic.cn.file' master=1.1.1.1, " }
                  "zone add zone_name = 'cnnic.cn\", file ='cnnic.cn.file' master=1.1.1.1, " }
 
 
         for cmd_line in lines:
         for cmd_line in lines:
-            cmd = cmdparse.BindCmdParse(cmd_line)
-            assert cmd.module == "zone"
-            assert cmd.command == "add"
-            assert cmd.params["zone_name"] == "cnnic.cn"
-            assert cmd.params["file"] == "cnnic.cn.file"
-            assert cmd.params["master"] == '1.1.1.1'
+            cmd_parser = cmdparse.BindCmdParser(cmd_line)
+            assert cmd_parser.module == "zone"
+            assert cmd_parser.command == "add"
+            assert cmd_parser.params["zone_name"] == "cnnic.cn"
+            assert cmd_parser.params["file"] == "cnnic.cn.file"
+            assert cmd_parser.params["master"] == '1.1.1.1'
 
 
     def testCommandWithParamters_2(self):
     def testCommandWithParamters_2(self):
         '''Test whether the parameters in key=value can be parsed properly.'''
         '''Test whether the parameters in key=value can be parsed properly.'''
-        cmd = cmdparse.BindCmdParse('zone cmd name = 1:34::2')
-        self.assertEqual(cmd.params['name'], '1:34::2')
-
-        cmd = cmdparse.BindCmdParse('zone cmd name = 1\"\'34**&2 value=44\"\'\"')
-        self.assertEqual(cmd.params['name'], '1\"\'34**&2')
-        self.assertEqual(cmd.params['value'], '44\"\'\"')
-
-        cmd = cmdparse.BindCmdParse('zone cmd name = 1\"\'34**&2 ,value=  44\"\'\"')
-        self.assertEqual(cmd.params['name'], '1\"\'34**&2')
-        self.assertEqual(cmd.params['value'], '44\"\'\"')
-
-        cmd = cmdparse.BindCmdParse('zone cmd name =  1\'34**&2value=44\"\'\" value = \"==============\'')
-        self.assertEqual(cmd.params['name'], '1\'34**&2value=44\"\'\"')
-        self.assertEqual(cmd.params['value'], '==============')
-
-        cmd = cmdparse.BindCmdParse('zone cmd name =    \"1234, 567890 \" value ==&*/')
-        self.assertEqual(cmd.params['name'], '1234, 567890 ')
-        self.assertEqual(cmd.params['value'], '=&*/')
+        cmd_parser = cmdparse.BindCmdParser('zone cmd name = 1:34::2')
+        self.assertEqual(cmd_parser.params['name'], '1:34::2')
+
+        cmd_parser = cmdparse.BindCmdParser('zone cmd name = 1\"\'34**&2'
+                                            ' value=44\"\'\"')
+        self.assertEqual(cmd_parser.params['name'], '1\"\'34**&2')
+        self.assertEqual(cmd_parser.params['value'], '44\"\'\"')
+
+        cmd_parser = cmdparse.BindCmdParser('zone cmd name = 1\"\'34**&2'
+                                            ',value=  44\"\'\"')
+        self.assertEqual(cmd_parser.params['name'], '1\"\'34**&2')
+        self.assertEqual(cmd_parser.params['value'], '44\"\'\"')
+
+        cmd_parser = cmdparse.BindCmdParser('zone cmd name =  1\'34**&2'
+                                            'value=44\"\'\" value = '
+                                            '\"==============\'')
+        self.assertEqual(cmd_parser.params['name'], '1\'34**&2value=44\"\'\"')
+        self.assertEqual(cmd_parser.params['value'], '==============')
+
+        cmd_parser = cmdparse.BindCmdParser('zone cmd name =    \"1234, '
+                                            '567890 \" value ==&*/')
+        self.assertEqual(cmd_parser.params['name'], '1234, 567890 ')
+        self.assertEqual(cmd_parser.params['value'], '=&*/')
 
 
     def testCommandWithListParam(self):
     def testCommandWithListParam(self):
-        cmd = cmdparse.BindCmdParse("zone set zone_name='cnnic.cn', master='1.1.1.1, 2.2.2.2'")
-        assert cmd.params["master"] == '1.1.1.1, 2.2.2.2'
+        cmd_parser = cmdparse.BindCmdParser("zone set zone_name='cnnic.cn', "
+                                            "master='1.1.1.1, 2.2.2.2'")
+        assert cmd_parser.params["master"] == '1.1.1.1, 2.2.2.2'
 
 
     def testCommandWithHelpParam(self):
     def testCommandWithHelpParam(self):
-        cmd = cmdparse.BindCmdParse("zone add help")
-        assert cmd.params["help"] == "help"
+        cmd_parser = cmdparse.BindCmdParser("zone add help")
+        assert cmd_parser.params["help"] == "help"
 
 
-        cmd = cmdparse.BindCmdParse("zone add help *&)&)*&&$#$^%")
-        assert cmd.params["help"] == "help"
-        self.assertEqual(len(cmd.params), 1)
+        cmd_parser = cmdparse.BindCmdParser("zone add help *&)&)*&&$#$^%")
+        assert cmd_parser.params["help"] == "help"
+        self.assertEqual(len(cmd_parser.params), 1)
 
 
 
 
     def testCmdModuleNameFormatError(self):
     def testCmdModuleNameFormatError(self):
@@ -130,15 +136,20 @@ class TestCmdSyntax(unittest.TestCase):
         int_spec = { 'item_type' : 'integer',
         int_spec = { 'item_type' : 'integer',
                        'item_optional' : False,
                        'item_optional' : False,
                        'item_default' : 10}
                        'item_default' : 10}
-        zone_file_param = ParamInfo(name = "zone_file", param_spec = string_spec)
+        zone_file_param = ParamInfo(name = "zone_file",
+                                    param_spec = string_spec)
         zone_name = ParamInfo(name = 'zone_name', param_spec = string_spec)
         zone_name = ParamInfo(name = 'zone_name', param_spec = string_spec)
         load_cmd = CommandInfo(name = "load")
         load_cmd = CommandInfo(name = "load")
         load_cmd.add_param(zone_file_param)
         load_cmd.add_param(zone_file_param)
         load_cmd.add_param(zone_name)
         load_cmd.add_param(zone_name)
 
 
-        param_master = ParamInfo(name = "master", optional = True, param_spec = string_spec)
-        param_master = ParamInfo(name = "port", optional = True, param_spec = int_spec)
-        param_allow_update = ParamInfo(name = "allow_update", optional = True, param_spec = string_spec)
+        param_master = ParamInfo(name = "master", optional = True,
+                                 param_spec = string_spec)
+        param_master = ParamInfo(name = "port", optional = True,
+                                 param_spec = int_spec)
+        param_allow_update = ParamInfo(name = "allow_update",
+                                       optional = True,
+                                       param_spec = string_spec)
         set_cmd = CommandInfo(name = "set")
         set_cmd = CommandInfo(name = "set")
         set_cmd.add_param(param_master)
         set_cmd.add_param(param_master)
         set_cmd.add_param(param_allow_update)
         set_cmd.add_param(param_allow_update)
@@ -160,13 +171,14 @@ class TestCmdSyntax(unittest.TestCase):
 
 
 
 
     def no_assert_raise(self, cmd_line):
     def no_assert_raise(self, cmd_line):
-        cmd = cmdparse.BindCmdParse(cmd_line)
-        self.bindcmd._validate_cmd(cmd)
+        cmd_parser = cmdparse.BindCmdParser(cmd_line)
+        self.bindcmd._validate_cmd(cmd_parser)
 
 
 
 
     def my_assert_raise(self, exception_type, cmd_line):
     def my_assert_raise(self, exception_type, cmd_line):
-        cmd = cmdparse.BindCmdParse(cmd_line)
-        self.assertRaises(exception_type, self.bindcmd._validate_cmd, cmd)
+        cmd_parser = cmdparse.BindCmdParser(cmd_line)
+        self.assertRaises(exception_type, self.bindcmd._validate_cmd,
+                          cmd_parser)
 
 
 
 
     def testValidateSuccess(self):
     def testValidateSuccess(self):
@@ -177,7 +189,8 @@ class TestCmdSyntax(unittest.TestCase):
         self.no_assert_raise("zone help help='dd' ")
         self.no_assert_raise("zone help help='dd' ")
         self.no_assert_raise("zone set allow_update='1.1.1.1' zone_name='cn'")
         self.no_assert_raise("zone set allow_update='1.1.1.1' zone_name='cn'")
         self.no_assert_raise("zone set zone_name='cn'")
         self.no_assert_raise("zone set zone_name='cn'")
-        self.my_assert_raise(isc.cc.data.DataTypeError, "zone set zone_name ='cn', port='cn'")
+        self.my_assert_raise(isc.cc.data.DataTypeError,
+                             "zone set zone_name ='cn', port='cn'")
         self.no_assert_raise("zone reload_all")
         self.no_assert_raise("zone reload_all")
 
 
     def testCmdUnknownModuleSyntaxError(self):
     def testCmdUnknownModuleSyntaxError(self):
@@ -188,15 +201,22 @@ class TestCmdSyntax(unittest.TestCase):
         self.my_assert_raise(CmdUnknownCmdSyntaxError, "zone dd")
         self.my_assert_raise(CmdUnknownCmdSyntaxError, "zone dd")
 
 
     def testCmdMissParamSyntaxError(self):
     def testCmdMissParamSyntaxError(self):
-        self.my_assert_raise(CmdMissParamSyntaxError, "zone load zone_file='cn'")
-        self.my_assert_raise(CmdMissParamSyntaxError, "zone load zone_name='cn'")
-        self.my_assert_raise(CmdMissParamSyntaxError, "zone set allow_update='1.1.1.1'")
-        self.my_assert_raise(CmdMissParamSyntaxError, "zone set ")
+        self.my_assert_raise(CmdMissParamSyntaxError,
+                             "zone load zone_file='cn'")
+        self.my_assert_raise(CmdMissParamSyntaxError,
+                             "zone load zone_name='cn'")
+        self.my_assert_raise(CmdMissParamSyntaxError,
+                             "zone set allow_update='1.1.1.1'")
+        self.my_assert_raise(CmdMissParamSyntaxError,
+                             "zone set ")
 
 
     def testCmdUnknownParamSyntaxError(self):
     def testCmdUnknownParamSyntaxError(self):
-        self.my_assert_raise(CmdUnknownParamSyntaxError, "zone load zone_d='cn'")
-        self.my_assert_raise(CmdUnknownParamSyntaxError, "zone reload_all zone_name = 'cn'")
-        self.my_assert_raise(CmdUnknownParamSyntaxError, "zone help a b c")
+        self.my_assert_raise(CmdUnknownParamSyntaxError,
+                             "zone load zone_d='cn'")
+        self.my_assert_raise(CmdUnknownParamSyntaxError,
+                             "zone reload_all zone_name = 'cn'")
+        self.my_assert_raise(CmdUnknownParamSyntaxError,
+                             "zone help a b c")
 
 
 class TestModuleInfo(unittest.TestCase):
 class TestModuleInfo(unittest.TestCase):
 
 
@@ -233,7 +253,8 @@ class TestNameSequence(unittest.TestCase):
             self.tool.add_module_info(ModuleInfo(name = random_str))
             self.tool.add_module_info(ModuleInfo(name = random_str))
 
 
     def setUp(self):
     def setUp(self):
-        self.random_names = ['1erdfeDDWsd', '3fe', '2009erd', 'Fe231', 'tere142', 'rei8WD']
+        self.random_names = ['1erdfeDDWsd', '3fe', '2009erd',
+                             'Fe231', 'tere142', 'rei8WD']
         self._create_bindcmd()
         self._create_bindcmd()
 
 
     def testSequence(self):
     def testSequence(self):
@@ -321,7 +342,8 @@ class TestConfigCommands(unittest.TestCase):
         def precmd(line):
         def precmd(line):
             self.tool.precmd(line)
             self.tool.precmd(line)
         self.tool._update_all_modules_info = update_all_modules_info
         self.tool._update_all_modules_info = update_all_modules_info
-        # If line is equals to 'EOF', _update_all_modules_info() shouldn't be called
+        # If line is equals to 'EOF', _update_all_modules_info()
+        # shouldn't be called
         precmd('EOF')
         precmd('EOF')
         self.assertRaises(socket.error, precmd, 'continue')
         self.assertRaises(socket.error, precmd, 'continue')
 
 
@@ -360,34 +382,41 @@ class TestConfigCommands(unittest.TestCase):
         self.assertEqual((1, MultiConfigData.DEFAULT),
         self.assertEqual((1, MultiConfigData.DEFAULT),
                          self.tool.config_data.get_value("/foo/an_int"))
                          self.tool.config_data.get_value("/foo/an_int"))
 
 
-        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"5\"")
-        self.tool.apply_config_cmd(cmd)
+        cmd_parser = cmdparse.BindCmdParser('config set identifier='
+                                            '"foo/an_int" value="5"')
+        self.tool.apply_config_cmd(cmd_parser)
         self.assertEqual((5, MultiConfigData.LOCAL),
         self.assertEqual((5, MultiConfigData.LOCAL),
                          self.tool.config_data.get_value("/foo/an_int"))
                          self.tool.config_data.get_value("/foo/an_int"))
 
 
-        cmd = cmdparse.BindCmdParse("config unset identifier=\"foo/an_int\"")
-        self.tool.apply_config_cmd(cmd)
+        cmd_parser = cmdparse.BindCmdParser('config unset identifier='
+                                            '"foo/an_int"')
+        self.tool.apply_config_cmd(cmd_parser)
 
 
         self.assertEqual((1, MultiConfigData.DEFAULT),
         self.assertEqual((1, MultiConfigData.DEFAULT),
                          self.tool.config_data.get_value("/foo/an_int"))
                          self.tool.config_data.get_value("/foo/an_int"))
 
 
         # this should raise a NotFoundError
         # this should raise a NotFoundError
-        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"[]\"")
-        self.assertRaises(isc.cc.data.DataNotFoundError, self.tool.apply_config_cmd, cmd)
+        cmd_parser = cmdparse.BindCmdParser('config set identifier='
+                                            '"foo/bar" value="[]"')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.tool.apply_config_cmd, cmd_parser)
 
 
-        cmd = cmdparse.BindCmdParse("config unset identifier=\"foo/bar\"")
+        cmd_parser = cmdparse.BindCmdParser('config unset identifier='
+                                            '"foo/bar"')
         self.assertRaises(isc.cc.data.DataNotFoundError,
         self.assertRaises(isc.cc.data.DataNotFoundError,
-                          self.tool.apply_config_cmd, cmd)
+                          self.tool.apply_config_cmd, cmd_parser)
 
 
         # this should raise a TypeError
         # this should raise a TypeError
-        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/an_int\" value=\"[]\"")
-        self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+        cmd_parser = cmdparse.BindCmdParser('config set identifier='
+                                            '"foo/an_int" value="[]"')
+        self.assertRaises(isc.cc.data.DataTypeError,
+                          self.tool.apply_config_cmd, cmd_parser)
 
 
     # this is a very specific one for use with a set of list tests
     # this is a very specific one for use with a set of list tests
     # to try out the flexibility of the parser (only in the next test)
     # to try out the flexibility of the parser (only in the next test)
     def clt(self, full_cmd_string, item_value):
     def clt(self, full_cmd_string, item_value):
-        cmd = cmdparse.BindCmdParse(full_cmd_string)
-        self.tool.apply_config_cmd(cmd)
+        cmd_parser = cmdparse.BindCmdParser(full_cmd_string)
+        self.tool.apply_config_cmd(cmd_parser)
         self.assertEqual(([item_value], MultiConfigData.LOCAL),
         self.assertEqual(([item_value], MultiConfigData.LOCAL),
                          self.tool.config_data.get_value("/foo/a_list"))
                          self.tool.config_data.get_value("/foo/a_list"))
 
 
@@ -410,15 +439,56 @@ class TestConfigCommands(unittest.TestCase):
         self.clt("config set identifier = \"foo/a_list\" value=[ \"k\" ]", "k")
         self.clt("config set identifier = \"foo/a_list\" value=[ \"k\" ]", "k")
 
 
         # this should raise a TypeError
         # this should raise a TypeError
-        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=\"a\"")
-        self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+        cmd_parser = cmdparse.BindCmdParser('config set identifier='
+                                            '"foo/a_list" value="a"')
+        self.assertRaises(isc.cc.data.DataTypeError,
+                          self.tool.apply_config_cmd, cmd_parser)
 
 
-        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=[1]")
-        self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+        cmd_parser = cmdparse.BindCmdParser('config set identifier='
+                                            '"foo/a_list" value=[1]')
+        self.assertRaises(isc.cc.data.DataTypeError,
+                          self.tool.apply_config_cmd, cmd_parser)
 
 
     def tearDown(self):
     def tearDown(self):
         sys.stdout = self.stdout_backup
         sys.stdout = self.stdout_backup
 
 
+    def test_cmd_has_identifier_param(self):
+        module = ModuleInfo(name="test_module")
+
+        cmd = CommandInfo(name="command_with_identifier")
+        param = ParamInfo(name=bindcmd.CFGITEM_IDENTIFIER_PARAM)
+        cmd.add_param(param)
+        module.add_command(cmd)
+
+        cmd = CommandInfo(name="command_without_identifier")
+        param = ParamInfo(name="some_argument")
+        cmd.add_param(param)
+        module.add_command(cmd)
+
+        self.tool.add_module_info(module)
+
+        cmd_parser = cmdparse.BindCmdParser('test_module '
+                                            'command_with_identifier')
+        self.assertTrue(self.tool._cmd_has_identifier_param(cmd_parser))
+
+        cmd_parser = cmdparse.BindCmdParser('test_module '
+                                            'command_without_identifier')
+        self.assertFalse(self.tool._cmd_has_identifier_param(cmd_parser))
+
+        cmd_parser = cmdparse.BindCmdParser('badmodule '
+                                            'command_without_identifier')
+        self.assertFalse(self.tool._cmd_has_identifier_param(cmd_parser))
+
+    def test_get_identifier_startswith(self):
+        hints = self.tool._get_identifier_startswith("/")
+        self.assertEqual(['foo/an_int', 'foo/a_list'], hints)
+
+        hints = self.tool._get_identifier_startswith("/foo/an")
+        self.assertEqual(['foo/an_int'], hints)
+
+        hints = self.tool._get_identifier_startswith("/bar")
+        self.assertEqual([], hints)
+
 class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
 class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
     def __init__(self):
     def __init__(self):
         pass
         pass

+ 1 - 1
src/bin/cfgmgr/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . plugins tests
+SUBDIRS = . plugins local_plugins tests
 
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 

+ 11 - 0
src/bin/cfgmgr/local_plugins/Makefile.am

@@ -0,0 +1,11 @@
+# Nothing is installed from this directory.  This local_plugins
+# directory overrides the plugins directory when lettuce is run, and the
+# spec file here is used to serve the static zone from the source tree
+# for testing (instead of installation prefix).
+
+noinst_DATA = datasrc.spec
+
+datasrc.spec: ../plugins/datasrc.spec.pre
+	$(SED) -e "s|@@STATIC_ZONE_FILE@@|$(abs_top_builddir)/src/lib/datasrc/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(abs_top_builddir)/local.zone.sqlite3|" ../plugins/datasrc.spec.pre >$@
+
+CLEANFILES = datasrc.spec

+ 1 - 1
src/bin/cfgmgr/plugins/Makefile.am

@@ -3,7 +3,7 @@ SUBDIRS = tests
 EXTRA_DIST = README logging.spec tsig_keys.spec
 EXTRA_DIST = README logging.spec tsig_keys.spec
 
 
 datasrc.spec: datasrc.spec.pre
 datasrc.spec: datasrc.spec.pre
-	$(SED) -e "s|@@PKGDATADIR@@|$(pkgdatadir)|;s|@@LOCALSTATEDIR@@|$(localstatedir)|" datasrc.spec.pre >$@
+	$(SED) -e "s|@@STATIC_ZONE_FILE@@|$(pkgdatadir)/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(localstatedir)/$(PACKAGE)/zone.sqlite3|" datasrc.spec.pre >$@
 
 
 config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
 config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
 config_plugin_DATA = logging.spec tsig_keys.spec datasrc.spec
 config_plugin_DATA = logging.spec tsig_keys.spec datasrc.spec

+ 2 - 2
src/bin/cfgmgr/plugins/datasrc.spec.pre.in

@@ -12,7 +12,7 @@
                         {
                         {
                             "type": "sqlite3",
                             "type": "sqlite3",
                             "params": {
                             "params": {
-                                "database_file": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
+                                "database_file": "@@SQLITE3_DATABASE_FILE@@"
                             }
                             }
                         }
                         }
                     ],
                     ],
@@ -20,7 +20,7 @@
                         {
                         {
                             "type": "static",
                             "type": "static",
                             "cache-enable": false,
                             "cache-enable": false,
-                            "params": "@@PKGDATADIR@@/static.zone"
+                            "params": "@@STATIC_ZONE_FILE@@"
                         }
                         }
                     ]
                     ]
                 },
                 },

+ 8 - 1
src/bin/dbutil/dbutil.py.in

@@ -193,10 +193,17 @@ UPGRADES = [
             "ALTER TABLE schema_version " +
             "ALTER TABLE schema_version " +
                 "ADD COLUMN minor INTEGER NOT NULL DEFAULT 0"
                 "ADD COLUMN minor INTEGER NOT NULL DEFAULT 0"
         ]
         ]
+     },
+
+    {'from': (2, 0), 'to': (2, 1),
+     'statements': [
+            "CREATE INDEX nsec3_byhash_and_rdtype ON nsec3 " +
+                "(hash, rdtype)"
+        ]
     }
     }
 
 
 # To extend this, leave the above statements in place and add another
 # To extend this, leave the above statements in place and add another
-# dictionary to the list.  The "from" version should be (2, 0), the "to"
+# dictionary to the list.  The "from" version should be (2, 1), the "to"
 # version whatever the version the update is to, and the SQL statements are
 # version whatever the version the update is to, and the SQL statements are
 # the statements required to perform the upgrade.  This way, the upgrade
 # the statements required to perform the upgrade.  This way, the upgrade
 # program will be able to upgrade both a V1.0 and a V2.0 database.
 # program will be able to upgrade both a V1.0 and a V2.0 database.

+ 2 - 0
src/bin/dbutil/tests/Makefile.am

@@ -2,6 +2,8 @@ SUBDIRS = . testdata
 
 
 # Tests of the update script.
 # Tests of the update script.
 
 
+noinst_SCRIPTS = dbutil_test.sh
+
 check-local:
 check-local:
 	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(SHELL) $(abs_builddir)/dbutil_test.sh
 	$(SHELL) $(abs_builddir)/dbutil_test.sh

+ 24 - 24
src/bin/dbutil/tests/dbutil_test.sh.in

@@ -161,11 +161,11 @@ get_schema() {
 # @param $2 Expected backup file
 # @param $2 Expected backup file
 upgrade_ok_test() {
 upgrade_ok_test() {
     copy_file $1 $tempfile
     copy_file $1 $tempfile
-    ../run_dbutil.sh --upgrade --noconfirm $tempfile
+    ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
     if [ $? -eq 0 ]
     if [ $? -eq 0 ]
     then
     then
         # Compare schema with the reference
         # Compare schema with the reference
-        get_schema $testdata/v2_0.sqlite3
+        get_schema $testdata/v2_1.sqlite3
         expected_schema=$db_schema
         expected_schema=$db_schema
         get_schema $tempfile
         get_schema $tempfile
         actual_schema=$db_schema
         actual_schema=$db_schema
@@ -177,7 +177,7 @@ upgrade_ok_test() {
         fi
         fi
 
 
         # Check the version is set correctly
         # Check the version is set correctly
-        check_version $tempfile "V2.0"
+        check_version $tempfile "V2.1"
 
 
         # Check that a backup was made
         # Check that a backup was made
         check_backup $1 $2
         check_backup $1 $2
@@ -199,7 +199,7 @@ upgrade_ok_test() {
 # @param $2 Expected backup file
 # @param $2 Expected backup file
 upgrade_fail_test() {
 upgrade_fail_test() {
     copy_file $1 $tempfile
     copy_file $1 $tempfile
-    ../run_dbutil.sh --upgrade --noconfirm $tempfile
+    ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
     failzero $?
     failzero $?
     check_backup $1 $backupfile
     check_backup $1 $backupfile
 }
 }
@@ -222,7 +222,7 @@ record_count_test() {
     records_count=`sqlite3 $tempfile 'select count(*) from records'`
     records_count=`sqlite3 $tempfile 'select count(*) from records'`
     zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
     zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
 
 
-    ../run_dbutil.sh --upgrade --noconfirm $tempfile
+    ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
     if [ $? -ne 0 ]
     if [ $? -ne 0 ]
     then
     then
         # Reason for failure should already have been output
         # Reason for failure should already have been output
@@ -268,12 +268,12 @@ record_count_test() {
 # @param $2 Expected version string
 # @param $2 Expected version string
 check_version() {
 check_version() {
     copy_file $1 $verfile
     copy_file $1 $verfile
-    ../run_dbutil.sh --check $verfile
+    ${SHELL} ../run_dbutil.sh --check $verfile
     if [ $? -gt 2 ]
     if [ $? -gt 2 ]
     then
     then
         fail "version check failed on database $1; return code $?"
         fail "version check failed on database $1; return code $?"
     else
     else
-        ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
+        ${SHELL} ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
         if [ $? -ne 0 ]
         if [ $? -ne 0 ]
         then
         then
             fail "database $1 not at expected version $2 (output: $?)"
             fail "database $1 not at expected version $2 (output: $?)"
@@ -293,7 +293,7 @@ check_version() {
 # @param $2 Backup file
 # @param $2 Backup file
 check_version_fail() {
 check_version_fail() {
     copy_file $1 $verfile
     copy_file $1 $verfile
-    ../run_dbutil.sh --check $verfile
+    ${SHELL} ../run_dbutil.sh --check $verfile
     failzero $?
     failzero $?
     check_no_backup $tempfile $backupfile
     check_no_backup $tempfile $backupfile
 }
 }
@@ -305,12 +305,12 @@ rm -f $tempfile $backupfile
 
 
 # Test 1 - check that the utility fails if the database does not exist
 # Test 1 - check that the utility fails if the database does not exist
 echo "1.1. Non-existent database - check"
 echo "1.1. Non-existent database - check"
-../run_dbutil.sh --check $tempfile
+${SHELL} ../run_dbutil.sh --check $tempfile
 failzero $?
 failzero $?
 check_no_backup $tempfile $backupfile
 check_no_backup $tempfile $backupfile
 
 
 echo "1.2. Non-existent database - upgrade"
 echo "1.2. Non-existent database - upgrade"
-../run_dbutil.sh --upgrade --noconfirm $tempfile
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
 failzero $?
 check_no_backup $tempfile $backupfile
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 rm -f $tempfile $backupfile
@@ -324,7 +324,7 @@ rm -f $tempfile $backupfile
 
 
 echo "2.2. Database is an empty file - upgrade"
 echo "2.2. Database is an empty file - upgrade"
 touch $tempfile
 touch $tempfile
-../run_dbutil.sh --upgrade --noconfirm $tempfile
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
 failzero $?
 # A backup is performed before anything else, so the backup should exist.
 # A backup is performed before anything else, so the backup should exist.
 check_backup $tempfile $backupfile
 check_backup $tempfile $backupfile
@@ -338,7 +338,7 @@ rm -f $tempfile $backupfile
 
 
 echo "3.2. Database is not an SQLite file - upgrade"
 echo "3.2. Database is not an SQLite file - upgrade"
 echo "This is not an sqlite3 database" > $tempfile
 echo "This is not an sqlite3 database" > $tempfile
-../run_dbutil.sh --upgrade --noconfirm $tempfile
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
 failzero $?
 # ...and as before, a backup should have been created
 # ...and as before, a backup should have been created
 check_backup $tempfile $backupfile
 check_backup $tempfile $backupfile
@@ -421,40 +421,40 @@ rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2
 
 
 echo "13.1 Command-line errors"
 echo "13.1 Command-line errors"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 copy_file $testdata/old_v1.sqlite3 $tempfile
-../run_dbutil.sh $tempfile
+${SHELL} ../run_dbutil.sh $tempfile
 failzero $?
 failzero $?
-../run_dbutil.sh --upgrade --check $tempfile
+${SHELL} ../run_dbutil.sh --upgrade --check $tempfile
 failzero $?
 failzero $?
-../run_dbutil.sh --noconfirm --check $tempfile
+${SHELL} ../run_dbutil.sh --noconfirm --check $tempfile
 failzero $?
 failzero $?
-../run_dbutil.sh --check
+${SHELL} ../run_dbutil.sh --check
 failzero $?
 failzero $?
-../run_dbutil.sh --upgrade --noconfirm
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm
 failzero $?
 failzero $?
-../run_dbutil.sh --check $tempfile $backupfile
+${SHELL} ../run_dbutil.sh --check $tempfile $backupfile
 failzero $?
 failzero $?
-../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
 failzero $?
 failzero $?
 rm -f $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 
 echo "13.2 verbose flag"
 echo "13.2 verbose flag"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 copy_file $testdata/old_v1.sqlite3 $tempfile
-../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
+${SHELL} ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
 passzero $?
 passzero $?
 rm -f $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 
 echo "13.3 Interactive prompt - yes"
 echo "13.3 Interactive prompt - yes"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 copy_file $testdata/old_v1.sqlite3 $tempfile
-../run_dbutil.sh --upgrade $tempfile << .
+${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
 Yes
 Yes
 .
 .
 passzero $?
 passzero $?
-check_version $tempfile "V2.0"
+check_version $tempfile "V2.1"
 rm -f $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 
 echo "13.4 Interactive prompt - no"
 echo "13.4 Interactive prompt - no"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 copy_file $testdata/old_v1.sqlite3 $tempfile
-../run_dbutil.sh --upgrade $tempfile << .
+${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
 no
 no
 .
 .
 passzero $?
 passzero $?
@@ -464,7 +464,7 @@ rm -f $tempfile $backupfile
 
 
 echo "13.5 quiet flag"
 echo "13.5 quiet flag"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 copy_file $testdata/old_v1.sqlite3 $tempfile
-../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
+${SHELL} ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
 failzero $?
 failzero $?
 rm -f $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 

+ 1 - 0
src/bin/dbutil/tests/testdata/Makefile.am

@@ -10,3 +10,4 @@ EXTRA_DIST += old_v1.sqlite3
 EXTRA_DIST += README
 EXTRA_DIST += README
 EXTRA_DIST += too_many_version.sqlite3
 EXTRA_DIST += too_many_version.sqlite3
 EXTRA_DIST += v2_0.sqlite3
 EXTRA_DIST += v2_0.sqlite3
+EXTRA_DIST += v2_1.sqlite3

BIN
src/bin/dbutil/tests/testdata/v2_1.sqlite3


+ 3 - 3
src/bin/dhcp4/dhcp4_log.h

@@ -12,8 +12,8 @@
 // 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 __DHCP4_LOG__H
-#define __DHCP4_LOG__H
+#ifndef DHCP4_LOG_H
+#define DHCP4_LOG_H
 
 
 #include <log/macros.h>
 #include <log/macros.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
@@ -56,4 +56,4 @@ extern isc::log::Logger dhcp4_logger;
 } // namespace dhcp4
 } // namespace dhcp4
 } // namespace isc
 } // namespace isc
 
 
-#endif // __DHCP4_LOG__H
+#endif // DHCP4_LOG_H

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -16,6 +16,7 @@ check-local:
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 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::DhcpConfigParser 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 feature 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::DebugParser. 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": [

+ 3 - 3
src/bin/dhcp6/dhcp6_log.h

@@ -12,8 +12,8 @@
 // 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 __DHCP6_LOG__H
-#define __DHCP6_LOG__H
+#ifndef DHCP6_LOG_H
+#define DHCP6_LOG_H
 
 
 #include <log/macros.h>
 #include <log/macros.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
@@ -56,4 +56,4 @@ extern isc::log::Logger dhcp6_logger;
 } // namespace dhcp6
 } // namespace dhcp6
 } // namespace isc
 } // namespace isc
 
 
-#endif // __DHCP6_LOG__H
+#endif // DHCP6_LOG_H

+ 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)

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

@@ -15,6 +15,7 @@ check-local:
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done
@@ -45,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
@@ -61,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());
+}
+
+};

+ 0 - 37
src/bin/host/Makefile.am

@@ -1,37 +0,0 @@
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-if USE_STATIC_LINK
-AM_LDFLAGS = -static
-endif
-
-CLEANFILES = *.gcno *.gcda
-
-bin_PROGRAMS = b10-host
-b10_host_SOURCES = host.cc
-b10_host_LDADD = $(top_builddir)/src/lib/dns/libb10-dns++.la
-b10_host_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-b10_host_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-
-man_MANS = b10-host.1
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) b10-host.xml
-
-.PHONY: man
-if GENERATE_DOCS
-
-man: b10-host.1
-
-b10-host.1: b10-host.xml
-	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-host.xml
-
-else
-
-$(man_MANS):
-	@echo Man generation disabled.  Creating dummy $@.  Configure with --enable-generate-docs to enable it.
-	@echo Man generation disabled.  Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
-
-endif

+ 3 - 3
src/bin/resolver/resolver.h

@@ -12,8 +12,8 @@
 // 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 __RESOLVER_H
-#define __RESOLVER_H 1
+#ifndef RESOLVER_H
+#define RESOLVER_H 1
 
 
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
@@ -266,7 +266,7 @@ private:
     isc::cache::ResolverCache* cache_;
     isc::cache::ResolverCache* cache_;
 };
 };
 
 
-#endif // __RESOLVER_H
+#endif // RESOLVER_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 3 - 3
src/bin/resolver/resolver_log.h

@@ -12,8 +12,8 @@
 // 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 __RESOLVER_LOG__H
-#define __RESOLVER_LOG__H
+#ifndef RESOLVER_LOG_H
+#define RESOLVER_LOG_H
 
 
 #include <log/macros.h>
 #include <log/macros.h>
 #include "resolver_messages.h"
 #include "resolver_messages.h"
@@ -46,4 +46,4 @@ const int RESOLVER_DBG_DETAIL = DBGLVL_TRACE_DETAIL_DATA;
 /// space.
 /// space.
 extern isc::log::Logger resolver_logger;
 extern isc::log::Logger resolver_logger;
 
 
-#endif // __RESOLVER_LOG__H
+#endif // RESOLVER_LOG_H

+ 1 - 1
src/bin/resolver/resolver_messages.mes

@@ -108,7 +108,7 @@ This error is issued when a resolver configuration update has specified
 a negative retry count: only zero or positive values are valid.  The
 a negative retry count: only zero or positive values are valid.  The
 configuration update was abandoned and the parameters were not changed.
 configuration update was abandoned and the parameters were not changed.
 
 
-% RESOLVER_NON_IN_PACKET non-IN class request received, returning REFUSED message
+% RESOLVER_NON_IN_PACKET non-IN class (%1) request received, returning REFUSED message
 This debug message is issued when resolver has received a DNS packet that
 This debug message is issued when resolver has received a DNS packet that
 was not IN (Internet) class.  The resolver cannot handle such packets,
 was not IN (Internet) class.  The resolver cannot handle such packets,
 so is returning a REFUSED response to the sender.
 so is returning a REFUSED response to the sender.

+ 3 - 3
src/bin/resolver/response_scrubber.h

@@ -14,8 +14,8 @@
 
 
 // $Id$
 // $Id$
 
 
-#ifndef __RESPONSE_SCRUBBER_H
-#define __RESPONSE_SCRUBBER_H
+#ifndef RESPONSE_SCRUBBER_H
+#define RESPONSE_SCRUBBER_H
 
 
 /// \page DataScrubbing Data Scrubbing
 /// \page DataScrubbing Data Scrubbing
 /// \section DataScrubbingIntro Introduction
 /// \section DataScrubbingIntro Introduction
@@ -419,4 +419,4 @@ public:
     }
     }
 };
 };
 
 
-#endif // __RESPONSE_SCRUBBER_H
+#endif // RESPONSE_SCRUBBER_H

+ 3 - 3
src/bin/sockcreator/sockcreator.h

@@ -18,8 +18,8 @@
 /// This module holds the functionality of the socket creator. It is a separate
 /// This module holds the functionality of the socket creator. It is a separate
 /// module from main to make testing easier.
 /// module from main to make testing easier.
 
 
-#ifndef __SOCKCREATOR_H
-#define __SOCKCREATOR_H 1
+#ifndef SOCKCREATOR_H
+#define SOCKCREATOR_H 1
 
 
 #include <util/io/fd_share.h>
 #include <util/io/fd_share.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
@@ -144,4 +144,4 @@ run(const int input_fd, const int output_fd, get_sock_t get_sock_fun,
 }   // namespace socket_creator
 }   // namespace socket_creator
 }   // NAMESPACE ISC
 }   // NAMESPACE ISC
 
 
-#endif // __SOCKCREATOR_H
+#endif // SOCKCREATOR_H

+ 2 - 1
src/bin/sysinfo/.gitignore

@@ -1,3 +1,4 @@
 /isc-sysinfo
 /isc-sysinfo
-/sysinfo.py
 /isc-sysinfo.1
 /isc-sysinfo.1
+/run_sysinfo.sh
+/sysinfo.py

+ 1 - 0
src/bin/sysinfo/Makefile.am

@@ -1,4 +1,5 @@
 bin_SCRIPTS = isc-sysinfo
 bin_SCRIPTS = isc-sysinfo
+noinst_SCRIPTS = run_sysinfo.sh
 
 
 CLEANFILES = isc-sysinfo sysinfo.pyc
 CLEANFILES = isc-sysinfo sysinfo.pyc
 
 

+ 38 - 0
src/bin/sysinfo/run_sysinfo.sh.in

@@ -0,0 +1,38 @@
+#! /bin/sh
+
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
+
+# Note: we shouldn't need log_messages except for the seemingly necessary
+# dependency due to the automatic import in the isc package (its __init__.py
+# imports some other modules)
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+export PYTHONPATH
+
+# Likewise, we need only because isc.log requires some loadable modules.
+# sysinfo itself shouldn't need any of them.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+	export @ENV_LIBRARY_PATH@
+fi
+
+cd ${SYSINFO_PATH}
+exec ${PYTHON_EXEC} -O sysinfo.py "$@"

+ 2 - 4
src/bin/sysinfo/sysinfo.py.in

@@ -22,11 +22,8 @@ ISC sysinfo program.
 
 
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import getopt
 import getopt
-import isc.util.process
 from isc.sysinfo import *
 from isc.sysinfo import *
 
 
-isc.util.process.rename()
-
 def usage():
 def usage():
     print("Usage: %s [-h] [-o <output-file>]" % sys.argv[0], \
     print("Usage: %s [-h] [-o <output-file>]" % sys.argv[0], \
               file=sys.stderr)
               file=sys.stderr)
@@ -88,7 +85,8 @@ def main():
 
 
     write_value(f, ' + Machine name: %s\n', s.get_platform_machine)
     write_value(f, ' + Machine name: %s\n', s.get_platform_machine)
     write_value(f, ' + Hostname: %s\n', s.get_platform_hostname)
     write_value(f, ' + Hostname: %s\n', s.get_platform_hostname)
-    write_value(f, ' + Uptime: %d seconds\n', s.get_uptime)
+    write_value(f, ' + Uptime: %d seconds', s.get_uptime)
+    write_value(f, ' (%s)\n', s.get_uptime_desc)
 
 
     write_value(f, ' + Loadavg: %f %f %f\n', s.get_loadavg)
     write_value(f, ' + Loadavg: %f %f %f\n', s.get_loadavg)
 
 

+ 7 - 0
src/bin/tests/process_rename_test.py.in

@@ -39,6 +39,11 @@ class TestRename(unittest.TestCase):
         Then scan them by looking at the source text
         Then scan them by looking at the source text
         (without actually running them)
         (without actually running them)
         """
         """
+
+        # Scripts named in this list are not expected to be renamed and
+        # should be excluded from the scan.
+        EXCLUDED_SCRIPTS = ['isc-sysinfo']
+
         # Regexp to find all the *_SCRIPTS = something lines (except for
         # Regexp to find all the *_SCRIPTS = something lines (except for
         # noinst_SCRIPTS, which are scripts for tests), including line
         # noinst_SCRIPTS, which are scripts for tests), including line
         # continuations (backslash and newline)
         # continuations (backslash and newline)
@@ -59,6 +64,8 @@ class TestRename(unittest.TestCase):
                 for (var, _) in lines.findall(re.sub(excluded_lines, '',
                 for (var, _) in lines.findall(re.sub(excluded_lines, '',
                                                      makefile)):
                                                      makefile)):
                     for (script, _) in scripts.findall(var):
                     for (script, _) in scripts.findall(var):
+                        if script in EXCLUDED_SCRIPTS:
+                            continue
                         self.__scan(d, script, fun)
                         self.__scan(d, script, fun)
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 15 - 0
src/cppcheck-suppress.lst

@@ -11,3 +11,18 @@ missingInclude
 //
 //
 //    // cppcheck-suppress duplicateExpression
 //    // cppcheck-suppress duplicateExpression
 //    EXPECT_FALSE(small_name < small_name);
 //    EXPECT_FALSE(small_name < small_name);
+
+// With cppcheck 1.56, there are a number of false positives, which
+// All of these should be checked and hopefully removed after upgrading
+// cppcheck past 1.56
+
+// eraseDereference: This is a known false positive, which has been
+// fixed in the current development version of cppcheck
+eraseDereference
+
+// Unused functions: there suddenly are a lot of unused function errors
+// We could address those by adding for instance early declarations or
+// (unnecessary) header files, but they were all somewhat false positives
+// When we upgrade past 1.56, we should re-check this, and perhaps enable
+// unused-functions again.
+unusedFunction

+ 13 - 10
src/lib/acl/dns.cc

@@ -12,12 +12,6 @@
 // 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.
 
 
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <dns/name.h>
 #include <dns/name.h>
@@ -31,6 +25,13 @@
 #include <acl/loader.h>
 #include <acl/loader.h>
 #include <acl/logic_check.h>
 #include <acl/logic_check.h>
 
 
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <memory>
+#include <string>
+#include <vector>
+
 using namespace std;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::data;
@@ -106,10 +107,12 @@ internal::RequestCheckCreator::create(const string& name,
 
 
 RequestLoader&
 RequestLoader&
 getRequestLoader() {
 getRequestLoader() {
-    static RequestLoader* loader(NULL);
-    if (loader == NULL) {
+    // To ensure that the singleton gets destroyed at the end of the
+    // program's lifetime, we put it in a static scoped_ptr.
+    static boost::scoped_ptr<RequestLoader> loader(NULL);
+    if (loader.get() == NULL) {
         // Creator registration may throw, so we first store the new loader
         // Creator registration may throw, so we first store the new loader
-        // in an auto pointer in order to provide the strong exception
+        // in a second auto pointer in order to provide the strong exception
         // guarantee.
         // guarantee.
         auto_ptr<RequestLoader> loader_ptr =
         auto_ptr<RequestLoader> loader_ptr =
             auto_ptr<RequestLoader>(new RequestLoader(REJECT));
             auto_ptr<RequestLoader>(new RequestLoader(REJECT));
@@ -129,7 +132,7 @@ getRequestLoader() {
                 new LogicCreator<AllOfSpec, RequestContext>("ALL")));
                 new LogicCreator<AllOfSpec, RequestContext>("ALL")));
 
 
         // From this point there shouldn't be any exception thrown
         // From this point there shouldn't be any exception thrown
-        loader = loader_ptr.release();
+        loader.reset(loader_ptr.release());
     }
     }
 
 
     return (*loader);
     return (*loader);

+ 3 - 3
src/lib/acl/dnsname_check.h

@@ -12,8 +12,8 @@
 // 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 __DNSNAME_CHECK_H
-#define __DNSNAME_CHECK_H 1
+#ifndef DNSNAME_CHECK_H
+#define DNSNAME_CHECK_H 1
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 
 
@@ -76,7 +76,7 @@ private:
 } // namespace acl
 } // namespace acl
 } // namespace isc
 } // namespace isc
 
 
-#endif // __DNSNAME_CHECK_H
+#endif // DNSNAME_CHECK_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 3 - 3
src/lib/acl/ip_check.h

@@ -12,8 +12,8 @@
 // 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 __IP_CHECK_H
-#define __IP_CHECK_H
+#ifndef IP_CHECK_H
+#define IP_CHECK_H
 
 
 #include <sys/socket.h>
 #include <sys/socket.h>
 
 
@@ -410,7 +410,7 @@ const size_t IPCheck<Context>::IPV4_SIZE;
 } // namespace acl
 } // namespace acl
 } // namespace isc
 } // namespace isc
 
 
-#endif // __IP_CHECK_H
+#endif // IP_CHECK_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 3 - 3
src/lib/acl/tests/sockaddr.h

@@ -12,8 +12,8 @@
 // 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 __ACL_TEST_SOCKADDR_H
-#define __ACL_TEST_SOCKADDR_H 1
+#ifndef ACL_TEST_SOCKADDR_H
+#define ACL_TEST_SOCKADDR_H 1
 
 
 #include <sys/types.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/socket.h>
@@ -62,7 +62,7 @@ getSockAddr(const char* const addr) {
 } // end of namespace "acl"
 } // end of namespace "acl"
 } // end of namespace "isc"
 } // end of namespace "isc"
 
 
-#endif  // __ACL_TEST_SOCKADDR_H
+#endif  // ACL_TEST_SOCKADDR_H
 
 
 // Local Variables:
 // Local Variables:
 // mode: c++
 // mode: c++

+ 3 - 3
src/lib/asiodns/asiodns.h

@@ -12,12 +12,12 @@
 // 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 __ASIODNS_H
-#define __ASIODNS_H 1
+#ifndef ASIODNS_H
+#define ASIODNS_H 1
 
 
 #include <asiodns/dns_service.h>
 #include <asiodns/dns_service.h>
 #include <asiodns/dns_server.h>
 #include <asiodns/dns_server.h>
 #include <asiodns/dns_lookup.h>
 #include <asiodns/dns_lookup.h>
 #include <asiodns/dns_answer.h>
 #include <asiodns/dns_answer.h>
 
 
-#endif // __ASIODNS_H
+#endif // ASIODNS_H

+ 3 - 3
src/lib/asiodns/dns_answer.h

@@ -12,8 +12,8 @@
 // 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 __ASIOLINK_DNS_ANSWER_H
-#define __ASIOLINK_DNS_ANSWER_H 1
+#ifndef ASIOLINK_DNS_ANSWER_H
+#define ASIOLINK_DNS_ANSWER_H 1
 
 
 #include <asiolink/io_message.h>
 #include <asiolink/io_message.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
@@ -74,4 +74,4 @@ public:
 
 
 }      // namespace asiodns
 }      // namespace asiodns
 }      // namespace isc
 }      // namespace isc
-#endif // __ASIOLINK_DNS_ANSWER_H
+#endif // ASIOLINK_DNS_ANSWER_H

+ 3 - 3
src/lib/asiodns/dns_lookup.h

@@ -12,8 +12,8 @@
 // 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 __ASIOLINK_DNS_LOOKUP_H
-#define __ASIOLINK_DNS_LOOKUP_H 1
+#ifndef ASIOLINK_DNS_LOOKUP_H
+#define ASIOLINK_DNS_LOOKUP_H 1
 
 
 #include <asiolink/io_message.h>
 #include <asiolink/io_message.h>
 #include <asiodns/dns_server.h>
 #include <asiodns/dns_server.h>
@@ -84,4 +84,4 @@ private:
 
 
 }      // namespace asiodns
 }      // namespace asiodns
 }      // namespace isc
 }      // namespace isc
-#endif // __ASIOLINK_DNS_LOOKUP_H
+#endif // ASIOLINK_DNS_LOOKUP_H

+ 0 - 0
src/lib/asiodns/dns_server.h


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff