Parcourir la source

Sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac356@3822 e5f2f494-b856-4b98-b285-d166d9295462
Michal Vaner il y a 14 ans
Parent
commit
fb41742d7d
100 fichiers modifiés avec 4319 ajouts et 1333 suppressions
  1. 143 9
      ChangeLog
  2. 1 1
      Makefile.am
  3. 3 2
      README
  4. 35 9
      configure.ac
  5. 1 1
      src/bin/Makefile.am
  6. 6 0
      src/bin/auth/Makefile.am
  7. 97 13
      src/bin/auth/asio_link.cc
  8. 2 2
      src/bin/auth/asio_link.h
  9. 24 25
      src/bin/auth/auth_srv.cc
  10. 96 3
      src/bin/auth/auth_srv.h
  11. 1 0
      src/bin/auth/benchmarks/Makefile.am
  12. 1 0
      src/bin/auth/benchmarks/query_bench.cc
  13. 23 22
      src/lib/dns/tests/tsig_unittest.cc
  14. 119 0
      src/bin/auth/query.h
  15. 4 1
      src/bin/auth/tests/Makefile.am
  16. 86 90
      src/bin/auth/tests/auth_srv_unittest.cc
  17. 70 0
      src/bin/auth/tests/query_unittest.cc
  18. 1 2
      src/bin/auth/tests/run_unittests.cc
  19. 322 219
      src/bin/bind10/bind10.py.in
  20. 12 0
      src/bin/bind10/bob.spec
  21. 1 1
      src/bin/bind10/run_bind10.sh.in
  22. 202 29
      src/bin/bind10/tests/bind10_test.py
  23. 5 2
      src/bin/bindctl/bindcmd.py
  24. 8 5
      src/bin/bindctl/bindctl-source.py.in
  25. 2 2
      src/bin/cfgmgr/b10-cfgmgr.py.in
  26. 1 1
      src/bin/cmdctl/Makefile.am
  27. 20 18
      src/bin/cmdctl/cmdctl.py.in
  28. 1 0
      src/bin/host/Makefile.am
  29. 6 5
      src/bin/host/host.cc
  30. 2 2
      src/bin/loadzone/b10-loadzone.py.in
  31. 1 1
      src/bin/loadzone/tests/error/error.known
  32. 7 5
      src/bin/msgq/msgq.py.in
  33. 1 0
      src/bin/msgq/tests/Makefile.am
  34. 7 0
      src/bin/msgq/tests/msgq_test.py
  35. 20 0
      src/bin/recurse/Makefile.am
  36. 7 0
      src/bin/recurse/README_FIRST.txt
  37. 177 0
      src/bin/recurse/recurse.py.in
  38. 15 0
      src/bin/recurse/recurse.spec.pre.in
  39. 27 0
      src/bin/recurse/run_b10-recurse.sh.in
  40. 37 0
      src/bin/stats/Makefile.am
  41. 68 0
      src/bin/stats/b10-stats.8
  42. 124 0
      src/bin/stats/b10-stats.xml
  43. 30 0
      src/bin/stats/run_b10-stats.sh.in
  44. 30 0
      src/bin/stats/run_b10-stats_stub.sh.in
  45. 416 0
      src/bin/stats/stats.py.in
  46. 140 0
      src/bin/stats/stats.spec.pre.in
  47. 155 0
      src/bin/stats/stats_stub.py.in
  48. 0 151
      src/bin/stats/statsd.py
  49. 0 55
      src/bin/stats/statsd.txt
  50. 0 6
      src/bin/stats/test/shutdown.py
  51. 0 178
      src/bin/stats/test/test_agent.py
  52. 0 54
      src/bin/stats/test_total.py
  53. 15 0
      src/bin/stats/tests/Makefile.am
  54. 116 0
      src/bin/stats/tests/b10-stats_stub_test.py
  55. 646 0
      src/bin/stats/tests/b10-stats_test.py
  56. 48 0
      src/bin/stats/tests/fake_time.py
  57. 3 0
      src/bin/stats/tests/isc/Makefile.am
  58. 0 0
      src/bin/stats/tests/isc/__init__.py
  59. 2 0
      src/bin/stats/tests/isc/cc/Makefile.am
  60. 1 0
      src/bin/stats/tests/isc/cc/__init__.py
  61. 127 0
      src/bin/stats/tests/isc/cc/session.py
  62. 2 0
      src/bin/stats/tests/isc/config/Makefile.am
  63. 1 0
      src/bin/stats/tests/isc/config/__init__.py
  64. 114 0
      src/bin/stats/tests/isc/config/ccsession.py
  65. 2 0
      src/bin/stats/tests/isc/util/Makefile.am
  66. 0 0
      src/bin/stats/tests/isc/util/__init__.py
  67. 20 0
      src/bin/stats/tests/isc/util/process.py
  68. 2 0
      src/bin/stats/tests/isc/utils/Makefile.am
  69. 0 0
      src/bin/stats/tests/isc/utils/__init__.py
  70. 20 0
      src/bin/stats/tests/isc/utils/process.py
  71. 31 0
      src/bin/stats/tests/stats_test.in
  72. 1 0
      src/bin/stats/tests/testdata/Makefile.am
  73. 19 0
      src/bin/stats/tests/testdata/stats_test.spec
  74. 3 3
      src/bin/tests/process_rename_test.py.in
  75. 2 2
      src/bin/usermgr/b10-cmdctl-usermgr.py.in
  76. 2 4
      src/bin/xfrin/Makefile.am
  77. 0 1
      src/bin/xfrin/b10-xfrin.xml
  78. 7 7
      src/bin/xfrin/tests/xfrin_test.py
  79. 44 39
      src/bin/xfrin/xfrin.py.in
  80. 0 6
      src/bin/xfrin/xfrin.spec.pre.in
  81. 2 7
      src/bin/xfrout/b10-xfrout.8
  82. 1 8
      src/bin/xfrout/b10-xfrout.xml
  83. 52 36
      src/bin/xfrout/tests/xfrout_test.py
  84. 134 113
      src/bin/xfrout/xfrout.py.in
  85. 0 6
      src/bin/xfrout/xfrout.spec.pre.in
  86. 2 0
      src/bin/zonemgr/tests/Makefile.am
  87. 94 36
      src/bin/zonemgr/tests/zonemgr_test.py
  88. 158 97
      src/bin/zonemgr/zonemgr.py.in
  89. 24 0
      src/bin/zonemgr/zonemgr.spec.pre.in
  90. 1 0
      src/lib/bench/Makefile.am
  91. 2 2
      src/lib/bench/benchmark.h
  92. 6 3
      src/lib/bench/example/search_bench.cc
  93. 1 0
      src/lib/bench/tests/Makefile.am
  94. 6 6
      src/lib/bench/tests/loadquery_unittest.cc
  95. 10 3
      src/lib/cc/Makefile.am
  96. 35 37
      src/lib/cc/data.cc
  97. 4 0
      src/lib/cc/tests/Makefile.am
  98. 1 1
      src/lib/cc/tests/data_unittests.cc
  99. 1 2
      src/lib/cc/tests/run_unittests.cc
  100. 0 0
      src/lib/cc/tests/session_unittests.cc

+ 143 - 9
ChangeLog

@@ -1,3 +1,137 @@
+bind10-devel-20101201 released on December 01, 2010
+
+  125.  [func]		jelte
+	Added support for addressing individual list items in bindctl
+	configuration commands; If you have an element that is a list, you
+	can use foo[X] to address a specific item, where X is an integer
+	(starting at 0)
+	(Trac #405, svn r3739)
+
+  124.  [bug]		jreed
+	Fix some wrong version reporting. Now also show the version
+	for the component and BIND 10 suite. (Trac #302, svn r3696)
+
+  123.  [bug]		jelte
+	src/bin/bindctl printed values had the form of python literals
+	(e.g. 'True'), while the input requires valid JSON (e.g. 'true').
+	Output changed to JSON format for consistency. (svn r3694)
+
+  122.  [func]		stephen
+	src/bin/bind10: Added configuration options to Boss to determine
+	whether to start the authoritative server, recursive server (or
+	both). A dummy recursor has been provided for test purposes.
+	(Trac #412, svn r3676)
+
+  121.  [func]		jinmei
+	src/lib/dns: Added support for TSIG RDATA.  At this moment this is
+	not much of real use, however, because no protocol support was
+	added yet.  It will soon be added. (Trac #372, svn r3649)
+
+  120.  [func]		jinmei
+	src/lib/dns: introduced two new classes, TSIGKey and TSIGKeyRing,
+	to manage TSIG keys. (Trac #381, svn r3622)
+
+  119.	[bug]		jinmei
+	The master file parser of the python datasrc module incorrectly
+	regarded a domain name beginning with a decimal number as a TTL
+	specification.  This confused b10-loadzone and had it reject to
+	load a zone file that contains such a name.
+	Note: this fix is incomplete and the loadzone would still be
+	confused if the owner name is a syntactically indistinguishable
+	from a TTL specification.  This is part of a more general issue
+	and will be addressed in Trac #413. (Trac #411, svn r3599)
+
+  118.	[func]		jinmei
+	src/lib/dns: changed the interface of
+	AbstractRRset::getRdataIterator() so that the internal
+	cursor would point to the first RDATA automatically.  This
+	will be a more intuitive and less error prone behavior.
+	This is a backward compatible change. (Trac #410, r3595)
+
+  117.  [func]		jinmei
+	src/lib/datasrc: added new zone and zone table classes for the
+	support of in memory data source.  This is an intermediate step to
+	the bigger feature, and is not yet actually usable in practice.
+	(Trac #399, svn r3590)
+
+  116.	[bug]		jerry
+	src/bin/xfrout: Xfrout and Auth will communicate by long tcp
+	connection, Auth needs to make a new connection only on the first
+	time or if an error occurred.
+	(Trac #299, svn r3482)
+
+  115.	[func]*		jinmei
+	src/lib/dns: Changed DNS message flags and section names from
+	separate classes to simpler enums, considering the balance between
+	type safety and usability.  API has been changed accordingly.
+	More documentation and tests were provided with these changes.
+	(Trac #358, r3439)
+
+  114.	[build]		jinmei
+	Supported clang++.  Note: Boost >= 1.44 is required.
+	(Trac #365, svn r3383)
+
+  113.	[func]*		zhanglikun
+	Folder name 'utils'(the folder in /src/lib/python/isc/) has been
+	renamed	to 'util'. Programs that used 'import isc.utils.process'
+	now need to use 'import isc.util.process'. The folder
+	/src/lib/python/isc/Util is removed since it isn't used by any
+	program. (Trac #364, r3382)
+
+  112.	[func]		zhang likun
+	Add one mixin class to override the naive serve_forever() provided
+	in python library socketserver. Instead of polling for shutdwon
+	every poll_interval seconds, one socketpair is used to wake up
+	the waiting server. (Trac #352, svn r3366)
+
+  111.	[bug]*   zhanglikun, Michal Vaner
+	Make sure process xfrin/xfrout/zonemgr/cmdctl can be stopped
+	properly when user enter "ctrl+c" or 'Boss shutdown' command
+	through bindctl.  The ZonemgrRefresh.run_timer and
+	NotifyOut.dispatcher spawn a thread themselves.
+	(Trac #335, svn r3273)
+
+  110.  [func]      Michal Vaner
+	Added isc.net.check module to check ip addresses and ports for
+	correctness and isc.net.addr to hold IP address. The bind10, xfrin
+	and cmdctl programs are modified to use it.
+	(Trac #353, svn r3240)
+
+  109.  [func]		naokikambe
+	Added the initial version of the stats module for the statistics
+	feature of BIND 10, which supports the restricted features and
+	items and reports via bindctl command. (Trac #191, r3218)
+	Added the document of the stats module, which is about how stats
+	module collects the data (Trac #170, [wiki:StatsModule])
+
+  108.	[func]		jerry
+	src/bin/zonemgr: Provide customizable configurations for
+	lowerbound_refresh, lowerbound_retry, max_transfer_timeout and
+	jitter_scope. (Trac #340, r3205)
+
+  107.  [func]       zhang likun
+	Remove the parameter 'db_file' for command 'retransfer' of
+	xfrin module. xfrin.spec will not be generated by script.
+	(Trac #329, r3171)
+
+  106.  [bug]       zhang likun
+	When xfrin can't connect with one zone's master, it should tell
+	the bad news to zonemgr, so that zonemgr can reset the timer for
+	that zone. (Trac #329, r3170)
+
+  105.  [bug]       Michal Vaner
+	Python processes: they no longer take 100% CPU while idle
+	due to a busy loop in reading command session in a nonblocking way.
+	(Trac #349, svn r3153), (Trac #382, svn r3294)
+
+  104.	[bug]		jerry
+	bin/zonemgr: zonemgr should be attempting to refresh expired zones.
+	(Trac #336, r3139)
+ 
+  103.	[bug]		jerry
+	lib/python/isc/log: Fixed an issue with python logging,
+	python log shouldn't die with OSError. (Trac #267, r3137)
+ 
   102.	[build]		jinmei
 	Disable threads in ASIO to minimize build time dependency.
 	(Trac #345, r3100)
@@ -50,7 +184,7 @@ bind10-devel-20100917 released on September 17, 2010
   93.	[bug]		jinmei
 	lib/datasrc: A DS query could crash the library (and therefore,
 	e.g. the authoritative server) if some RR of the same apex name
-	is stored in the hot spot cache.  (Trac #307, svn r2923)
+	is stored in the hot spot cache. (Trac #307, svn r2923)
 
   92.	[func]*		jelte
 	libdns_python (the python wrappers for libdns++) has been renamed
@@ -89,8 +223,8 @@ bind10-devel-20100917 released on September 17, 2010
 	zone axfr/ixfr finishing, the server will notify its slaves.
 	(Trac #289, svn r2737)
 
-  86.   [func]		jerry
-    	bin/zonemgr: Added zone manager module. The zone manager is one 
+  86.	[func]		jerry
+	bin/zonemgr: Added zone manager module. The zone manager is one 
 	of the co-operating processes of BIND10, which keeps track of 
 	timers and other information necessary for BIND10 to act as a 
 	slave. (Trac #215, svn r2737)
@@ -230,7 +364,7 @@ bind10-devel-20100701 released on July 1, 2010
   66.  [bug]		each
 	Check for duplicate RRsets before inserting data into a message
 	section; this, among other things, will prevent multiple copies
-	of the same CNAME from showing up when there's a loop.  (Trac #69,
+	of the same CNAME from showing up when there's a loop. (Trac #69,
 	svn r2350)
     
   65.  [func]		shentingting
@@ -352,7 +486,7 @@ bind10-devel-20100602 released on June 2, 2010
 	#205, svn r1957)
 
   44.   [build]         jreed
-	Install headers for libdns and libexception.  (Trac #68,
+	Install headers for libdns and libexception. (Trac #68,
 	svn r1941)
 
   43.   [func]          jelte
@@ -360,7 +494,7 @@ bind10-devel-20100602 released on June 2, 2010
 
   42.   [func]          jelte
 	lib/python/isc/config:      Make temporary file with python
-	tempfile module instead of manual with fixed name.  (Trac
+	tempfile module instead of manual with fixed name. (Trac
 	#184, svn r1859)
 
   41.   [func]          jelte
@@ -368,7 +502,7 @@ bind10-devel-20100602 released on June 2, 2010
 
   40.   [build]         jreed
 	Report detected features and configure settings at end of
-	configure output.  (svn r1836)
+	configure output. (svn r1836)
 
   39.   [func]*         each
 	Renamed libauth to libdatasrc.
@@ -381,7 +515,7 @@ bind10-devel-20100602 released on June 2, 2010
 	(Trac #135, #151, #134, svn r1797)
 
   37.   [build]         jinmei
-	Check for the availability of python-config.  (Trac #159,
+	Check for the availability of python-config. (Trac #159,
 	svn r1794)
 
   36.	[func]		shane
@@ -426,7 +560,7 @@ bind10-devel-20100421 released on April 21, 2010
 
   27.	[build]
 	Add missing copyright license statements to various source
-	files.  (svn r1750)
+	files. (svn r1750)
 
   26.	[func]
 	Use PACKAGE_STRING (name + version) from config.h instead

+ 1 - 1
Makefile.am

@@ -37,7 +37,7 @@ report-coverage:
 			\*_unittest.cc \
 			\*_unittests.h \
 		--output report.info
-	$(GENHTML) -o coverage report.info 
+	$(GENHTML) --legend -o coverage report.info 
 
 coverage: clean-coverage perform-coverage report-coverage
 

+ 3 - 2
README

@@ -17,8 +17,9 @@ This release includes the bind10 master process, b10-msgq message
 bus, b10-auth authoritative DNS server (with SQLite3 backend),
 b10-cmdctl remote control daemon, b10-cfgmgr configuration manager,
 b10-xfrin AXFR inbound service, b10-xfrout outgoing AXFR service,
-b10-zonemgr secondary manager, and a new libdns++ library for C++
-with a python wrapper.
+b10-zonemgr secondary manager, b10-stats statistics collection and
+reporting daemon, and a new libdns++ library for C++ with a python
+wrapper.
 
 Documentation is included and also available via the BIND 10
 website at http://bind10.isc.org/

+ 35 - 9
configure.ac

@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20100701, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20101201, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE
 AC_CONFIG_HEADERS([config.h])
@@ -17,6 +17,8 @@ AC_LANG([C++])
 # Identify the compiler: this check must be after AC_PROG_CXX and AC_LANG.
 AM_CONDITIONAL(USE_GXX, test "X${GXX}" = "Xyes")
 AC_CHECK_DECL([__SUNPRO_CC], [SUNCXX="yes"], [SUNCXX="no"])
+AC_CHECK_DECL([__clang__], [CLANGPP="yes"], [CLANGPP="no"])
+AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 
 # Linker options
 
@@ -204,7 +206,6 @@ fi
 # gcc specific settings:
 if test "X$GXX" = "Xyes"; then
 B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
-UNUSED_PARAM_ATTRIBUTE='__attribute__((unused))'
 
 # 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
@@ -223,7 +224,6 @@ CXXFLAGS="$CXXFLAGS_SAVED"
 fi				dnl GXX = yes
 
 AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-AC_DEFINE_UNQUOTED(UNUSED_PARAM, $UNUSED_PARAM_ATTRIBUTE, Define to compiler keyword indicating a function argument is intentionally unused)
 
 # produce PIC unless we disable shared libraries. need this for python bindings.
 if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
@@ -261,7 +261,6 @@ AC_ARG_WITH(gtest,
 [  --with-gtest=PATH       specify a path to gtest header files (PATH/include) and library (PATH/lib)],
     gtest_path="$withval", gtest_path="no")
 
-
 USE_LCOV="no"
 if test "$lcov" != "no"; then
 	# force gtest if not set
@@ -314,12 +313,14 @@ if test -z "$with_boost_include"; then
 		fi
 	done
 fi
+CPPFLAGS_SAVES="$CPPFLAGS"
 if test "${boost_include_path}" ; then
 	BOOST_INCLUDES="-I${boost_include_path}"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 fi
 AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
+CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(BOOST_INCLUDES)
 
 #
@@ -435,7 +436,7 @@ if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
 fi
 
 AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man],
-  [regenerate man pages [default=no]])] ,enable_man=yes, enable_man=no)
+  [regenerate man pages [default=no]])], enable_man=yes, enable_man=no)
 
 AM_CONDITIONAL(ENABLE_MAN, test x$enable_man != xno)
 
@@ -466,12 +467,20 @@ AC_CONFIG_FILES([Makefile
                  src/bin/auth/tests/Makefile
                  src/bin/auth/tests/testdata/Makefile
                  src/bin/auth/benchmarks/Makefile
+                 src/bin/recurse/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/tests/Makefile
                  src/bin/zonemgr/Makefile
                  src/bin/zonemgr/tests/Makefile
+                 src/bin/stats/Makefile
+                 src/bin/stats/tests/Makefile
+                 src/bin/stats/tests/isc/Makefile
+                 src/bin/stats/tests/isc/cc/Makefile
+                 src/bin/stats/tests/isc/config/Makefile
+                 src/bin/stats/tests/isc/util/Makefile
+                 src/bin/stats/tests/testdata/Makefile
                  src/bin/usermgr/Makefile
                  src/bin/tests/Makefile
                  src/lib/Makefile
@@ -482,15 +491,18 @@ AC_CONFIG_FILES([Makefile
                  src/lib/cc/tests/Makefile
                  src/lib/python/Makefile
                  src/lib/python/isc/Makefile
-                 src/lib/python/isc/utils/Makefile
-                 src/lib/python/isc/utils/tests/Makefile
+                 src/lib/python/isc/util/Makefile
+                 src/lib/python/isc/util/tests/Makefile
                  src/lib/python/isc/datasrc/Makefile
+                 src/lib/python/isc/datasrc/tests/Makefile
                  src/lib/python/isc/cc/Makefile
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile
                  src/lib/python/isc/config/tests/Makefile
                  src/lib/python/isc/log/Makefile
                  src/lib/python/isc/log/tests/Makefile
+                 src/lib/python/isc/net/Makefile
+                 src/lib/python/isc/net/tests/Makefile
                  src/lib/python/isc/notify/Makefile
                  src/lib/python/isc/notify/tests/Makefile
                  src/lib/config/Makefile
@@ -517,16 +529,24 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cmdctl/cmdctl.spec.pre
            src/bin/xfrin/tests/xfrin_test
            src/bin/xfrin/xfrin.py
-           src/bin/xfrin/xfrin.spec.pre
            src/bin/xfrin/run_b10-xfrin.sh
            src/bin/xfrout/xfrout.py
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/run_b10-xfrout.sh
+           src/bin/recurse/recurse.py
+           src/bin/recurse/recurse.spec.pre
+           src/bin/recurse/run_b10-recurse.sh
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/run_b10-zonemgr.sh
+           src/bin/stats/stats.py
+           src/bin/stats/stats_stub.py
+           src/bin/stats/stats.spec.pre
+           src/bin/stats/run_b10-stats.sh
+           src/bin/stats/run_b10-stats_stub.sh
+           src/bin/stats/tests/stats_test
            src/bin/bind10/bind10.py
            src/bin/bind10/tests/bind10_test
            src/bin/bind10/run_bind10.sh
@@ -559,7 +579,11 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
+           chmod +x src/bin/recurse/run_b10-recurse.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+           chmod +x src/bin/stats/tests/stats_test
+           chmod +x src/bin/stats/run_b10-stats.sh
+           chmod +x src/bin/stats/run_b10-stats_stub.sh
            chmod +x src/bin/bind10/run_bind10.sh
            chmod +x src/bin/cmdctl/tests/cmdctl_test
            chmod +x src/bin/xfrin/tests/xfrin_test
@@ -590,6 +614,8 @@ Package:
   Name:          $PACKAGE_NAME
   Version:       $PACKAGE_VERSION
 
+C++ Compiler:    $CXX
+
 Flags:
   DEFS:          $DEFS
   CPPFLAGS:      $CPPFLAGS
@@ -599,6 +625,7 @@ dnl includes too
   Python:        ${PYTHON_INCLUDES}
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LIB}
+  Boost:         ${BOOST_INCLUDES}
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
 
@@ -618,4 +645,3 @@ cat <<EOF
   Now you can type "make" to build BIND 10
 
 EOF
-

+ 1 - 1
src/bin/Makefile.am

@@ -1,4 +1,4 @@
 SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout \
-	usermgr zonemgr tests
+	usermgr zonemgr stats tests recurse
 
 check-recursive: all-recursive

+ 6 - 0
src/bin/auth/Makefile.am

@@ -5,6 +5,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -46,11 +47,16 @@ libasio_link_a_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_GXX
 libasio_link_a_CXXFLAGS += -Wno-unused-parameter
 endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+libasio_link_a_CXXFLAGS += -Wno-error
+endif
 libasio_link_a_CPPFLAGS = $(AM_CPPFLAGS)
 
 BUILT_SOURCES = spec_config.h 
 pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
+b10_auth_SOURCES += query.cc query.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += main.cc

+ 97 - 13
src/bin/auth/asio_link.cc

@@ -64,24 +64,50 @@ IOAddress::toText() const {
     return (asio_address_.to_string());
 }
 
-// Note: this implementation is optimized for the case where this object
-// is created from an ASIO endpoint object in a receiving code path
-// by avoiding to make a copy of the base endpoint.  For TCP it may not be
-// a big deal, but when we receive UDP packets at a high rate, the copy
-// overhead might be significant.
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP connection.
+///
+/// In the current implementation, an object of this class is always
+/// instantiated within the wrapper routines.  Applications are expected to
+/// get access to the object via the abstract base class, \c IOEndpoint.
+/// This design may be changed when we generalize the wrapper interface.
+///
+/// Note: this implementation is optimized for the case where this object
+/// is created from an ASIO endpoint object in a receiving code path
+/// by avoiding to make a copy of the base endpoint.  For TCP it may not be
+/// a big deal, but when we receive UDP packets at a high rate, the copy
+/// overhead might be significant.
 class TCPEndpoint : public IOEndpoint {
 public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    //@{
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The TCP port number of the endpoint.
     TCPEndpoint(const IOAddress& address, const unsigned short port) :
         asio_endpoint_placeholder_(
             new tcp::endpoint(ip::address::from_string(address.toText()),
                               port)),
         asio_endpoint_(*asio_endpoint_placeholder_)
     {}
+
+    /// \brief Constructor from an ASIO TCP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c tcp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the TCP endpoint.
     TCPEndpoint(const tcp::endpoint& asio_endpoint) :
         asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
     {}
-        
+
+    /// \brief The destructor.        
     ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
     virtual IOAddress getAddress() const {
         return (asio_endpoint_.address());
     }
@@ -90,18 +116,41 @@ private:
     const tcp::endpoint& asio_endpoint_;
 };
 
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
 class UDPEndpoint : public IOEndpoint {
 public:
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+    /// \brief Constructor from a pair of address and port.
+    ///
+    /// \param address The IP address of the endpoint.
+    /// \param port The UDP port number of the endpoint.
     UDPEndpoint(const IOAddress& address, const unsigned short port) :
         asio_endpoint_placeholder_(
             new udp::endpoint(ip::address::from_string(address.toText()),
                               port)),
         asio_endpoint_(*asio_endpoint_placeholder_)
     {}
+
+    /// \brief Constructor from an ASIO UDP endpoint.
+    ///
+    /// This constructor is designed to be an efficient wrapper for the
+    /// corresponding ASIO class, \c udp::endpoint.
+    ///
+    /// \param asio_endpoint The ASIO representation of the UDP endpoint.
     UDPEndpoint(const udp::endpoint& asio_endpoint) :
         asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
     {}
+
+    /// \brief The destructor.
     ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+    //@}
+
     virtual IOAddress getAddress() const {
         return (asio_endpoint_.address());
     }
@@ -124,37 +173,74 @@ IOEndpoint::create(const int protocol, const IOAddress& address,
               protocol);
 }
 
+/// \brief The \c TCPSocket class is a concrete derived class of
+/// \c IOSocket that represents a TCP socket.
+///
+/// In the current implementation, an object of this class is always
+/// instantiated within the wrapper routines.  Applications are expected to
+/// get access to the object via the abstract base class, \c IOSocket.
+/// This design may be changed when we generalize the wrapper interface.
 class TCPSocket : public IOSocket {
 private:
     TCPSocket(const TCPSocket& source);
     TCPSocket& operator=(const TCPSocket& source);
 public:
+    /// \brief Constructor from an ASIO TCP socket.
+    ///
+    /// \param socket The ASIO representation of the TCP socket.
     TCPSocket(tcp::socket& socket) : socket_(socket) {}
+
     virtual int getNative() const { return (socket_.native()); }
     virtual int getProtocol() const { return (IPPROTO_TCP); }
 private:
     tcp::socket& socket_;
 };
 
+/// \brief The \c UDPSocket class is a concrete derived class of
+/// \c IOSocket that represents a UDP socket.
+///
+/// Other notes about \c TCPSocket applies to this class, too.
 class UDPSocket : public IOSocket {
 private:
     UDPSocket(const UDPSocket& source);
     UDPSocket& operator=(const UDPSocket& source);
 public:
+    /// \brief Constructor from an ASIO UDP socket.
+    ///
+    /// \param socket The ASIO representation of the UDP socket.
     UDPSocket(udp::socket& socket) : socket_(socket) {}
+
     virtual int getNative() const { return (socket_.native()); }
     virtual int getProtocol() const { return (IPPROTO_UDP); }
 private:
     udp::socket& socket_;
 };
 
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
 class DummySocket : public IOSocket {
 private:
     DummySocket(const DummySocket& source);
     DummySocket& operator=(const DummySocket& source);
 public:
+    /// \brief Constructor from the protocol number.
+    ///
+    /// The protocol must validly identify a standard network protocol.
+    /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+    ///
+    /// \param protocol The network protocol number for the socket.
     DummySocket(const int protocol) : protocol_(protocol) {}
+
+    /// \brief A dummy derived method of \c IOSocket::getNative().
+    ///
+    /// This version of method always returns -1 as the object is not
+    /// associated with a real (native) socket.
     virtual int getNative() const { return (-1); }
+
     virtual int getProtocol() const { return (protocol_); }
 private:
     const int protocol_;
@@ -197,8 +283,8 @@ public:
     void start() {
         // Check for queued configuration commands
         if (auth_server_ != NULL &&
-            auth_server_->configSession()->hasQueuedMsgs()) {
-            auth_server_->configSession()->checkCommand();
+            auth_server_->getConfigSession()->hasQueuedMsgs()) {
+            auth_server_->getConfigSession()->checkCommand();
         }
         async_read(socket_, asio::buffer(data_, TCP_MESSAGE_LENGTHSIZE),
                    boost::bind(&TCPClient::headerRead, this,
@@ -385,8 +471,8 @@ public:
     {
         // Check for queued configuration commands
         if (auth_server_ != NULL &&
-            auth_server_->configSession()->hasQueuedMsgs()) {
-            auth_server_->configSession()->checkCommand();
+            auth_server_->getConfigSession()->hasQueuedMsgs()) {
+            auth_server_->getConfigSession()->checkCommand();
         }
         if (!error && bytes_recvd > 0) {
             const UDPEndpoint remote_endpoint(sender_endpoint_);
@@ -419,9 +505,7 @@ public:
         }
     }
 
-    void sendCompleted(const asio::error_code& error UNUSED_PARAM,
-                       size_t bytes_sent UNUSED_PARAM)
-    {
+    void sendCompleted(const asio::error_code&, size_t) {
         // Even if error occurred there's nothing to do.  Simply handle
         // the next request.
         startReceive();

+ 2 - 2
src/bin/auth/asio_link.h

@@ -88,7 +88,7 @@ class AuthSrv;
 /// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
 
 namespace asio_link {
-struct IOServiceImpl;
+class IOServiceImpl;
 
 /// \brief An exception that is thrown if an error occurs within the IO
 /// module.  This is mainly intended to be a wrapper exception class for
@@ -132,7 +132,7 @@ public:
     /// This constructor never throws an exception.
     ///
     /// \param asio_address The ASIO \c ip::address to be converted.
-    IOAddress(const asio::ip::address& asio_adress);
+    IOAddress(const asio::ip::address& asio_address);
     //@}
 
     /// \brief Convert the address to a string.

+ 24 - 25
src/bin/auth/auth_srv.cc

@@ -77,7 +77,7 @@ public:
                             MessageRenderer& response_renderer);
     bool processAxfrQuery(const IOMessage& io_message, Message& message,
                             MessageRenderer& response_renderer);
-    bool processNotify(const IOMessage& io_message, Message& message, 
+    bool processNotify(const IOMessage& io_message, Message& message,
                             MessageRenderer& response_renderer);
     std::string db_file_;
     ModuleCCSession* config_session_;
@@ -152,8 +152,8 @@ makeErrorMessage(Message& message, MessageRenderer& renderer,
     // XXX: with the current implementation, it's not easy to set EDNS0
     // depending on whether the query had it.  So we'll simply omit it.
     const qid_t qid = message.getQid();
-    const bool rd = message.getHeaderFlag(MessageFlag::RD());
-    const bool cd = message.getHeaderFlag(MessageFlag::CD());
+    const bool rd = message.getHeaderFlag(Message::HEADERFLAG_RD);
+    const bool cd = message.getHeaderFlag(Message::HEADERFLAG_CD);
     const Opcode& opcode = message.getOpcode();
     vector<QuestionPtr> questions;
 
@@ -166,12 +166,12 @@ makeErrorMessage(Message& message, MessageRenderer& renderer,
     message.clear(Message::RENDER);
     message.setQid(qid);
     message.setOpcode(opcode);
-    message.setHeaderFlag(MessageFlag::QR());
+    message.setHeaderFlag(Message::HEADERFLAG_QR);
     if (rd) {
-        message.setHeaderFlag(MessageFlag::RD());
+        message.setHeaderFlag(Message::HEADERFLAG_RD);
     }
     if (cd) {
-        message.setHeaderFlag(MessageFlag::CD());
+        message.setHeaderFlag(Message::HEADERFLAG_CD);
     }
     for_each(questions.begin(), questions.end(), QuestionInserter(&message));
     message.setRcode(rcode);
@@ -215,7 +215,7 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
 }
 
 ModuleCCSession*
-AuthSrv::configSession() const {
+AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
@@ -231,7 +231,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         message.parseHeader(request_buffer);
 
         // Ignore all responses.
-        if (message.getHeaderFlag(MessageFlag::QR())) {
+        if (message.getHeaderFlag(Message::HEADERFLAG_QR)) {
             if (impl_->verbose_mode_) {
                 cerr << "[b10-auth] received unexpected response, ignoring"
                      << endl;
@@ -279,7 +279,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         return (true);
     }
 
-    if (message.getRRCount(Section::QUESTION()) != 1) {
+    if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
         makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
                          impl_->verbose_mode_);
         return (true);
@@ -307,10 +307,10 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     ConstEDNSPtr remote_edns = message.getEDNS();
     const bool dnssec_ok = remote_edns && remote_edns->getDNSSECAwareness();
     const uint16_t remote_bufsize = remote_edns ? remote_edns->getUDPSize() :
-        Message::DEFAULT_MAX_UDPSIZE; 
+        Message::DEFAULT_MAX_UDPSIZE;
 
     message.makeResponse();
-    message.setHeaderFlag(MessageFlag::AA());
+    message.setHeaderFlag(Message::HEADERFLAG_AA);
     message.setRcode(Rcode::NOERROR());
 
     if (remote_edns) {
@@ -360,8 +360,10 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
     }
 
     try {
-        xfrout_client_.connect();
-        xfrout_connected_ = true;
+        if (!xfrout_connected_) {
+            xfrout_client_.connect();
+            xfrout_connected_ = true;
+        }
         xfrout_client_.sendXfroutRequestInfo(
             io_message.getSocket().getNative(),
             io_message.getData(),
@@ -375,7 +377,7 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
             xfrout_client_.disconnect();
             xfrout_connected_ = false;
         }
-        
+
         if (verbose_mode_) {
             cerr << "[b10-auth] Error in handling XFR request: " << err.what()
                  << endl;
@@ -385,22 +387,19 @@ AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
         return (true);
     }
 
-    xfrout_client_.disconnect();
-    xfrout_connected_ = false;
-
     return (false);
 }
 
 bool
-AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message, 
-                           MessageRenderer& response_renderer) 
+AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
+                           MessageRenderer& response_renderer)
 {
     // The incoming notify must contain exactly one question for SOA of the
     // zone name.
-    if (message.getRRCount(Section::QUESTION()) != 1) {
+    if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
         if (verbose_mode_) {
                 cerr << "[b10-auth] invalid number of questions in notify: "
-                     << message.getRRCount(Section::QUESTION()) << endl;
+                     << message.getRRCount(Message::SECTION_QUESTION) << endl;
         }
         makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
                          verbose_mode_);
@@ -435,7 +434,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         }
         return (false);
     }
-    
+
     const string remote_ip_address =
         io_message.getRemoteEndpoint().getAddress().toText();
     static const string command_template_start =
@@ -446,7 +445,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
 
     try {
         ConstElementPtr notify_command = Element::fromJSON(
-                command_template_start + question->getName().toText() + 
+                command_template_start + question->getName().toText() +
                 command_template_master + remote_ip_address +
                 command_template_rrclass + question->getClass().toText() +
                 command_template_end);
@@ -460,7 +459,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         if (rcode != 0) {
             if (verbose_mode_) {
                 cerr << "[b10-auth] failed to notify Zonemgr: "
-                     << parsed_answer->str() << endl; 
+                     << parsed_answer->str() << endl;
             }
             return (false);
         }
@@ -472,7 +471,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     }
 
     message.makeResponse();
-    message.setHeaderFlag(MessageFlag::AA());
+    message.setHeaderFlag(Message::HEADERFLAG_AA);
     message.setRcode(Rcode::NOERROR());
     message.toWire(response_renderer);
     return (true);

+ 96 - 3
src/bin/auth/auth_srv.h

@@ -38,8 +38,30 @@ namespace asio_link {
 class IOMessage;
 }
 
+/// \brief The implementation class for the \c AuthSrv class using the pimpl
+/// idiom.
 class AuthSrvImpl;
 
+/// \brief The authoritative nameserver class.
+///
+/// \c AuthSrv is a concrete class that implements authoritative DNS server
+/// protocol processing.
+/// An \c AuthSrv object is primarily responsible for handling incoming DNS
+/// requests: It parses the request and dispatches subsequent processing to
+/// the corresponding module (which may be an internal library or a separate
+/// process) depending on the request type.  For normal queries, the
+/// \c AuthSrv object searches configured data sources for the answer to the
+/// query, and builds a response containing the answer.
+///
+/// This class uses the "pimpl" idiom, and hides detailed implementation
+/// through the \c impl_ pointer (which points to an instance of the
+/// \c AuthSrvImpl class).  An \c AuthSrv object is supposed to exist for quite
+/// a long period, and only a few \c AuthSrv objects will be created (in fact,
+/// in this current implementation there will only be one object), so the
+/// construction overhead of this approach should be acceptable.
+///
+/// The design of this class is still in flux.  It's quite likely to change
+/// in future versions.
 class AuthSrv {
     ///
     /// \name Constructors, Assignment Operator and Destructor.
@@ -62,15 +84,82 @@ public:
             isc::xfr::AbstractXfroutClient& xfrout_client);
     ~AuthSrv();
     //@}
-    /// \return \c true if the \message contains a response to be returned;
+    /// \return \c true if the \a message contains a response to be returned;
     /// otherwise \c false.
     bool processMessage(const asio_link::IOMessage& io_message,
                         isc::dns::Message& message,
                         isc::dns::MessageRenderer& response_renderer);
-    void setVerbose(bool on);
+
+    /// \brief Enable or disable verbose logging.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param on \c true to enable verbose logging; \c false to disable
+    /// verbose logging.
+    void setVerbose(const bool on);
+
+    /// \brief Returns the logging verbosity of the \c AuthSrv object.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return \c true if verbose logging is enabled; otherwise \c false.
     bool getVerbose() const;
+
+    /// \brief Updates the data source for the \c AuthSrv object.
+    ///
+    /// This method installs or replaces the data source that the \c AuthSrv
+    /// object refers to for query processing.
+    /// Although the method name is generic, the only thing it does is to
+    /// update the data source information.
+    /// If there is a data source installed, it will be replaced with the
+    /// new one.
+    ///
+    /// In the current implementation, the SQLite data source is assumed.
+    /// The \c config parameter will simply be passed to the initialization
+    /// routine of the \c Sqlite3DataSrc class.
+    ///
+    /// On success this method returns a data \c Element (in the form of a
+    /// pointer like object) indicating the successful result,
+    /// i.e., {"result": [0]}.
+    /// Otherwise, it returns a data \c Element explaining the error:
+    /// {"result": [1, <error-description>]}.
+    ///
+    /// This method is mostly exception free (error conditions are represented
+    /// via the return value).  But it may still throw a standard exception
+    /// if memory allocation fails inside the method.
+    /// When a standard exception is thrown or an implementation specific
+    /// exception is triggered and caught internally, this function provides
+    /// the strong exception guarantee: Unless everything succeeds, currently
+    /// installed data source (if any) won't be replaced.
+    ///
+    /// \param config An immutable pointer-like object to a data \c Element,
+    /// possibly containing the data source information to be used.
+    /// \return An immutable pointer-like object to a data \c Element
+    /// containing the result of the update operation.
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
-    isc::config::ModuleCCSession* configSession() const;
+
+    /// \brief Returns the command and configuration session for the
+    /// \c AuthSrv.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A pointer to \c ModuleCCSession object stored in the
+    /// \c AuthSrv object.  In this implementation it could be NULL.
+    isc::config::ModuleCCSession* getConfigSession() const;
+
+    /// \brief Set the command and configuration session for the \c AuthSrv.
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and session
+    /// frameworks, at which point the session will probably be passed on
+    /// construction of the server.
+    /// In the current implementation, this method is expected to be called
+    /// exactly once as part of initialization.  If this method is called
+    /// multiple times, previously specified session is silently overridden.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param config_session A pointer to \c ModuleCCSession object to receive
+    /// control commands and configuration updates.
     void setConfigSession(isc::config::ModuleCCSession* config_session);
 
     /// \brief Set or update the size (number of slots) of hot spot cache.
@@ -93,6 +182,8 @@ public:
     /// \return The current number of cache slots.
     size_t getCacheSlots() const;
 
+    /// \brief Set the communication session with a separate process for
+    /// outgoing zone transfers.
     ///
     /// Note: this interface is tentative.  We'll revisit the ASIO and session
     /// frameworks, at which point the session will probably be passed on
@@ -105,8 +196,10 @@ public:
     /// Ownership isn't transferred: the caller is responsible for keeping
     /// this object to be valid while the server object is working and for
     /// disconnecting the session and destroying the object when the server
+    /// is shutdown.
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
+
 private:
     AuthSrvImpl* impl_;
 };

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

@@ -1,5 +1,6 @@
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 

+ 1 - 0
src/bin/auth/benchmarks/query_bench.cc

@@ -38,6 +38,7 @@
 
 using namespace std;
 using namespace isc;
+using namespace isc::data;
 using namespace isc::dns;
 using namespace isc::xfr;
 using namespace isc::bench;

+ 23 - 22
src/lib/dns/tests/tsig_unittest.cc

@@ -12,30 +12,31 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $
+#include <dns/message.h>
+#include <dns/rcode.h>
 
-#include <gtest/gtest.h>
+#include <datasrc/zonetable.h>
 
-#include <dns/tsig.h>
+#include <auth/query.h>
 
-#include <dns/tests/unittest_util.h>
-
-using isc::UnitTestUtil;
-using namespace std;
 using namespace isc::dns;
-
-namespace {
-class TsigTest : public ::testing::Test {
-protected:
-    TsigTest() {}
-};
-
-// simple creation test to get the testing ball rolling
-TEST_F(TsigTest, creates) {
-    Tsig tsig(Name("example.com"), Tsig::HMACMD5, "someRandomData");
-    EXPECT_TRUE(1);
+using namespace isc::datasrc;
+
+namespace isc {
+namespace auth {
+void
+Query::process() const {
+    const ZoneTable::FindResult result = zone_table_.find(qname_);
+
+    if (result.code != ZoneTable::SUCCESS &&
+        result.code != ZoneTable::PARTIALMATCH) {
+        response_.setRcode(Rcode::SERVFAIL());
+        return;
+    }
+
+    // Right now we have no code to search the zone, so we simply return
+    // NXDOMAIN for tests.
+    response_.setRcode(Rcode::NXDOMAIN());
+}
+}
 }
-
-} // end namespace
-
-

+ 119 - 0
src/bin/auth/query.h

@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+namespace isc {
+namespace dns {
+class Message;
+class Name;
+class RRType;
+}
+
+namespace datasrc {
+class ZoneTable;
+}
+
+namespace auth {
+
+/// The \c Query class represents a standard DNS query that encapsulates
+/// processing logic to answer the query.
+///
+/// Many of the design details for this class are still in flux.
+/// We'll revisit and update them as we add more functionality, for example:
+/// - zone_table parameter of the constructor.  This will eventually be
+///   replaced with a generic DataSrc object, or perhaps a notion of "view".
+/// - as a related point, we may have to pass the RR class of the query.
+///   in the initial implementation the RR class is an attribute of zone
+///   table and omitted.  It's not clear if this assumption holds with
+///   generic data sources.  On the other hand, it will help keep
+///   implementation simpler, and we might rather want to modify the design
+///   of the data source on this point.
+/// - return value of process().  rather than or in addition to setting the
+///   Rcode, we might use it as a return value of \c process().
+/// - we'll have to be able to specify whether DNSSEC is requested.
+///   It's an open question whether it should be in the constructor or via a
+///   separate attribute setter.
+/// - likewise, we'll eventually need to do per zone access control, for which
+///   we need querier's information such as its IP address.
+/// - zone_table (or DataSrc eventually) and response may better be parameters
+///   to process() instead of the constructor.
+///
+/// <b>Note:</b> The class name is intentionally the same as the one used in
+/// the datasrc library.  This is because the plan is to eventually merge
+/// the two classes.  We could give it a different name such as "AuthQuery"
+/// to avoid possible ambiguity, but it may sound redundant in that it's
+/// obvious that this class is for authoritative queries.
+/// Since the interfaces are very different for now and it's less
+/// likely to misuse one of the classes instead of the other
+/// accidentally, and since it's considered a temporary development state,
+/// we keep this name at the moment.
+class Query {
+public:
+    /// Constructor from query parameters.
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param zone_table The zone table wherein the answer to the query is
+    /// to be found.
+    /// \param qname The query name
+    /// \param qtype The RR type of the query
+    /// \param response The response message to store the answer to the query.
+    Query(const isc::datasrc::ZoneTable& zone_table,
+          const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+          isc::dns::Message& response) :
+        zone_table_(zone_table), qname_(qname), qtype_(qtype),
+        response_(response)
+    {}
+
+    /// Process the query.
+    ///
+    /// This method first identifies the zone that best matches the query
+    /// name (and in some cases RR type when the search is dependent on the
+    /// type) and then searches the zone for an entry that best matches the
+    /// query name.
+    /// It then updates the response message accordingly; for example, a
+    /// successful search would result in adding a corresponding RRset to
+    /// the answer section of the response.
+    ///
+    /// If no matching zone is found in the zone table, the RCODE of
+    /// SERVFAIL will be set in the response.
+    /// <b>Note:</b> this is different from the error code that BIND 9 returns
+    /// by default when it's configured as an authoritative-only server (and
+    /// from the behavior of the BIND 10 datasrc library, which was implemented
+    /// to be compatible with BIND 9).
+    /// The difference comes from the fact that BIND 9 returns REFUSED as a
+    /// result of access control check on the use of its cache.
+    /// Since BIND 10's authoritative server doesn't have the notion of cache
+    /// by design, it doesn't make sense to return REFUSED.  On the other hand,
+    /// providing compatible behavior may have its own benefit, so this point
+    /// should be revisited later.
+    ///
+    /// Right now this method never throws an exception, but it may in a
+    /// future version.
+    void process() const;
+
+private:
+    const isc::datasrc::ZoneTable& zone_table_;
+    const isc::dns::Name& qname_;
+    const isc::dns::RRType& qtype_;
+    isc::dns::Message& response_;
+};
+
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -3,8 +3,9 @@ SUBDIRS = testdata .
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(top_builddir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/auth/tests/testdata\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -20,8 +21,10 @@ TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
+run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
+run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += asio_link_unittest.cc
 run_unittests_SOURCES += run_unittests.cc

+ 86 - 90
src/bin/auth/tests/auth_srv_unittest.cc

@@ -97,7 +97,7 @@ private:
         virtual void startRead(boost::function<void()> read_callback);
         virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
         virtual bool hasQueuedMsgs() const;
-        virtual void setTimeout(size_t timeout UNUSED_PARAM) {};
+        virtual void setTimeout(size_t) {}
         virtual size_t getTimeout() const { return 0; };
 
         void setMessage(ConstElementPtr msg) { msg_ = msg; }
@@ -156,30 +156,25 @@ protected:
 };
 
 void
-AuthSrvTest::MockSession::establish(const char* socket_file UNUSED_PARAM) {}
+AuthSrvTest::MockSession::establish(const char*) {}
 
 void
 AuthSrvTest::MockSession::disconnect() {}
 
 void
-AuthSrvTest::MockSession::subscribe(string group UNUSED_PARAM,
-                                    string instance UNUSED_PARAM)
+AuthSrvTest::MockSession::subscribe(string, string)
 {}
 
 void
-AuthSrvTest::MockSession::unsubscribe(string group UNUSED_PARAM,
-                                      string instance UNUSED_PARAM)
+AuthSrvTest::MockSession::unsubscribe(string, string)
 {}
 
 void
-AuthSrvTest::MockSession::startRead(
-    boost::function<void()> read_callback UNUSED_PARAM)
+AuthSrvTest::MockSession::startRead(boost::function<void()>)
 {}
 
 int
-AuthSrvTest::MockSession::reply(ConstElementPtr envelope UNUSED_PARAM,
-                                ConstElementPtr newmsg UNUSED_PARAM)
-{
+AuthSrvTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
     return (-1);
 }
 
@@ -190,8 +185,7 @@ AuthSrvTest::MockSession::hasQueuedMsgs() const {
 
 int
 AuthSrvTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
-                                        string instance UNUSED_PARAM,
-                                        string to UNUSED_PARAM)
+                                        string, string)
 {
     if (!send_ok_) {
         isc_throw(XfroutError, "mock session send is disabled for test");
@@ -203,10 +197,8 @@ AuthSrvTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
 }
 
 bool
-AuthSrvTest::MockSession::group_recvmsg(ConstElementPtr& envelope UNUSED_PARAM,
-                                        ConstElementPtr& msg,
-                                        bool nonblock UNUSED_PARAM,
-                                        int seq UNUSED_PARAM)
+AuthSrvTest::MockSession::group_recvmsg(ConstElementPtr&,
+                                        ConstElementPtr& msg, bool, int)
 {
     if (!receive_ok_) {
         isc_throw(XfroutError, "mock session receive is disabled for test");
@@ -234,10 +226,9 @@ AuthSrvTest::MockXfroutClient::disconnect() {
 }
 
 int
-AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(
-    const int tcp_sock UNUSED_PARAM,
-    const void* msg_data UNUSED_PARAM,
-    const uint16_t msg_len UNUSED_PARAM)
+AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(const int,
+                                                     const void*,
+                                                     const uint16_t)
 {
     if (!send_ok_) {
         isc_throw(XfroutError, "xfrout connection send is disabled for test");
@@ -321,18 +312,25 @@ headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
     EXPECT_EQ(qid, message.getQid());
     EXPECT_EQ(rcode, message.getRcode());
     EXPECT_EQ(opcodeval, message.getOpcode().getCode());
-    EXPECT_EQ((flags & QR_FLAG) != 0, message.getHeaderFlag(MessageFlag::QR()));
-    EXPECT_EQ((flags & AA_FLAG) != 0, message.getHeaderFlag(MessageFlag::AA()));
-    EXPECT_EQ((flags & TC_FLAG) != 0, message.getHeaderFlag(MessageFlag::TC()));
-    EXPECT_EQ((flags & RA_FLAG) != 0, message.getHeaderFlag(MessageFlag::RA()));
-    EXPECT_EQ((flags & RD_FLAG) != 0, message.getHeaderFlag(MessageFlag::RD()));
-    EXPECT_EQ((flags & AD_FLAG) != 0, message.getHeaderFlag(MessageFlag::AD()));
-    EXPECT_EQ((flags & CD_FLAG) != 0, message.getHeaderFlag(MessageFlag::CD()));
-
-    EXPECT_EQ(qdcount, message.getRRCount(Section::QUESTION()));
-    EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
-    EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
-    EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
+    EXPECT_EQ((flags & QR_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_QR));
+    EXPECT_EQ((flags & AA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ((flags & TC_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_TC));
+    EXPECT_EQ((flags & RA_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RA));
+    EXPECT_EQ((flags & RD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_RD));
+    EXPECT_EQ((flags & AD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_AD));
+    EXPECT_EQ((flags & CD_FLAG) != 0,
+              message.getHeaderFlag(Message::HEADERFLAG_CD));
+
+    EXPECT_EQ(qdcount, message.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
+    EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
+    EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
 }
 
 // Unsupported requests.  Should result in NOTIMP.
@@ -348,8 +346,8 @@ TEST_F(AuthSrvTest, unsupportedRequest) {
         data[2] = ((i << 3) & 0xff);
 
         parse_message.clear(Message::PARSE);
-        EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                              response_renderer));
+        EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                          response_renderer));
         headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
                     0, 0, 0, 0);
     }
@@ -367,8 +365,8 @@ TEST_F(AuthSrvTest, verbose) {
 // Multiple questions.  Should result in FORMERR.
 TEST_F(AuthSrvTest, multiQuestion) {
     createDataFromFile("multiquestion_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                 QR_FLAG, 2, 0, 0, 0);
 
@@ -388,8 +386,8 @@ TEST_F(AuthSrvTest, multiQuestion) {
 // dropped.
 TEST_F(AuthSrvTest, shortMessage) {
     createDataFromFile("shortmessage_fromWire");
-    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
-                                           response_renderer));
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
@@ -397,26 +395,26 @@ TEST_F(AuthSrvTest, shortMessage) {
 TEST_F(AuthSrvTest, response) {
     // A valid (although unusual) response
     createDataFromFile("simpleresponse_fromWire.wire");
-    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
-                                           response_renderer));
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
 
     // A response with a broken question section.  must be dropped rather than
     // returning FORMERR.
     createDataFromFile("shortresponse_fromWire");
-    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
-                                           response_renderer));
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
 
     // A response to iquery.  must be dropped rather than returning NOTIMP.
     createDataFromFile("iqueryresponse_fromWire.wire");
-    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
-                                           response_renderer));
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
 }
 
 // Query with a broken question
 TEST_F(AuthSrvTest, shortQuestion) {
     createDataFromFile("shortquestion_fromWire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     // Since the query's question is broken, the question section of the
     // response should be empty.
     headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
@@ -426,8 +424,8 @@ TEST_F(AuthSrvTest, shortQuestion) {
 // Query with a broken answer section
 TEST_F(AuthSrvTest, shortAnswer) {
     createDataFromFile("shortanswer_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
 
     // This is a bogus query, but question section is valid.  So the response
     // should copy the question section.
@@ -445,8 +443,8 @@ TEST_F(AuthSrvTest, shortAnswer) {
 // Query with unsupported version of EDNS.
 TEST_F(AuthSrvTest, ednsBadVers) {
     createDataFromFile("queryBadEDNS_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
 
     // The response must have an EDNS OPT RR in the additional section, but
     // it will be added automatically at the render time.
@@ -468,8 +466,8 @@ TEST_F(AuthSrvTest, AXFROverUDP) {
     // AXFR over UDP is invalid and should result in FORMERR.
     createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
                         RRType::AXFR(), IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                 QR_FLAG, 1, 0, 0, 0);
 }
@@ -480,9 +478,9 @@ TEST_F(AuthSrvTest, AXFRSuccess) {
                         RRType::AXFR(), IPPROTO_TCP);
     // On success, the AXFR query has been passed to a separate process,
     // so we shouldn't have to respond.
-    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
-                                           response_renderer));
-    EXPECT_FALSE(xfrout.isConnected());
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+    EXPECT_TRUE(xfrout.isConnected());
 }
 
 TEST_F(AuthSrvTest, AXFRConnectFail) {
@@ -494,8 +492,6 @@ TEST_F(AuthSrvTest, AXFRConnectFail) {
                                       response_renderer));
     headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
-    // For a shot term workaround with xfrout we currently close the connection
-    // for each AXFR attempt
     EXPECT_FALSE(xfrout.isConnected());
 }
 
@@ -505,7 +501,7 @@ TEST_F(AuthSrvTest, AXFRSendFail) {
     createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
                         RRType::AXFR(), IPPROTO_TCP);
     server.processMessage(*io_message, parse_message, response_renderer);
-    EXPECT_FALSE(xfrout.isConnected()); // see above
+    EXPECT_TRUE(xfrout.isConnected());
 
     xfrout.disableSend();
     parse_message.clear(Message::PARSE);
@@ -540,10 +536,10 @@ TEST_F(AuthSrvTest, AXFRDisconnectFail) {
 TEST_F(AuthSrvTest, notify) {
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
 
     // An internal command message should have been created and sent to an
     // external module.  Check them.
@@ -572,10 +568,10 @@ TEST_F(AuthSrvTest, notifyForCHClass) {
     // Same as the previous test, but for the CH RRClass.
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::CH(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
 
     // Other conditions should be the same, so simply confirm the RR class is
     // set correctly.
@@ -588,12 +584,12 @@ TEST_F(AuthSrvTest, notifyEmptyQuestion) {
     request_message.clear(Message::RENDER);
     request_message.setOpcode(Opcode::NOTIFY());
     request_message.setRcode(Rcode::NOERROR());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     request_message.setQid(default_qid);
     request_message.toWire(request_renderer);
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
@@ -604,10 +600,10 @@ TEST_F(AuthSrvTest, notifyMultiQuestions) {
     // add one more SOA question
     request_message.addQuestion(Question(Name("example.com"), RRClass::IN(),
                                          RRType::SOA()));
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 2, 0, 0, 0);
 }
@@ -615,10 +611,10 @@ TEST_F(AuthSrvTest, notifyMultiQuestions) {
 TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::NS());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
 }
@@ -627,8 +623,8 @@ TEST_F(AuthSrvTest, notifyWithoutAA) {
     // implicitly leave the AA bit off.  our implementation will accept it.
     createRequestPacket(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
 }
@@ -636,11 +632,11 @@ TEST_F(AuthSrvTest, notifyWithoutAA) {
 TEST_F(AuthSrvTest, notifyWithErrorRcode) {
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     request_message.setRcode(Rcode::SERVFAIL());
     createRequestPacket(IPPROTO_UDP);
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
 }
@@ -650,7 +646,7 @@ TEST_F(AuthSrvTest, notifyWithoutSession) {
 
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
 
     // we simply ignore the notify and let it be resent if an internal error
@@ -664,7 +660,7 @@ TEST_F(AuthSrvTest, notifySendFail) {
 
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
 
     EXPECT_FALSE(server.processMessage(*io_message, parse_message,
@@ -676,7 +672,7 @@ TEST_F(AuthSrvTest, notifyReceiveFail) {
 
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
     EXPECT_FALSE(server.processMessage(*io_message, parse_message,
                                        response_renderer));
@@ -687,7 +683,7 @@ TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
 
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
     EXPECT_FALSE(server.processMessage(*io_message, parse_message,
                                        response_renderer));
@@ -699,7 +695,7 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
 
     createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                         RRType::SOA());
-    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(IPPROTO_UDP);
     EXPECT_FALSE(server.processMessage(*io_message, parse_message,
                                        response_renderer));
@@ -727,8 +723,8 @@ TEST_F(AuthSrvTest, updateConfig) {
     // response should have the AA flag on, and have an RR in each answer
     // and authority section.
     createDataFromFile("examplequery_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
@@ -741,8 +737,8 @@ TEST_F(AuthSrvTest, datasourceFail) {
     // in a SERVFAIL response, and the answer and authority sections should
     // be empty.
     createDataFromFile("badExampleQuery_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
@@ -756,8 +752,8 @@ TEST_F(AuthSrvTest, updateConfigFail) {
 
     // The original data source should still exist.
     createDataFromFile("examplequery_fromWire.wire");
-    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
-                                          response_renderer));
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }

+ 70 - 0
src/bin/auth/tests/query_unittest.cc

@@ -0,0 +1,70 @@
+// 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 <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+
+#include <datasrc/zonetable.h>
+
+#include <auth/query.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::auth;
+
+namespace {
+class QueryTest : public ::testing::Test {
+protected:
+    QueryTest() :
+        qname(Name("www.example.com")), qclass(RRClass::IN()),
+        qtype(RRType::A()), response(Message::RENDER),
+        query(zone_table, qname, qtype, response)
+    {
+        response.setRcode(Rcode::NOERROR());
+    }
+    ZoneTable zone_table;
+    const Name qname;
+    const RRClass qclass;
+    const RRType qtype;
+    Message response;
+    Query query;
+};
+
+TEST_F(QueryTest, noZone) {
+    // There's no zone in the zone table.  So the response should have
+    // SERVFAIL.
+    query.process();
+    EXPECT_EQ(Rcode::SERVFAIL(), response.getRcode());
+}
+
+TEST_F(QueryTest, matchZone) {
+    // add a matching zone.  since the zone is empty right now, the response
+    // should have NXDOMAIN.
+    zone_table.add(ZonePtr(new MemoryZone(qclass, Name("example.com"))));
+    query.process();
+    EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
+}
+
+TEST_F(QueryTest, noMatchZone) {
+    // there's a zone in the table but it doesn't match the qname.  should
+    // result in SERVFAIL.
+    zone_table.add(ZonePtr(new MemoryZone(qclass, Name("example.org"))));
+    query.process();
+    EXPECT_EQ(Rcode::SERVFAIL(), response.getRcode());
+}
+}

+ 1 - 2
src/bin/auth/tests/run_unittests.cc

@@ -19,8 +19,7 @@
 #include <dns/tests/unittest_util.h>
 
 int
-main(int argc, char* argv[])
-{
+main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
     isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);

+ 322 - 219
src/bin/bind10/bind10.py.in

@@ -15,7 +15,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-"""\
+"""
 This file implements the Boss of Bind (BoB, or bob) program.
 
 Its purpose is to start up the BIND 10 system, and then manage the
@@ -63,15 +63,19 @@ import pwd
 import posix
 
 import isc.cc
-import isc.utils.process
+import isc.util.process
+import isc.net.parse
 
 # Assign this process some longer name
-isc.utils.process.rename(sys.argv[0])
+isc.util.process.rename(sys.argv[0])
 
 # This is the version that gets displayed to the user.
 # The VERSION string consists of the module name, the module version
 # number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20100916 (BIND 10 @PACKAGE_VERSION@)"
+VERSION = "bind10 20101129 (BIND 10 @PACKAGE_VERSION@)"
+
+# This is for bind10.boottime of stats module
+_BASETIME = time.gmtime()
 
 class RestartSchedule:
     """
@@ -137,9 +141,14 @@ class ProcessInfo:
         self.username = username
         self._spawn()
 
-    def _setuid(self):
+    def _preexec_work(self):
         """Function used before running a program that needs to run as a
         different user."""
+        # First, put us into a separate process group so we don't get
+        # SIGINT signals on Ctrl-C (the boss will shut everthing down by
+        # other means).
+        os.setpgrp()
+        # Second, set the user ID if one has been specified
         if self.uid is not None:
             try:
                 posix.setuid(self.uid)
@@ -173,169 +182,240 @@ class ProcessInfo:
                                         stderr=spawn_stderr,
                                         close_fds=True,
                                         env=spawn_env,
-                                        preexec_fn=self._setuid)
+                                        preexec_fn=self._preexec_work)
         self.pid = self.process.pid
         self.restart_schedule.set_run_start_time()
 
     def respawn(self):
         self._spawn()
 
-class IPAddr:
-    """Stores an IPv4 or IPv6 address."""
-    family = None
-    addr = None
-
-    def __init__(self, addr):
-        try:
-            a = socket.inet_pton(socket.AF_INET, addr)
-            self.family = socket.AF_INET
-            self.addr = a
-            return
-        except:
-            pass
-
-        try:
-            a = socket.inet_pton(socket.AF_INET6, addr)
-            self.family = socket.AF_INET6
-            self.addr = a
-            return
-        except Exception as e:
-            raise e
-    
-    def __str__(self):
-        return socket.inet_ntop(self.family, self.addr)
+class CChannelConnectError(Exception): pass
 
 class BoB:
     """Boss of BIND class."""
     
-    def __init__(self, msgq_socket_file=None, auth_port=5300, address='',
+    def __init__(self, msgq_socket_file=None, auth_port=5300, address=None,
                  nocache=False, verbose=False, setuid=None, username=None):
-        """Initialize the Boss of BIND. This is a singleton (only one
-        can run).
+        """
+            Initialize the Boss of BIND. This is a singleton (only one can run).
         
-        The msgq_socket_file specifies the UNIX domain socket file
-        that the msgq process listens on.
-        If verbose is True, then the boss reports what it is doing.
+            The msgq_socket_file specifies the UNIX domain socket file that the
+            msgq process listens on.  If verbose is True, then the boss reports
+            what it is doing.
         """
-        self.verbose = verbose
-        self.msgq_socket_file = msgq_socket_file
+        self.address = address
         self.auth_port = auth_port
-        self.address = None
-        if address:
-            self.address = IPAddr(address)
         self.cc_session = None
         self.ccs = None
-        self.processes = {}
+        self.cfg_start_auth = True
+        self.cfg_start_recurse = False
+        self.curproc = None
         self.dead_processes = {}
+        self.msgq_socket_file = msgq_socket_file
+        self.nocache = nocache
+        self.processes = {}
         self.runnable = False
         self.uid = setuid
         self.username = username
-        self.nocache = nocache
+        self.verbose = verbose
 
     def config_handler(self, new_config):
         if self.verbose:
-            sys.stdout.write("[bind10] handling new config:\n")
-            sys.stdout.write(new_config + "\n")
+            sys.stdout.write("[bind10] Handling new configuration: " +
+                str(new_config) + "\n")
         answer = isc.config.ccsession.create_answer(0)
         return answer
         # TODO
 
     def command_handler(self, command, args):
         if self.verbose:
-            sys.stdout.write("[bind10] Boss got command:\n")
-            sys.stdout.write(command + "\n")
+            sys.stdout.write("[bind10] Boss got command: " + command + "\n")
         answer = isc.config.ccsession.create_answer(1, "command not implemented")
         if type(command) != str:
             answer = isc.config.ccsession.create_answer(1, "bad command")
         else:
-            cmd = command
-            if cmd == "shutdown":
-                sys.stdout.write("[bind10] got shutdown command\n")
+            if command == "shutdown":
                 self.runnable = False
                 answer = isc.config.ccsession.create_answer(0)
             else:
                 answer = isc.config.ccsession.create_answer(1, 
                                                             "Unknown command")
         return answer
-    
-    def startup(self):
-        """Start the BoB instance.
- 
-        Returns None if successful, otherwise an string describing the
-        problem.
+
+    def kill_started_processes(self):
+        """
+            Called as part of the exception handling when a process fails to
+            start, this runs through the list of started processes, killing
+            each one.  It then clears that list.
         """
-        # try to connect to the c-channel daemon, 
-        # to see if it is already running
-        c_channel_env = {}
-        if self.msgq_socket_file is not None:
-             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file 
         if self.verbose:
-            sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
-        # try to connect, and if we can't wait a short while
-        try:
-            self.cc_session = isc.cc.Session(self.msgq_socket_file)
-            return "b10-msgq already running, or socket file not cleaned , cannot start"
-        except isc.cc.session.SessionError:
-            # this is the case we want, where the msgq is not running
-            pass
+            sys.stdout.write("[bind10] killing started processes:\n")
+
+        for pid in self.processes:
+            if self.verbose:
+                sys.stdout.write("[bind10] - %s\n" % self.processes[pid].name)
+            self.processes[pid].process.kill()
+        self.processes = {}
+
+    def read_bind10_config(self):
+        """
+            Reads the parameters associated with the BoB module itself.
 
-        # start the c-channel daemon
+            At present these are the components to start although arguably this
+            information should be in the configuration for the appropriate
+            module itself. (However, this would cause difficulty in the case of
+            xfrin/xfrout and zone manager as we don't need to start those if we
+            are not running the authoritative server.)
+        """
         if self.verbose:
-            if self.msgq_socket_file:
-                sys.stdout.write("[bind10] Starting b10-msgq\n")
-        try:
-            c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
-                                    True, not self.verbose, uid=self.uid,
-                                    username=self.username)
-        except Exception as e:
-            return "Unable to start b10-msgq; " + str(e)
-        self.processes[c_channel.pid] = c_channel
+            sys.stdout.write("[bind10] Reading Boss configuration:\n")
+
+        config_data = self.ccs.get_full_config()
+        self.cfg_start_auth = config_data.get("start_auth")
+        self.cfg_start_recurse = config_data.get("start_recurse")
+
+        if self.verbose:
+            sys.stdout.write("[bind10] - start_auth: %s\n" %
+                str(self.cfg_start_auth))
+            sys.stdout.write("[bind10] - start_recurse: %s\n" %
+                str(self.cfg_start_recurse))
+
+    def log_starting(self, process, port = None, address = None):
+        """
+            A convenience function to output a "Starting xxx" message if the
+            verbose option is set.  Putting this into a separate method ensures
+            that the output form is consistent across all processes.
+
+            The process name (passed as the first argument) is put into
+            self.curproc, and is used to indicate which process failed to
+            start if there is an error (and is used in the "Started" message
+            on success).  The optional port and address information are
+            appended to the message (if present).
+        """
+        self.curproc = process
+        if self.verbose:
+            sys.stdout.write("[bind10] Starting %s" % self.curproc)
+            if port is not None:
+                sys.stdout.write(" on port %d" % port)
+                if address is not None:
+                    sys.stdout.write(" (address %s)" % str(address))
+            sys.stdout.write("\n")
+
+    def log_started(self, pid = None):
+        """
+            A convenience function to output a 'Started xxxx (PID yyyy)'
+            message.  As with starting_message(), this ensures a consistent
+            format.
+        """
         if self.verbose:
-            sys.stdout.write("[bind10] Started b10-msgq (PID %d)\n" % 
-                             c_channel.pid)
+            sys.stdout.write("[bind10] Started %s" % self.curproc)
+            if pid is not None:
+                sys.stdout.write(" (PID %d)" % pid)
+            sys.stdout.write("\n")
+
+    # The next few methods start the individual processes of BIND-10.  They
+    # are called via start_all_process().  If any fail, an exception is raised
+    # which is caught by the caller of start_all_processes(); this kills
+    # processes started up to that point before terminating the program.
 
-        # now connect to the c-channel
+    def start_msgq(self, c_channel_env):
+        """
+            Start the message queue and connect to the command channel.
+        """
+        self.log_starting("b10-msgq")
+        c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
+                                True, not self.verbose, uid=self.uid,
+                                username=self.username)
+        self.processes[c_channel.pid] = c_channel
+        self.log_started(c_channel.pid)
+
+        # Now connect to the c-channel
         cc_connect_start = time.time()
         while self.cc_session is None:
             # if we have been trying for "a while" give up
             if (time.time() - cc_connect_start) > 5:
-                c_channel.process.kill()
-                return "Unable to connect to c-channel after 5 seconds"
+                raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+
             # try to connect, and if we can't wait a short while
             try:
                 self.cc_session = isc.cc.Session(self.msgq_socket_file)
             except isc.cc.session.SessionError:
                 time.sleep(0.1)
 
-        # start the configuration manager
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-cfgmgr\n")
-        try:
-            bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
-                                    c_channel_env, uid=self.uid,
-                                    username=self.username)
-        except Exception as e:
-            c_channel.process.kill()
-            return "Unable to start b10-cfgmgr; " + str(e)
+    def start_cfgmgr(self, c_channel_env):
+        """
+            Starts the configuration manager process
+        """
+        self.log_starting("b10-cfgmgr")
+        bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
+                                c_channel_env, uid=self.uid,
+                                username=self.username)
         self.processes[bind_cfgd.pid] = bind_cfgd
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-cfgmgr (PID %d)\n" % 
-                             bind_cfgd.pid)
+        self.log_started(bind_cfgd.pid)
 
         # sleep until b10-cfgmgr is fully up and running, this is a good place
         # to have a (short) timeout on synchronized groupsend/receive
         # TODO: replace the sleep by a listen for ConfigManager started
         # message
         time.sleep(1)
-        if self.verbose:
-            sys.stdout.write("[bind10] starting ccsession\n")
+
+    def start_ccsession(self, c_channel_env):
+        """
+            Start the CC Session
+
+            The argument c_channel_env is unused but is supplied to keep the
+            argument list the same for all start_xxx methods.
+        """
+        self.log_starting("ccsession")
         self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
                                       self.config_handler, self.command_handler)
         self.ccs.start()
+        self.log_started()
+
+    # A couple of utility methods for starting processes...
+
+    def start_process(self, name, args, c_channel_env, port=None, address=None):
+        """
+            Given a set of command arguments, start the process and output
+            appropriate log messages.  If the start is successful, the process
+            is added to the list of started processes.
+
+            The port and address arguments are for log messages only.
+        """
+        self.log_starting(name, port, address)
+        newproc = ProcessInfo(name, args, c_channel_env)
+        self.processes[newproc.pid] = newproc
+        self.log_started(newproc.pid)
+
+    def start_simple(self, name, c_channel_env, port=None, address=None):
+        """
+            Most of the BIND-10 processes are started with the command:
+
+                <process-name> [-v]
+
+            ... where -v is appended if verbose is enabled.  This method
+            generates the arguments from the name and starts the process.
+
+            The port and address arguments are for log messages only.
+        """
+        # Set up the command arguments.
+        args = [name]
         if self.verbose:
-            sys.stdout.write("[bind10] ccsession started\n")
+            args += ['-v']
+
+        # ... and start the process
+        self.start_process(name, args, c_channel_env, port, address)
 
-        # start b10-auth
+    # The next few methods start up the rest of the BIND-10 processes.
+    # Although many of these methods are little more than a call to
+    # start_simple, they are retained (a) for testing reasons and (b) as a place
+    # where modifications can be made if the process start-up sequence changes
+    # for a given process.
+
+    def start_auth(self, c_channel_env):
+        """
+            Start the Authoritative server
+        """
         # XXX: this must be read from the configuration manager in the future
         authargs = ['b10-auth', '-p', str(self.auth_port)]
         if self.address:
@@ -346,119 +426,128 @@ class BoB:
             authargs += ['-u', str(self.uid)]
         if self.verbose:
             authargs += ['-v']
-            sys.stdout.write("Starting b10-auth using port %d" %
-                             self.auth_port)
-            if self.address:
-                sys.stdout.write(" on %s" % str(self.address))
-            sys.stdout.write("\n")
-        try:
-            auth = ProcessInfo("b10-auth", authargs,
-                               c_channel_env)
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            xfrout.process.kill()
-            return "Unable to start b10-auth; " + str(e)
-        self.processes[auth.pid] = auth
+
+        # ... and start
+        self.start_process("b10-auth", authargs, c_channel_env,
+            self.auth_port, self.address)
+
+    def start_recurse(self, c_channel_env):
+        """
+            Start the Resolver.  At present, all these arguments and switches
+            are pure speculation.  As with the auth daemon, they should be
+            read from the configuration database.
+        """
+        self.curproc = "b10-recurse"
+        # XXX: this must be read from the configuration manager in the future
+        resargs = ['b10-recurse']
+        if self.uid:
+            resargs += ['-u', str(self.uid)]
         if self.verbose:
-            sys.stdout.write("[bind10] Started b10-auth (PID %d)\n" % auth.pid)
+            resargs += ['-v']
+
+        # ... and start
+        self.start_process("b10-recurse", resargs, c_channel_env)
+
+    def start_xfrout(self, c_channel_env):
+        self.start_simple("b10-xfrout", c_channel_env)
+
+    def start_xfrin(self, c_channel_env):
+        self.start_simple("b10-xfrin", c_channel_env)
 
-        # everything after the authoritative server can run as non-root
+    def start_zonemgr(self, c_channel_env):
+        self.start_simple("b10-zonemgr", c_channel_env)
+
+    def start_stats(self, c_channel_env):
+        self.start_simple("b10-stats", c_channel_env)
+
+    def start_cmdctl(self, c_channel_env):
+        # XXX: we hardcode port 8080
+        self.start_simple("b10-cmdctl", c_channel_env, 8080)
+
+    def start_all_processes(self, c_channel_env):
+        """
+            Starts up all the processes.  Any exception generated during the
+            starting of the processes is handled by the caller.
+        """
+        self.start_msgq(c_channel_env)
+        self.start_cfgmgr(c_channel_env)
+        self.start_ccsession(c_channel_env)
+
+        # Extract the parameters associated with Bob.  This can only be
+        # done after the CC Session is started.
+        self.read_bind10_config()
+
+        # Continue starting the processes.  The authoritative server (if
+        # selected):
+        if self.cfg_start_auth:
+            self.start_auth(c_channel_env)
+
+        # ... and resolver (if selected):
+        if self.cfg_start_recurse:
+            self.start_recurse(c_channel_env)
+
+        # Everything after the main components can run as non-root.
+        # TODO: this is only temporary - once the privileged socket creator is
+        # fully working, nothing else will run as root.
         if self.uid is not None:
             posix.setuid(self.uid)
 
-        # start the xfrout before auth-server, to make sure every xfr-query can
-        # be processed properly.
-        xfrout_args = ['b10-xfrout']
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-xfrout\n")
-            xfrout_args += ['-v']
-        try:
-            xfrout = ProcessInfo("b10-xfrout", xfrout_args, 
-                                 c_channel_env )
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            return "Unable to start b10-xfrout; " + str(e)
-        self.processes[xfrout.pid] = xfrout
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-xfrout (PID %d)\n" % 
-                             xfrout.pid)
+        # xfrin/xfrout and the zone manager are only meaningful if the
+        # authoritative server has been started.
+        if self.cfg_start_auth:
+            self.start_xfrout(c_channel_env)
+            self.start_xfrin(c_channel_env)
+            self.start_zonemgr(c_channel_env)
 
-        # start b10-xfrin
-        xfrin_args = ['b10-xfrin']
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-xfrin\n")
-            xfrin_args += ['-v']
-        try:
-            xfrind = ProcessInfo("b10-xfrin", xfrin_args,
-                                 c_channel_env)
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            xfrout.process.kill()
-            auth.process.kill()
-            return "Unable to start b10-xfrin; " + str(e)
-        self.processes[xfrind.pid] = xfrind
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" % 
-                             xfrind.pid)
-
-        # start b10-zonemgr
-        zonemgr_args = ['b10-zonemgr']
+        # ... and finally start the remaining processes
+        self.start_stats(c_channel_env)
+        self.start_cmdctl(c_channel_env)
+    
+    def startup(self):
+        """
+            Start the BoB instance.
+ 
+            Returns None if successful, otherwise an string describing the
+            problem.
+        """
+        # Try to connect to the c-channel daemon, to see if it is already
+        # running
+        c_channel_env = {}
+        if self.msgq_socket_file is not None:
+             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file 
         if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-zonemgr\n")
-            zonemgr_args += ['-v']
+           sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
+        # try to connect, and if we can't wait a short while
         try:
-            zonemgr = ProcessInfo("b10-zonemgr", zonemgr_args,
-                                 c_channel_env)
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            xfrout.process.kill()
-            auth.process.kill()
-            xfrind.process.kill()
-            return "Unable to start b10-zonemgr; " + str(e)
-        self.processes[zonemgr.pid] = zonemgr 
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-zonemgr(PID %d)\n" % 
-                             zonemgr.pid)
+            self.cc_session = isc.cc.Session(self.msgq_socket_file)
+            return "b10-msgq already running, or socket file not cleaned , cannot start"
+        except isc.cc.session.SessionError:
+            # this is the case we want, where the msgq is not running
+            pass
 
-        # start the b10-cmdctl
-        # XXX: we hardcode port 8080
-        cmdctl_args = ['b10-cmdctl']
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-cmdctl on port 8080\n")
-            cmdctl_args += ['-v']
+        # Start all processes.  If any one fails to start, kill all started
+        # processes and exit with an error indication.
         try:
-            cmd_ctrld = ProcessInfo("b10-cmdctl", cmdctl_args,
-                                    c_channel_env)
+            self.start_all_processes(c_channel_env)
         except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            xfrout.process.kill()
-            auth.process.kill()
-            xfrind.process.kill()
-            zonemgr.process.kill()
-            return "Unable to start b10-cmdctl; " + str(e)
-        self.processes[cmd_ctrld.pid] = cmd_ctrld
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-cmdctl (PID %d)\n" % 
-                             cmd_ctrld.pid)
+            self.kill_started_processes()
+            return "Unable to start " + self.curproc + ": " + str(e)
 
+        # Started successfully
         self.runnable = True
-
         return None
 
     def stop_all_processes(self):
         """Stop all processes."""
         cmd = { "command": ['shutdown']}
-        self.cc_session.group_sendmsg(cmd, 'Boss', 'Cmdctl')
-        self.cc_session.group_sendmsg(cmd, "Boss", "ConfigManager")
-        self.cc_session.group_sendmsg(cmd, "Boss", "Auth")
-        self.cc_session.group_sendmsg(cmd, "Boss", "Xfrout")
-        self.cc_session.group_sendmsg(cmd, "Boss", "Xfrin")
-        self.cc_session.group_sendmsg(cmd, "Boss", "Zonemgr")
+        self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
+        self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
+        self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
+        self.cc_session.group_sendmsg(cmd, "Recurse", "Recurse")
+        self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
+        self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
+        self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
+        self.cc_session.group_sendmsg(cmd, "Boss", "Stats")
 
     def stop_process(self, process):
         """Stop the given process, friendly-like."""
@@ -475,7 +564,9 @@ class BoB:
         except:
             pass
         # XXX: some delay probably useful... how much is uncertain
-        time.sleep(0.5)  
+        # I have changed the delay from 0.5 to 1, but sometime it's 
+        # still not enough.
+        time.sleep(1)  
         self.reap_children()
         # next try sending a SIGTERM
         processes_to_stop = list(self.processes.values())
@@ -582,7 +673,7 @@ def reaper(signal_number, stack_frame):
     # the Python signal handler has been set up to write
     # down a pipe, waking up our select() bit
     pass
-                   
+
 def get_signame(signal_number):
     """Return the symbolic name for a signal."""
     for sig in dir(signal):
@@ -604,30 +695,28 @@ def fatal_signal(signal_number, stack_frame):
 def check_port(option, opt_str, value, parser):
     """Function to insure that the port we are passed is actually 
     a valid port number. Used by OptionParser() on startup."""
-    if not re.match('^(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$', value):
-        raise OptionValueError("%s requires a port number (0-65535)" % opt_str)
-    if (opt_str == '-m' or opt_str == '--msgq-port'):
-        parser.values.msgq_port = value
-    elif (opt_str == '-p' or opt_str == '--port'):
-        parser.values.auth_port = value
-    else:
-        raise OptionValueError("Unknown option " + opt_str)
-  
+    try:
+        if opt_str in ['-p', '--port']:
+            parser.values.auth_port = isc.net.parse.port_parse(value)
+        else:
+            raise OptionValueError("Unknown option " + opt_str)
+    except ValueError as e:
+        raise OptionValueError(str(e))
+
 def check_addr(option, opt_str, value, parser):
     """Function to insure that the address we are passed is actually 
     a valid address. Used by OptionParser() on startup."""
     try:
-        IPAddr(value)
-    except:
+        if opt_str in ['-a', '--address']:
+            parser.values.address = isc.net.parse.addr_parse(value)
+        else:
+            raise OptionValueError("Unknown option " + opt_str)
+    except ValueError:
         raise OptionValueError("%s requires a valid IPv4 or IPv6 address" % opt_str)
-    if (opt_str == '-a' or opt_str == '--address'):
-        parser.values.address = value
-    else:
-        raise OptionValueError("Unknown option " + opt_str)
 
 def process_rename(option, opt_str, value, parser):
     """Function that renames the process if it is requested by a option."""
-    isc.utils.process.rename(value)
+    isc.util.process.rename(value)
 
 def main():
     global options
@@ -635,19 +724,18 @@ def main():
     # Enforce line buffering on stdout, even when not a TTY
     sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
 
-
     # Parse any command-line options.
     parser = OptionParser(version=VERSION)
     parser.add_option("-a", "--address", dest="address", type="string",
-                      action="callback", callback=check_addr, default='',
+                      action="callback", callback=check_addr, default=None,
                       help="address the b10-auth daemon will use (default: listen on all addresses)")
     parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
                       type="string", default=None,
                       help="UNIX domain socket file the b10-msgq daemon will use")
     parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
                       default=False, help="disable hot-spot cache in b10-auth")
-    parser.add_option("-p", "--port", dest="auth_port", type="string",
-                      action="callback", callback=check_port, default="5300",
+    parser.add_option("-p", "--port", dest="auth_port", type="int",
+                      action="callback", callback=check_port, default=5300,
                       help="port the b10-auth daemon will use (default 5300)")
     parser.add_option("-u", "--user", dest="user",
                       type="string", default=None,
@@ -710,8 +798,11 @@ def main():
     signal.signal(signal.SIGINT, fatal_signal)
     signal.signal(signal.SIGTERM, fatal_signal)
 
+    # Block SIGPIPE, as we don't want it to end this process
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+
     # Go bob!
-    boss_of_bind = BoB(options.msgq_socket_file, int(options.auth_port),
+    boss_of_bind = BoB(options.msgq_socket_file, options.auth_port,
                        options.address, options.nocache, options.verbose,
                        setuid, username)
     startup_result = boss_of_bind.startup()
@@ -720,6 +811,17 @@ def main():
         sys.exit(1)
     sys.stdout.write("[bind10] BIND 10 started\n")
 
+    # send "bind10.boot_time" to b10-stats
+    time.sleep(1) # wait a second
+    if options.verbose:
+        sys.stdout.write("[bind10] send \"bind10.boot_time\" to b10-stats\n")
+    cmd = isc.config.ccsession.create_command('set', 
+            { "stats_data": {
+              'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+              }
+            })
+    boss_of_bind.cc_session.group_sendmsg(cmd, 'Stats')
+
     # In our main loop, we check for dead processes or messages 
     # on the c-channel.
     wakeup_fd = wakeup_pipe[0]
@@ -760,6 +862,7 @@ def main():
     # shutdown
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     boss_of_bind.shutdown()
+    sys.stdout.write("[bind10] BIND 10 exiting\n");
     sys.exit(0)
 
 if __name__ == "__main__":

+ 12 - 0
src/bin/bind10/bob.spec

@@ -3,6 +3,18 @@
     "module_name": "Boss",
     "module_description": "Master process",
     "config_data": [
+      {
+        "item_name": "start_auth",
+        "item_type": "boolean",
+        "item_optional": false,
+        "item_default": true
+      },
+      {
+        "item_name": "start_recurse",
+        "item_type": "boolean",
+        "item_optional": false,
+        "item_default": false
+      }
     ],
     "commands": [
       {

+ 1 - 1
src/bin/bind10/run_bind10.sh.in

@@ -20,7 +20,7 @@ export PYTHON_EXEC
 
 BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/recurse:$PATH
 export PATH
 
 PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs

+ 202 - 29
src/bin/bind10/tests/bind10_test.py

@@ -1,4 +1,4 @@
-from bind10 import ProcessInfo, BoB, IPAddr
+from bind10 import ProcessInfo, BoB
 
 # XXX: environment tests are currently disabled, due to the preprocessor
 #      setup that we have now complicating the environment
@@ -8,6 +8,7 @@ import sys
 import os
 import signal
 import socket
+from isc.net.addr import IPAddr
 
 class TestProcessInfo(unittest.TestCase):
     def setUp(self):
@@ -72,71 +73,243 @@ class TestProcessInfo(unittest.TestCase):
         self.assertTrue(type(pi.pid) is int)
         self.assertNotEqual(pi.pid, old_pid)
 
-class TestIPAddr(unittest.TestCase):
-    def test_v6ok(self):
-        addr = IPAddr('2001:4f8::1')
-        self.assertEqual(addr.family, socket.AF_INET6)
-        self.assertEqual(addr.addr, socket.inet_pton(socket.AF_INET6, '2001:4f8::1'))
-
-    def test_v4ok(self):
-        addr = IPAddr('127.127.127.127')
-        self.assertEqual(addr.family, socket.AF_INET)
-        self.assertEqual(addr.addr, socket.inet_aton('127.127.127.127'))
-
-    def test_badaddr(self):
-        self.assertRaises(socket.error, IPAddr, 'foobar')
-        self.assertRaises(socket.error, IPAddr, 'foo::bar')
-        self.assertRaises(socket.error, IPAddr, '123')
-        self.assertRaises(socket.error, IPAddr, '123.456.789.0')
-        self.assertRaises(socket.error, IPAddr, '127/8')
-        self.assertRaises(socket.error, IPAddr, '0/0')
-        self.assertRaises(socket.error, IPAddr, '1.2.3.4/32')
-        self.assertRaises(socket.error, IPAddr, '0')
-        self.assertRaises(socket.error, IPAddr, '')
-
 class TestBoB(unittest.TestCase):
     def test_init(self):
         bob = BoB()
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.auth_port, 5300)
-        self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.address, None)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
     def test_init_alternate_socket(self):
         bob = BoB("alt_socket_file")
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
+        self.assertEqual(bob.auth_port, 5300)
+        self.assertEqual(bob.address, None)
         self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
     def test_init_alternate_auth_port(self):
         bob = BoB(None, 9999)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.auth_port, 9999)
-        self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.address, None)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
     def test_init_alternate_address(self):
-        bob = BoB(None, 5300, '127.127.127.127')
+        bob = BoB(None, 1234, IPAddr('127.127.127.127'))
         self.assertEqual(bob.verbose, False)
-        self.assertEqual(bob.auth_port, 5300)
         self.assertEqual(bob.msgq_socket_file, None)
-        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.auth_port, 1234)
         self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
-    # verbose testing...
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
+
+# Class for testing the Bob.start_all_processes() method call.
+#
+# Although testing that external processes start is outside the scope
+# of the unit test, by overriding the process start methods we can check
+# that the right processes are started depending on the configuration
+# options.
+class StartAllProcessesBob(BoB):
+    def __init__(self):
+        BoB.__init__(self)
+
+# Set flags as to which of the overridden methods has been run.
+        self.msgq = False
+        self.cfgmgr = False
+        self.ccsession = False
+        self.auth = False
+        self.recurse = False
+        self.xfrout = False
+        self.xfrin = False
+        self.zonemgr = False
+        self.stats = False
+        self.cmdctl = False
+
+    def read_bind10_config(self):
+        # Configuration options are set directly
+        pass
+
+    def start_msgq(self, c_channel_env):
+        self.msgq = True
+
+    def start_cfgmgr(self, c_channel_env):
+        self.cfgmgr = True
+
+    def start_ccsession(self, c_channel_env):
+        self.ccsession = True
+
+    def start_auth(self, c_channel_env):
+        self.auth = True
+
+    def start_recurse(self, c_channel_env):
+        self.recurse = True
+
+    def start_xfrout(self, c_channel_env):
+        self.xfrout = True
+
+    def start_xfrin(self, c_channel_env):
+        self.xfrin = True
+
+    def start_zonemgr(self, c_channel_env):
+        self.zonemgr = True
+
+    def start_stats(self, c_channel_env):
+        self.stats = True
+
+    def start_cmdctl(self, c_channel_env):
+        self.cmdctl = True
+
+# Check that the start_all_processes method starts the right combination
+# of processes.
+class TestStartAllProcessesBob(unittest.TestCase):
+    def check_preconditions(self, bob):
+        self.assertEqual(bob.msgq, False)
+        self.assertEqual(bob.cfgmgr, False)
+        self.assertEqual(bob.ccsession, False)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, False)
+        self.assertEqual(bob.cmdctl, False)
+
+    # Checks the processes started when starting neither auth nor recurse
+    # is specified.
+    def test_start_none(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = False
+        bob.cfg_start_recurse = False
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting only the auth process
+    def test_start_auth(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = True
+        bob.cfg_start_recurse = False
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, True)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, True)
+        self.assertEqual(bob.xfrin, True)
+        self.assertEqual(bob.zonemgr, True)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting only the recurse process
+    def test_start_recurse(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = False
+        bob.cfg_start_recurse = True
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting both auth and recurse process
+    def test_start_both(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = True
+        bob.cfg_start_recurse = True
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, True)
+        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.xfrout, True)
+        self.assertEqual(bob.xfrin, True)
+        self.assertEqual(bob.zonemgr, True)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
 
 if __name__ == '__main__':
     unittest.main()

+ 5 - 2
src/bin/bindctl/bindcmd.py

@@ -558,7 +558,7 @@ class BindCmdInterpreter(Cmd):
                     if value_map['type'] in [ 'module', 'map', 'list' ]:
                         line += "/"
                     else:
-                        line += ":\t" + str(value_map['value'])
+                        line += ":\t" + json.dumps(value_map['value'])
                     line += "\t" + value_map['type']
                     line += "\t"
                     if value_map['default']:
@@ -569,7 +569,10 @@ class BindCmdInterpreter(Cmd):
             elif cmd.command == "add":
                 self.config_data.add_value(identifier, cmd.params['value'])
             elif cmd.command == "remove":
-                self.config_data.remove_value(identifier, cmd.params['value'])
+                if 'value' in cmd.params:
+                    self.config_data.remove_value(identifier, cmd.params['value'])
+                else:
+                    self.config_data.remove_value(identifier, None)
             elif cmd.command == "set":
                 if 'identifier' not in cmd.params:
                     print("Error: missing identifier or value")

+ 8 - 5
src/bin/bindctl/bindctl-source.py.in

@@ -24,11 +24,14 @@ from bindctl.moduleinfo import *
 from bindctl.bindcmd import *
 import pprint
 from optparse import OptionParser, OptionValueError
-import isc.utils.process
+import isc.util.process
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
-__version__ = 'Bindctl'
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bindctl 20101201 (BIND 10 @PACKAGE_VERSION@)"
 
 def prepare_config_commands(tool):
     '''Prepare fixed commands for local configuration editing'''
@@ -48,7 +51,7 @@ def prepare_config_commands(tool):
     cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
     param = ParamInfo(name = "identifier", type = "string", optional=True)
     cmd.add_param(param)
-    param = ParamInfo(name = "value", type = "string", optional=False)
+    param = ParamInfo(name = "value", type = "string", optional=True)
     cmd.add_param(param)
     module.add_command(cmd)
 
@@ -113,7 +116,7 @@ def set_bindctl_options(parser):
 
 if __name__ == '__main__':
     try:
-        parser = OptionParser(version = __version__)
+        parser = OptionParser(version = VERSION)
         set_bindctl_options(parser)
         (options, args) = parser.parse_args()
         server_addr = options.addr + ':' + str(options.port)

+ 2 - 2
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -21,11 +21,11 @@ import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError
 from isc.cc import SessionError
-import isc.utils.process
+import isc.util.process
 import signal
 import os
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 # If B10_FROM_SOURCE is set in the environment, we use data files
 # from a directory relative to that, otherwise we use the ones

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

@@ -15,7 +15,7 @@ CMDCTL_CONFIGURATIONS += cmdctl-keyfile.pem cmdctl-certfile.pem
 
 b10_cmdctl_DATA = $(CMDCTL_CONFIGURATIONS)
 b10_cmdctl_DATA += cmdctl.spec
- 
+
 EXTRA_DIST = $(CMDCTL_CONFIGURATIONS)
 
 CLEANFILES=	b10-cmdctl cmdctl.pyc cmdctl.spec

+ 20 - 18
src/bin/cmdctl/cmdctl.py.in

@@ -1,6 +1,7 @@
 #!@PYTHON@
 
 # Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010  CZ NIC
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -42,15 +43,18 @@ import random
 import time
 import signal
 from isc.config import ccsession
-import isc.utils.process
+import isc.util.process
+import isc.net.parse
 from optparse import OptionParser, OptionValueError
 from hashlib import sha1
+from isc.util import socketserver_mixin
+
 try:
     import threading
 except ImportError:
     import dummy_threading as threading
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 __version__ = 'BIND10'
 URL_PATTERN = re.compile('/([\w]+)(?:/([\w]+))?/?')
@@ -323,8 +327,8 @@ class CommandControl():
     def _handle_msg_from_msgq(self):
         '''Process all the received commands with module session. '''
         while self._serving:
-            self._module_cc.check_command() 
- 
+            self._module_cc.check_command(False)
+
     def _parse_command_result(self, rcode, reply):
         '''Ignore the error reason when command rcode isn't 0, '''
         if rcode != 0:
@@ -439,7 +443,9 @@ class CommandControl():
 
         return (keyfile, certfile, accountsfile)
 
-class SecureHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
+class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
+                       socketserver.ThreadingMixIn,
+                       http.server.HTTPServer):
     '''Make the server address can be reused.'''
     allow_reuse_address = True
 
@@ -447,6 +453,7 @@ class SecureHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
                  CommandControlClass,
                  idle_timeout = 1200, verbose = False):
         '''idle_timeout: the max idle time for login'''
+        socketserver_mixin.NoPollMixIn.__init__(self)
         try:
             http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
         except socket.error as err:
@@ -564,22 +571,17 @@ def run(addr = 'localhost', port = 8080, idle_timeout = 1200, verbose = False):
     httpd.serve_forever()
 
 def check_port(option, opt_str, value, parser):
-    if (value < 0) or (value > 65535):
-        raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
-    parser.values.port = value
+    try:
+        parser.values.port = isc.net.parse.port_parse(value)
+    except ValueError as e:
+        raise OptionValueError(str(e))
 
 def check_addr(option, opt_str, value, parser):
-    ipstr = value
-    ip_family = socket.AF_INET
-    if (ipstr.find(':') != -1):
-        ip_family = socket.AF_INET6
-
     try:
-        socket.inet_pton(ip_family, ipstr)
-    except:
-        raise OptionValueError("%s invalid ip address" % ipstr)
-
-    parser.values.addr = value
+        isc.net.parse.addr_parse(value)
+        parser.values.addr = value
+    except ValueError as e:
+        raise OptionValueError(str(e))
 
 def set_cmd_options(parser):
     parser.add_option('-p', '--port', dest = 'port', type = 'int',

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

@@ -1,5 +1,6 @@
 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)
 

+ 6 - 5
src/bin/host/host.cc

@@ -58,7 +58,7 @@ host_lookup(const char* const name, const char* const type) {
     msg.setOpcode(Opcode::QUERY());
     msg.setRcode(Rcode::NOERROR());
     if (recursive_bit) {
-        msg.setHeaderFlag(MessageFlag::RD());    // set recursive bit
+        msg.setHeaderFlag(Message::HEADERFLAG_RD); // set recursive bit
     }
 
     msg.addQuestion(Question(Name(name),
@@ -122,15 +122,16 @@ host_lookup(const char* const name, const char* const type) {
 
             rmsg.fromWire(ibuffer);
             if (!verbose) {
-                  for (RRsetIterator it = rmsg.beginSection(Section::ANSWER());
-                       it != rmsg.endSection(Section::ANSWER());
-                       ++it) {
+                for (RRsetIterator it =
+                         rmsg.beginSection(Message::SECTION_ANSWER);
+                     it != rmsg.endSection(Message::SECTION_ANSWER);
+                     ++it) {
                       if ((*it)->getType() != RRType::A()) {
                           continue;
                       }
 
                       RdataIteratorPtr rit = (*it)->getRdataIterator();
-                      for (rit->first(); !rit->isLast(); rit->next()) {
+                      for (; !rit->isLast(); rit->next()) {
                           // instead of using my name, maybe use returned label?
                           cout << name << " has address " <<
                               (*rit).getCurrent().toText() << endl;

+ 2 - 2
src/bin/loadzone/b10-loadzone.py.in

@@ -18,12 +18,12 @@
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import re, getopt
 import isc.datasrc
-import isc.utils.process
+import isc.util.process
 from isc.datasrc.master import MasterFile
 import time
 import os
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 #########################################################################
 # usage: print usage note and exit

+ 1 - 1
src/bin/loadzone/tests/error/error.known

@@ -1,5 +1,5 @@
 Error reading zone file: Cannot parse RR, No $ORIGIN: @ IN SOA ns hostmaster 1 3600 1800 1814400 3600
-Error reading zone file: $ORIGIN is not absolute in record:$ORIGIN com
+Error reading zone file: $ORIGIN is not absolute in record: $ORIGIN com
 Error reading zone file: Cannot parse RR: $TL 300
 Error reading zone file: Cannot parse RR: $OIGIN com.
 Error loading database: Error while loading com.: Cannot parse RR: $INLUDE file.txt

+ 7 - 5
src/bin/msgq/msgq.py.in

@@ -31,14 +31,16 @@ import select
 import pprint
 import random
 from optparse import OptionParser, OptionValueError
-import isc.utils.process
+import isc.util.process
 
 import isc.cc
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 # This is the version that gets displayed to the user.
-__version__ = "v20091030 (Paving the DNS Parking Lot)"
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "b10-msgq 20100818 (BIND 10 @PACKAGE_VERSION@)"
 
 class MsgQReceiveError(Exception): pass
 
@@ -421,7 +423,7 @@ if __name__ == "__main__":
         parser.values.msgq_port = intval
 
     # Parse any command-line options.
-    parser = OptionParser(version=__version__)
+    parser = OptionParser(version=VERSION)
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                       help="display more about what is going on")
     parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -433,7 +435,7 @@ if __name__ == "__main__":
 
     # Announce startup.
     if options.verbose:
-        sys.stdout.write("[b10-msgq] MsgQ %s\n" % __version__)
+        sys.stdout.write("[b10-msgq] %s\n" % VERSION)
 
     msgq = MsgQ(options.msgq_socket_file, options.verbose)
 

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

@@ -8,6 +8,7 @@ check-local:
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/msgq:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
+	BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
 	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
 	done
 

+ 7 - 0
src/bin/msgq/tests/msgq_test.py

@@ -79,9 +79,14 @@ class TestSubscriptionManager(unittest.TestCase):
 
     def test_open_socket_default(self):
         env_var = None
+        orig_socket_file = None
         if "BIND10_MSGQ_SOCKET_FILE" in os.environ:
             env_var = os.environ["BIND10_MSGQ_SOCKET_FILE"]
             del os.environ["BIND10_MSGQ_SOCKET_FILE"]
+        # temporarily replace the class "default" not to be disrupted by
+        # any running BIND 10 instance.
+        if "BIND10_TEST_SOCKET_FILE" in os.environ:
+            MsgQ.SOCKET_FILE = os.environ["BIND10_TEST_SOCKET_FILE"]
         socket_file = MsgQ.SOCKET_FILE
         self.assertFalse(os.path.exists(socket_file))
         msgq = MsgQ();
@@ -96,6 +101,8 @@ class TestSubscriptionManager(unittest.TestCase):
             pass
         if env_var is not None:
             os.environ["BIND10_MSGQ_SOCKET_FILE"] = env_var
+        if orig_socket_file is not None:
+            MsgQ.SOCKET_FILE = orig_socket_file
 
     def test_open_socket_bad(self):
         msgq = MsgQ("/does/not/exist")

+ 20 - 0
src/bin/recurse/Makefile.am

@@ -0,0 +1,20 @@
+# SUBDIRS = . tests
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_SCRIPTS = b10-recurse
+
+b10_recursedir = $(DESTDIR)$(pkgdatadir)
+b10_recurse_DATA = recurse.spec
+
+CLEANFILES=	b10-recurse recurse.pyc recurse.py recurse.spec recurse.spec.pre
+
+recurse.spec: recurse.spec.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" recurse.spec.pre >$@
+
+# TODO: does this need $$(DESTDIR) also?
+# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
+b10-recurse: recurse.py
+	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
+	       -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" recurse.py >$@
+	chmod a+x $@

+ 7 - 0
src/bin/recurse/README_FIRST.txt

@@ -0,0 +1,7 @@
+All the files in this directory are for testing ticket #412 only.
+
+Another ticket has created the "b10-recurse" program.  When both are merged
+into the trunk, these files should be deleted.
+
+Stephen Morris
+24 November 2010

+ 177 - 0
src/bin/recurse/recurse.py.in

@@ -0,0 +1,177 @@
+#!@PYTHON@
+
+# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010  CZ NIC
+#
+# 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.
+
+"""
+   This is a dummy recursor module, purely for testing that the changes to
+   the Boss regarding the starting of recurse/auth works.  It should be deleted
+   when the real recursor module is made available.
+"""
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import isc
+import isc.cc
+import threading
+import struct
+import signal
+from isc.datasrc import sqlite3_ds
+from socketserver import *
+import os
+from isc.config.ccsession import *
+from isc.log.log import *
+from isc.cc import SessionError, SessionTimeout
+from isc.notify import notify_out
+import isc.util.process
+import socket
+import select
+import errno
+from optparse import OptionParser, OptionValueError
+from isc.util import socketserver_mixin
+
+try:
+    from libxfr_python import *
+    from pydnspp import *
+except ImportError as e:
+    # C++ loadable module may not be installed; even so the recurse process
+    # must keep running, so we warn about it and move forward.
+    sys.stderr.write('[b10-recurse] failed to import DNS or XFR module: %s\n' % str(e))
+
+isc.util.process.rename()
+
+if "B10_FROM_BUILD" in os.environ:
+    SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/recurse"
+    AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
+    UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/auth_recurse_conn"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+    AUTH_SPECFILE_PATH = SPECFILE_PATH
+    UNIX_SOCKET_FILE = "@@LOCALSTATEDIR@@/auth_recurse_conn"
+
+SPECFILE_LOCATION = SPECFILE_PATH + "/recurse.spec"
+AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
+MAX_TRANSFERS_OUT = 10
+VERBOSE_MODE = False
+
+
+RESOLVER_MAX_MESSAGE_SIZE = 65535
+
+class ResolverServer:
+    def __init__(self):
+        self._unix_socket_server = None
+        self._log = None
+        self._listen_sock_file = UNIX_SOCKET_FILE
+        self._shutdown_event = threading.Event()
+        self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
+        self._config_data = self._cc.get_full_config()
+        self._cc.start()
+        self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
+
+    def config_handler(self, new_config):
+        '''Update config data. TODO. Do error check'''
+        answer = create_answer(0)
+        for key in new_config:
+            if key not in self._config_data:
+                answer = create_answer(1, "Unknown config data: " + str(key))
+                continue
+            self._config_data[key] = new_config[key]
+
+        if self._log:
+            self._log.update_config(new_config)
+
+        if self._unix_socket_server:
+            self._unix_socket_server.update_config_data(self._config_data)
+
+        return answer
+
+
+    def shutdown(self):
+        '''
+            shutdown the recurse process.
+        '''
+
+        global recurse_server
+        recurse_server = None #Avoid shutdown is called twice
+        self._shutdown_event.set()
+        if self._unix_socket_server:
+            self._unix_socket_server.shutdown()
+        sys.exit(0)
+
+    def command_handler(self, cmd, args):
+        if cmd == "shutdown":
+            self._log.log_message("info", "Received shutdown command.")
+            self.shutdown()
+            answer = create_answer(0)
+        else:
+            answer = create_answer(1, "Unknown command:" + str(cmd))
+
+        return answer
+
+    def run(self):
+        '''Get and process all commands sent from cfgmgr or other modules. '''
+        while not self._shutdown_event.is_set():
+            self._cc.check_command(False)
+
+
+recurse_server = None
+
+def signal_handler(signal, frame):
+    if recurse_server:
+        recurse_server.shutdown()
+        sys.exit(0)
+
+def set_signal_handler():
+    signal.signal(signal.SIGTERM, signal_handler)
+    signal.signal(signal.SIGINT, signal_handler)
+
+def set_cmd_options(parser):
+    parser.add_option("-a", "--address", dest="address", type="string",
+            default="127.0.0.1", help="Address on which recursor listens")
+    parser.add_option("-n", "--nocache", dest="nocache", action="store_true",
+            help="Specify to disable the cache")
+    parser.add_option("-p", "--port", dest="port", type="string",
+            default="10", help="UID under which recursor runs")
+    parser.add_option("-u", "--uid", dest="uid", type="string",
+            default="5301", help="Port on which recursor listens")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+            help="display more about what is going on")
+
+if '__main__' == __name__:
+    try:
+        parser = OptionParser()
+        set_cmd_options(parser)
+        (options, args) = parser.parse_args()
+        VERBOSE_MODE = options.verbose
+
+        set_signal_handler()
+        recurse_server = ResolverServer()
+        recurse_server.run()
+    except KeyboardInterrupt:
+        sys.stderr.write("[b10-recurse] exit recurse process\n")
+    except SessionError as e:
+        sys.stderr.write("[b10-recurse] Error creating recurse, "
+                           "is the command channel daemon running?\n")
+    except SessionTimeout as e:
+        sys.stderr.write("[b10-recurse] Error creating recurse, "
+                           "is the configuration manager running?\n")
+    except ModuleCCSessionError as e:
+        sys.stderr.write("[b10-recurse] exit recurse process:%s\n" % str(e))
+
+    if recurse_server:
+        recurse_server.shutdown()
+

+ 15 - 0
src/bin/recurse/recurse.spec.pre.in

@@ -0,0 +1,15 @@
+{
+  "module_spec": {
+     "module_name": "Recurse",
+     "config_data": [
+      ],
+      "commands": [
+        {
+          "command_name": "shutdown",
+          "command_description": "Shut down Resolver",
+          "command_args": []
+        }
+      ]
+  }
+}
+     

+ 27 - 0
src/bin/recurse/run_b10-recurse.sh.in

@@ -0,0 +1,27 @@
+#! /bin/sh
+
+# Copyright (C) 2010  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
+
+MYPATH_PATH=@abs_top_builddir@/src/bin/recurse
+PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/dns/python/.libs
+export PYTHONPATH
+
+cd ${MYPATH_PATH}
+${PYTHON_EXEC} b10-recurse
+

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

@@ -0,0 +1,37 @@
+SUBDIRS = tests
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_SCRIPTS = b10-stats
+noinst_SCRIPTS = b10-stats_stub
+
+b10_statsdir = $(DESTDIR)$(pkgdatadir)
+b10_stats_DATA = stats.spec
+
+CLEANFILES = stats.spec b10-stats stats.pyc stats.pyo b10-stats_stub stats_stub.pyc stats_stub.pyo
+
+man_MANS = b10-stats.8
+EXTRA_DIST = $(man_MANS) b10-stats.xml
+
+if ENABLE_MAN
+
+b10-stats.8: b10-stats.xml
+	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-stats.xml
+
+endif
+
+stats.spec: stats.spec.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" stats.spec.pre >$@
+
+# TODO: does this need $$(DESTDIR) also?
+# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
+b10-stats: stats.py
+	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
+	       -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" \
+	       -e "s|.*#@@REMOVED@@$$||"  stats.py >$@
+	chmod a+x $@
+
+b10-stats_stub: stats_stub.py stats.py
+	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
+	       -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" stats_stub.py >$@
+	chmod a+x $@

+ 68 - 0
src/bin/stats/b10-stats.8

@@ -0,0 +1,68 @@
+'\" t
+.\"     Title: b10-stats
+.\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\"      Date: Oct 15, 2010
+.\"    Manual: BIND10
+.\"    Source: BIND10
+.\"  Language: English
+.\"
+.TH "B10\-STATS" "8" "Oct 15, 2010" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-stats \- BIND 10 statistics module
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-stats\fR\ 'u
+\fBb10\-stats\fR [\fB\-v\fR] [\fB\-\-verbose\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-stats\fR
+is a daemon forked by
+\fBbind10\fR\&. Stats module collects statistics data from each module and reports statistics information via
+\fBbindctl\fR\&. It communicates by using the Command Channel by
+\fBb10\-msgq\fR
+with other modules like
+\fBbind10\fR,
+\fBb10\-auth\fR
+and so on\&. It waits for coming data from other modules, then other modules send data to stats module periodically\&. Other modules send stats data to stats module independently from implementation of stats module, so the frequency of sending data may not be constant\&. Stats module collects data and aggregates it\&.
+.SH "OPTIONS"
+.PP
+The arguments are as follows:
+.PP
+\fB\-v\fR, \fB\-\-verbose\fR
+.RS 4
+This
+\fBb10\-stats\fR
+switches to verbose mode\&. It sends verbose messages to STDOUT\&.
+.RE
+.SH "FILES"
+.PP
+/usr/local/share/bind10\-devel/stats\&.spec
+\(em This is a spec file for
+\fBb10\-stats\fR\&. It contains definitions of statistics items of BIND 10 and commands received vi bindctl\&.
+.SH "SEE ALSO"
+.PP
+
+\fBbind10\fR(8),
+\fBbindctl\fR(1),
+\fBb10-auth\fR(8),
+BIND 10 Guide\&.
+.SH "HISTORY"
+.PP
+The
+\fBb10\-stats\fR
+daemon was initially designed and implemented by Naoki Kambe of JPRS in Oct 2010\&.
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+.br

+ 124 - 0
src/bin/stats/b10-stats.xml

@@ -0,0 +1,124 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - 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.
+-->
+
+<!-- $Id$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>Oct 15, 2010</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-stats</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-stats</refname>
+    <refpurpose>BIND 10 statistics module</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-stats</command>
+      <arg><option>-v</option></arg>
+      <arg><option>--verbose</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>
+      The <command>b10-stats</command> is a daemon forked by
+      <command>bind10</command>. Stats module collects statistics data
+      from each module and reports statistics information
+      via <command>bindctl</command>.  It communicates by using the
+      Command Channel by <command>b10-msgq</command> with other
+      modules
+      like <command>bind10</command>, <command>b10-auth</command> and
+      so on. It waits for coming data from other modules, then other
+      modules send data to stats module periodically. Other modules
+      send stats data to stats module independently from
+      implementation of stats module, so the frequency of sending data
+      may not be constant. Stats module collects data and aggregates
+      it.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+    <para>The arguments are as follows:</para>
+    <variablelist>
+      <varlistentry>
+        <term><option>-v</option>, <option>--verbose</option></term>
+        <listitem>
+	  <para>
+          This <command>b10-stats</command> switches to verbose
+          mode. It sends verbose messages to STDOUT.
+	  </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+      &mdash; This is a spec file for <command>b10-stats</command>. It
+      contains definitions of statistics items of BIND 10 and commands
+      received vi bindctl.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-stats</command> daemon was initially designed
+      and implemented by Naoki Kambe of JPRS in Oct 2010.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 30 - 0
src/bin/stats/run_b10-stats.sh.in

@@ -0,0 +1,30 @@
+#! /bin/sh
+
+# Copyright (C) 2010  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
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+STATS_PATH=@abs_top_builddir@/src/bin/stats
+
+cd ${STATS_PATH}
+exec ${PYTHON_EXEC} -O b10-stats $*

+ 30 - 0
src/bin/stats/run_b10-stats_stub.sh.in

@@ -0,0 +1,30 @@
+#! /bin/sh
+
+# Copyright (C) 2010  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
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_srcdir@
+export B10_FROM_BUILD
+
+STATS_PATH=@abs_top_builddir@/src/bin/stats
+
+cd ${STATS_PATH}
+exec ${PYTHON_EXEC} -O b10-stats_stub $*

+ 416 - 0
src/bin/stats/stats.py.in

@@ -0,0 +1,416 @@
+#!@PYTHON@
+
+# Copyright (C) 2010  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.
+
+# $Id$
+__version__ = "$Revision$"
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+import signal
+import select
+from time import time, strftime, gmtime
+from optparse import OptionParser, OptionValueError
+from collections import defaultdict
+from isc.config.ccsession import ModuleCCSession, create_answer
+from isc.cc import Session, SessionError
+# Note: Following lines are removed in b10-stats	#@@REMOVED@@
+if __name__ == 'stats':					#@@REMOVED@@
+    try:						#@@REMOVED@@
+        from fake_time import time, strftime, gmtime	#@@REMOVED@@
+    except ImportError:					#@@REMOVED@@
+        pass						#@@REMOVED@@
+
+# for setproctitle
+import isc.util.process
+isc.util.process.rename()
+
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+    SPECFILE_LOCATION = os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+
+class Singleton(type):
+    """
+    A abstract class of singleton pattern
+    """
+    # Because of singleton pattern: 
+    #   At the beginning of coding, one UNIX domain socket is needed
+    #  for config manager, another socket is needed for stats module,
+    #  then stats module might need two sockets. So I adopted the
+    #  singleton pattern because I avoid creating multiple sockets in
+    #  one stats module. But in the initial version stats module
+    #  reports only via bindctl, so just one socket is needed. To use
+    #  the singleton pattern is not important now. :(
+
+    def __init__(self, *args, **kwargs):
+        type.__init__(self, *args, **kwargs)
+        self._instances = {}
+
+    def __call__(self, *args, **kwargs):
+        if args not in self._instances:
+            self._instances[args]={}
+        kw = tuple(kwargs.items())
+        if  kw not in self._instances[args]:
+            self._instances[args][kw] = type.__call__(self, *args, **kwargs)
+        return self._instances[args][kw]
+
+class Callback():
+    """
+    A Callback handler class
+    """
+    def __init__(self, name=None, callback=None, args=(), kwargs={}):
+        self.name = name
+        self.callback = callback
+        self.args = args
+        self.kwargs = kwargs
+
+    def __call__(self, *args, **kwargs):
+        if not args:
+            args = self.args
+        if not kwargs:
+            kwargs = self.kwargs
+        if self.callback:
+            return self.callback(*args, **kwargs)
+
+class Subject():
+    """
+    A abstract subject class of observer pattern
+    """
+    # Because of observer pattern:
+    #   In the initial release, I'm also sure that observer pattern
+    #  isn't definitely needed because the interface between gathering
+    #  and reporting statistics data is single.  However in the future
+    #  release, the interfaces may be multiple, that is, multiple
+    #  listeners may be needed. For example, one interface, which
+    #  stats module has, is for between ''config manager'' and stats
+    #  module, another interface is for between ''HTTP server'' and
+    #  stats module, and one more interface is for between ''SNMP
+    #  server'' and stats module. So by considering that stats module
+    #  needs multiple interfaces in the future release, I adopted the
+    #  observer pattern in stats module. But I don't have concrete
+    #  ideas in case of multiple listener currently.
+
+    def __init__(self):
+        self._listeners = []
+
+    def attach(self, listener):
+        if not listener in self._listeners:
+            self._listeners.append(listener)
+
+    def detach(self, listener):
+        try:
+            self._listeners.remove(listener)
+        except ValueError:
+            pass
+
+    def notify(self, event, modifier=None):
+        for listener in self._listeners:
+            if modifier != listener:
+                listener.update(event)
+
+class Listener():
+    """
+    A abstract listener class of observer pattern
+    """
+    def __init__(self, subject):
+        self.subject = subject
+        self.subject.attach(self)
+        self.events = {}
+
+    def update(self, name):
+        if name in self.events:
+            callback = self.events[name]
+            return callback()
+
+    def add_event(self, event):
+        self.events[event.name]=event
+
+class SessionSubject(Subject, metaclass=Singleton):
+    """
+    A concrete subject class which creates CC session object
+    """
+    def __init__(self, session=None, verbose=False):
+        Subject.__init__(self)
+        self.verbose = verbose
+        self.session=session
+        self.running = False
+
+    def start(self):
+        self.running = True
+        self.notify('start')
+
+    def stop(self):
+        self.running = False
+        self.notify('stop')
+
+    def check(self):
+        self.notify('check')
+
+class CCSessionListener(Listener):
+    """
+    A concrete listener class which creates SessionSubject object and
+    ModuleCCSession object
+    """
+    def __init__(self, subject, verbose=False):
+        Listener.__init__(self, subject)
+        self.verbose = verbose
+        self.session = subject.session
+        self.boot_time = get_datetime()
+
+        # create ModuleCCSession object
+        self.cc_session = ModuleCCSession(SPECFILE_LOCATION,
+                                          self.config_handler,
+                                          self.command_handler,
+                                          self.session)
+
+        self.session = self.subject.session = self.cc_session._session
+
+        # initialize internal data
+        self.config_spec = self.cc_session.get_module_spec().get_config_spec()
+        self.stats_spec = self.config_spec
+        self.stats_data = self.initialize_data(self.stats_spec)
+
+        # add event handler invoked via SessionSubject object
+        self.add_event(Callback('start', self.start))
+        self.add_event(Callback('stop', self.stop))
+        self.add_event(Callback('check', self.check))
+        # don't add 'command_' suffix to the special commands in
+        # order to prevent executing internal command via bindctl
+
+        # get commands spec
+        self.commands_spec = self.cc_session.get_module_spec().get_commands_spec()
+
+        # add event handler related command_handler of ModuleCCSession
+        # invoked via bindctl
+        for cmd in self.commands_spec:
+            try:
+                # add prefix "command_"
+                name = "command_" + cmd["command_name"]
+                callback = getattr(self, name)
+                kwargs = self.initialize_data(cmd["command_args"])
+                self.add_event(Callback(name=name, callback=callback, args=(), kwargs=kwargs))
+            except AttributeError as ae:
+                sys.stderr.write("[b10-stats] Caught undefined command while parsing spec file: "
+                                 +str(cmd["command_name"])+"\n")
+
+    def start(self):
+        """
+        start the cc chanel
+        """
+        # set initial value
+        self.stats_data['stats.boot_time'] = self.boot_time
+        self.stats_data['stats.start_time'] = get_datetime()
+        self.stats_data['stats.last_update_time'] = get_datetime()
+        self.stats_data['stats.lname'] = self.session.lname
+        return self.cc_session.start()
+
+    def stop(self):
+        """
+        stop the cc chanel
+        """
+        return self.cc_session.close()
+
+    def check(self):
+        """
+        check the cc chanel
+        """
+        return self.cc_session.check_command(False)
+
+    def config_handler(self, new_config):
+        """
+        handle a configure from the cc channel
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] newconfig received: "+str(new_config)+"\n")
+
+        # do nothing currently
+        return create_answer(0)
+
+    def command_handler(self, command, *args, **kwargs):
+        """
+        handle commands from the cc channel
+        """
+        # add 'command_' suffix in order to executing command via bindctl
+        name = 'command_' + command
+        
+        if name in self.events:
+            event = self.events[name]
+            return event(*args, **kwargs)
+        else:
+            return self.command_unknown(command, args)
+
+    def command_shutdown(self, args):
+        """
+        handle shutdown command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'shutdown' command received\n")
+        self.subject.running = False
+        return create_answer(0)
+
+    def command_set(self, args, stats_data={}):
+        """
+        handle set command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'set' command received, args: "+str(args)+"\n")
+
+        # 'args' must be dictionary type
+        self.stats_data.update(args['stats_data'])
+
+        # overwrite "stats.LastUpdateTime"
+        self.stats_data['stats.last_update_time'] = get_datetime()
+
+        return create_answer(0)
+
+    def command_remove(self, args, stats_item_name=''):
+        """
+        handle remove command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'remove' command received, args: "+str(args)+"\n")
+
+        # 'args' must be dictionary type
+        if args and args['stats_item_name'] in self.stats_data:
+            stats_item_name = args['stats_item_name']
+
+        # just remove one item
+        self.stats_data.pop(stats_item_name)
+
+        return create_answer(0)
+
+    def command_show(self, args, stats_item_name=''):
+        """
+        handle show command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'show' command received, args: "+str(args)+"\n")
+
+        # always overwrite 'report_time' and 'stats.timestamp'
+        # if "show" command invoked
+        self.stats_data['report_time'] = get_datetime()
+        self.stats_data['stats.timestamp'] = get_timestamp()
+
+        # if with args
+        if args and args['stats_item_name'] in self.stats_data:
+            stats_item_name = args['stats_item_name']
+            return create_answer(0, {stats_item_name: self.stats_data[stats_item_name]})
+
+        return create_answer(0, self.stats_data)
+
+    def command_reset(self, args):
+        """
+        handle reset command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'reset' command received\n")
+
+        # re-initialize internal variables
+        self.stats_data = self.initialize_data(self.stats_spec)
+
+        # reset initial value
+        self.stats_data['stats.boot_time'] = self.boot_time
+        self.stats_data['stats.start_time'] = get_datetime()
+        self.stats_data['stats.last_update_time'] = get_datetime()
+        self.stats_data['stats.lname'] = self.session.lname
+
+        return create_answer(0)
+
+    def command_status(self, args):
+        """
+        handle status command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] 'status' command received\n")
+        # just return "I'm alive."
+        return create_answer(0, "I'm alive.")
+
+    def command_unknown(self, command, args):
+        """
+        handle an unknown command
+        """
+        if self.verbose:
+            sys.stdout.write("[b10-stats] Unknown command received: '"
+                             + str(command) + "'\n")
+        return create_answer(1, "Unknown command: '"+str(command)+"'")
+
+
+    def initialize_data(self, spec):
+        """
+        initialize stats data
+        """
+        def __get_init_val(spec):
+            if spec['item_type'] == 'null':
+                return None
+            elif spec['item_type'] == 'boolean':
+                return bool(spec.get('item_default', False))
+            elif spec['item_type'] == 'string':
+                return str(spec.get('item_default', ''))
+            elif spec['item_type'] in set(['number', 'integer']):
+                return int(spec.get('item_default', 0))
+            elif spec['item_type'] in set(['float', 'double', 'real']):
+                return float(spec.get('item_default', 0.0))
+            elif spec['item_type'] in set(['list', 'array']):
+                return spec.get('item_default',
+                                [ __get_init_val(s) for s in spec['list_item_spec'] ])
+            elif spec['item_type'] in set(['map', 'object']):
+                return spec.get('item_default',
+                                dict([ (s['item_name'], __get_init_val(s)) for s in spec['map_item_spec'] ]) )
+            else:
+                return spec.get('item_default')
+        return dict([ (s['item_name'], __get_init_val(s)) for s in spec ])
+
+def get_timestamp():
+    """
+    get current timestamp
+    """
+    return time()
+
+def get_datetime():
+    """
+    get current datetime
+    """
+    return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
+
+def main(session=None):
+    try:
+        parser = OptionParser()
+        parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      help="display more about what is going on")
+        (options, args) = parser.parse_args()
+        subject = SessionSubject(session=session, verbose=options.verbose)
+        listener = CCSessionListener(subject, verbose=options.verbose)
+        subject.start()
+        while subject.running:
+            subject.check()
+        subject.stop()
+
+    except OptionValueError:
+        sys.stderr.write("[b10-stats] Error parsing options\n")
+    except SessionError as se:
+        sys.stderr.write("[b10-stats] Error creating Stats module, "
+              + "is the command channel daemon running?\n")
+    except KeyboardInterrupt as kie:
+        sys.stderr.write("[b10-stats] Interrupted, exiting\n")
+
+if __name__ == "__main__":
+    main()

+ 140 - 0
src/bin/stats/stats.spec.pre.in

@@ -0,0 +1,140 @@
+{
+  "module_spec": {
+    "module_name": "Stats",
+    "module_description": "Stats daemon",
+    "config_data": [
+      {
+        "item_name": "report_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Report time",
+        "item_description": "A date time when stats module reports",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "bind10.boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.BootTime",
+        "item_description": "A date time when bind10 process starts initially",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.BootTime",
+        "item_description": "A date time when the stats module starts initially or when the stats module restarts",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.start_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.StartTime",
+        "item_description": "A date time when the stats module starts collecting data or resetting values last time",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.last_update_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.LastUpdateTime",
+        "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.timestamp",
+        "item_type": "real",
+        "item_optional": false,
+        "item_default": 0.0,
+        "item_title": "stats.Timestamp",
+        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
+        "item_format": "second"
+      },
+      {
+        "item_name": "stats.lname",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "",
+        "item_title": "stats.LocalName",
+        "item_description": "A localname of stats module given via CC protocol"
+      },
+      {
+        "item_name": "auth.queries.tcp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "auth.queries.tcp",
+        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
+      },
+      {
+        "item_name": "auth.queries.udp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "auth.queries.udp",
+        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "status",
+        "command_description": "identify whether stats module is alive or not",
+        "command_args": []
+      },
+      {
+        "command_name": "show",
+        "command_description": "show the specified/all statistics data",
+        "command_args": [
+          {
+            "item_name": "stats_item_name",
+            "item_type": "string",
+            "item_optional": true,
+            "item_default": ""
+          }
+        ]
+      },
+      {
+        "command_name": "set",
+        "command_description": "set the value of specified name in statistics data",
+        "command_args": [
+          {
+            "item_name": "stats_data",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {},
+            "map_item_spec": []
+          }
+        ]
+      },
+      {
+        "command_name": "remove",
+        "command_description": "remove the specified name from statistics data",
+        "command_args": [
+          {
+            "item_name": "stats_item_name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          }
+        ]
+      },
+      {
+        "command_name": "reset",
+        "command_description": "reset all statistics data to default values except for several constant names",
+        "command_args": []
+      },
+      {
+        "command_name": "shutdown",
+        "command_description": "Shut down the stats module",
+        "command_args": []
+      }
+    ]
+  }
+}

+ 155 - 0
src/bin/stats/stats_stub.py.in

@@ -0,0 +1,155 @@
+#!@PYTHON@
+
+# Copyright (C) 2010  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.
+
+# $Id$
+__version__ = "$Revision$"
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+import time
+from optparse import OptionParser, OptionValueError
+from isc.config.ccsession import ModuleCCSession, create_command, parse_answer, parse_command, create_answer
+from isc.cc import Session, SessionError
+from stats import get_datetime
+
+# for setproctitle
+import isc.util.process
+isc.util.process.rename()
+
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+    SPECFILE_LOCATION = os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+
+class CCSessionStub:
+    """
+    This class is intended to behaves as a sender to Stats module. It
+    creates MoudleCCSession object and send specified command.
+    """
+    def __init__(self, session=None, verbose=False):
+        # create ModuleCCSession object
+        self.verbose = verbose
+        self.cc_session = ModuleCCSession(SPECFILE_LOCATION,
+                                          self.__dummy, self.__dummy, session)
+        self.module_name = self.cc_session._module_name
+        self.session = self.cc_session._session
+
+    def __dummy(self, *args):
+        pass
+
+    def send_command(self, command, args):
+        """
+        send command to stats module with args
+        """
+        cmd = create_command(command, args)
+        if self.verbose:
+            sys.stdout.write("[b10-stats_stub] send command : " + str(cmd) + "\n")
+        seq = self.session.group_sendmsg(cmd, self.module_name)
+        msg, env = self.session.group_recvmsg(False, seq) # non-blocking is False
+        if self.verbose:
+            sys.stdout.write("[b10-stats_stub] received env : " + str(env) + "\n")
+            sys.stdout.write("[b10-stats_stub] received message : " + str(msg) + "\n")
+        (ret, arg) = (None, None)
+        if 'result' in msg:
+            ret, arg = parse_answer(msg)
+        elif 'command' in msg:
+            ret, arg = parse_command(msg)
+        self.session.group_reply(env, create_answer(0))
+        return ret, arg, env
+        
+class BossModuleStub:
+    """
+    This class is customized from CCSessionStub and is intended to behaves
+    as a virtual Boss module to send to Stats Module.
+    """
+    def __init__(self, session=None, verbose=False):
+        self.stub = CCSessionStub(session=session, verbose=verbose)
+    
+    def send_boottime(self):
+        return self.stub.send_command("set", {"stats_data": {"bind10.boot_time": get_datetime()}})
+
+class AuthModuleStub:
+    """
+    This class is customized CCSessionStub and is intended to behaves
+    as a virtual Auth module to send to Stats Module.
+    """
+    def __init__(self, session=None, verbose=False):
+        self.stub = CCSessionStub(session=session, verbose=verbose)
+        self.count = { "udp": 0, "tcp": 0 }
+    
+    def send_udp_query_count(self, cmd="set", cnt=0):
+        """
+        count up udp query count
+        """
+        prt = "udp"
+        self.count[prt] = 1
+        if cnt > 0:
+            self.count[prt] = cnt
+        return self.stub.send_command(cmd,
+                                      {"stats_data":
+                                           {"auth.queries."+prt: self.count[prt]}
+                                       })
+
+    def send_tcp_query_count(self, cmd="set", cnt=0):
+        """
+        set udp query count
+        """
+        prt = "tcp"
+        self.count[prt] = self.count[prt] + 1
+        if cnt > 0:
+            self.count[prt] = cnt
+        return self.stub.send_command(cmd,
+                                      {"stats_data":
+                                           {"auth.queries."+prt: self.count[prt]}
+                                       })
+
+def main(session=None):
+    try:
+        parser=OptionParser()
+        parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      help="display more about what is going on")
+        (options, args) = parser.parse_args()
+        stub = CCSessionStub(session=session, verbose=options.verbose)
+        boss = BossModuleStub(session=stub.session, verbose=options.verbose)
+        auth = AuthModuleStub(session=stub.session, verbose=options.verbose)
+        stub.send_command("status", None)
+        boss.send_boottime()
+        t_cnt=0
+        u_cnt=81120
+        auth.send_udp_query_count(cnt=u_cnt) # This is an example.
+        while True:
+            u_cnt = u_cnt + 1
+            t_cnt = t_cnt + 1
+            auth.send_udp_query_count(cnt=u_cnt)
+            auth.send_tcp_query_count(cnt=t_cnt)
+            time.sleep(1)
+
+    except OptionValueError:
+        sys.stderr.write("[b10-stats_stub] Error parsing options\n")
+    except SessionError as se:
+        sys.stderr.write("[b10-stats_stub] Error creating Stats module, "
+              + "is the command channel daemon running?\n")
+    except KeyboardInterrupt as kie:
+        sys.stderr.write("[b10-stats_stub] Interrupted, exiting\n")
+
+if __name__ == "__main__":
+    main()

+ 0 - 151
src/bin/stats/statsd.py

@@ -1,151 +0,0 @@
-#!/usr/bin/python
-#
-# This program collects 'counters' from 'statistics' channel.
-# It accepts one command: 'Boss' group 'shutdown'
-
-import isc.cc
-import time
-import select
-import os
-
-bossgroup = 'Boss'
-myname = 'statsd'
-debug = 0
-
-def total(s):
-    def totalsub(d,s):
-        for k in s.keys():
-            if (k == 'component' or k == 'version' 
-                or k == 'timestamp' or k == 'from'):
-                continue
-            if (k in d):
-                if (isinstance(s[k], dict)):
-                    totalsub(d[k], s[k])
-                else:
-                    d[k] = s[k] + d[k]
-            else:
-                d[k] = s[k]
-
-    if (len(s) == 0):
-        return {}
-    if (len(s) == 1):
-        for k in s.keys():
-            out = s[k]
-        out['components'] = 1
-        out['timestamp2'] = out['timestamp']
-        del out['from']
-        return out
-    _time1 = 0
-    _time2 = 0
-    out = {}
-    for i in s.values():
-        if (_time1 == 0 or _time1 < i['timestamp']):
-            _time1 = i['timestamp']
-        if (_time2 == 0 or _time2 > i['timestamp']):
-            _time2 = i['timestamp']
-        totalsub(out, i)
-    out['components'] = len(s)
-    out['timestamp'] = _time1;
-    out['timestamp2'] = _time2;
-    return out
-
-def dicttoxml(stats, level = 0):
-    def dicttoxmlsub(s, level):
-        output = ''
-        spaces = ' ' * level
-        for k in s.keys():
-            if (isinstance(s[k], dict)):
-                output += spaces + ('<%s>\n' %k) \
-                  + dicttoxmlsub(s[k], level+1) \
-                  + spaces + '</%s>\n' %k
-            else:
-                output += spaces + '<%s>%s</%s>\n' % (k, s[k], k)
-        return output
-
-    for k in stats.keys():
-        space = ' ' * level
-        output = space + '<component component="%s">\n' % k
-        s = stats[k]
-        if ('component' in s or 'components' in s):
-            output += dicttoxmlsub(s, level+1)
-        else:
-            for l in s.keys():
-                output +=  space + ' <from from="%s">\n' % l \
-                          + dicttoxmlsub(s[l], level+2) \
-                          + space + ' </from>\n'
-        output += space + '</component>\n'
-        return output
-
-def dump_stats(statpath, statcount, stat, statraw):
-    newfile = open(statpath + '.new', 'w')
-    newfile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
-    newfile.write('<!-- created at '+time.strftime('%Y%m%d %H%M%S')+' -->\n')
-    newfile.write('<isc version="0.0">\n')
-    newfile.write(' <bind10>\n')
-    newfile.write('  <total>\n')
-    newfile.write(dicttoxml(stat, 3))
-    newfile.write('  </total>\n')
-    newfile.write('  <each>\n')
-    newfile.write(dicttoxml(statraw, 3))
-    newfile.write('  </each>\n')
-    newfile.write(' </bind10>\n')
-    newfile.write('</isc>\n')
-    newfile.close()
-    loop = statcount
-    while(loop > 0):
-        old = statpath + '.%d' % loop
-        loop -= 1
-        new = statpath + '.%d' % loop
-        if (os.access(new, os.F_OK)):
-            os.rename(new, old)
-    if (os.access(statpath, os.F_OK)):
-        os.rename(statpath, new)
-    os.rename(statpath + '.new', statpath)
-
-def collector(statgroup,step,statpath,statcount):
-    cc = isc.cc.Session()
-    if debug:
-        print ("cc.lname=",cc.lname)
-    cc.group_subscribe(statgroup)
-    cc.group_subscribe(bossgroup, myname)
-    wrote_time = -1
-    last_wrote_time = -1
-    last_recvd_time = -1
-    stats = {}
-    statstotal = {}
-    while 1:
-        wait = wrote_time + step - time.time()
-        if wait <= 0 and last_recvd_time > wrote_time:
-            if debug:
-                print ("dump stats")
-            dump_stats(statpath, statcount, statstotal, stats)
-            last_wrote_time = wrote_time;
-            wrote_time = time.time();
-            wait = last_wrote_time + step - time.time()
-            if wait < 0:
-                wait = step
-        r,w,e = select.select([cc._socket],[],[], wait)
-        for sock in r:
-            if sock == cc._socket:
-                data,envelope = cc.group_recvmsg(False)
-                if (envelope['group'] == bossgroup):
-                    if ('shutdown' in data):
-                        exit()
-                if (envelope['group'] == statgroup):
-                    # Check received data
-                    if (not('component' in data and 'version' in data
-                        and 'stats' in data)):
-                        continue
-                    component = data['component']
-                    _from = envelope['from']
-                    data['from'] = _from
-                    if debug:
-                        print ("received from ",_from)
-                    if (not (component in stats)):
-                        stats[component] = {}
-                    (stats[component])[_from] = data;
-                    statstotal[component] = total(stats[component])
-                    last_recvd_time = time.time()
-
-if __name__ == '__main__':
-    collector('statistics', 10, '/tmp/stats.xml', 100)

+ 0 - 55
src/bin/stats/statsd.txt

@@ -1,55 +0,0 @@
-= Statistics overview =
-
-Result of 26 Jan 2010 evening discussion,
-statistics overview was almost fixed.
-
-Statsd listens msgq "statistics" channel, gathers statistics
-from each BIND 10 components and dump them into a XML file periodically.
-
-= Statsd current status =
-
-Statsd can run with msgq.
-Statsd is not controlled by BoB.
-Statsd does not read configuration from cfgd.
-File path, dump frequency, rotate generations are fixed.
-Statsd dumps to "/tmp/stats" every 10 seconds except no statistics received.
-"/tmp/stats" are preserved 100 generations.
-
-Current implementation is put on "bind10/branches/parkinglot/src/bin/stats/".
-
-= statistics channel Message format =
-
-The Statsd accepts python dictionary format data from msgq
-"statistics" channel.
-
-The data need to contain "components", "version", "timestamp", "stats" keys.
-
-The statistics data format is { "component" : "<component_name>",
-"version": "<version number>", "timestamp": "<unixtime>", "stats":
-<python dictionary format statistics>}.
-
-"stats" data may be nested.
-"stats" data is defined by each component.
-
-Each component sends statistics data to "statistics" group periodically
-without joining the group.
-
-See a example component: "stats/test/test-agent.py".
-
-= How to publish statistics from each component =
-
-For example, parkinglot auth server has one "counter".
-Then, parkinglot's statistics message may be
- { "component":"parkinglot", "version":1, "timestamp":unixtime,
-   stats: { "counter": counter } }.
-Send it to msgq "statistics" channel periodically
-(For example, every 10 second).
-
-Then "Statsd" will write it to the statistics file periodically.
-
-= TODO =
-
-- statsd.spec
-- read configuration from cfgd.
-- how to publish statistics data
-- controlled by BoB

+ 0 - 6
src/bin/stats/test/shutdown.py

@@ -1,6 +0,0 @@
-#!/usr/bin/python
-
-import isc
-cc = isc.cc.Session()
-cc.group_subscribe("Boss")
-cc.group_sendmsg({ "command":"shutdown"},"Boss")

+ 0 - 178
src/bin/stats/test/test_agent.py

@@ -1,178 +0,0 @@
-#!/usr/bin/python
-
-# This program acts statistics agent.
-# It has pseudo counters which is incremented each 10 second and
-# sends data to "statistics" channel periodically.
-# One command is available
-#   "Boss"       group: "shutdown"
-
-import isc
-import time
-import select
-import random
-
-step_time = 10
-statgroup = "statistics"
-
-cc = isc.cc.Session()
-print (cc.lname)
-#cc.group_subscribe(statgroup)
-cc.group_subscribe("Boss")
-
-# counters
-
-NSSTATDESC={}
-NSSTATDESC["counterid"] = 0
-NSSTATDESC["requestv4"] = 0
-NSSTATDESC["requestv6"] = 0
-NSSTATDESC["edns0in"] = 0
-NSSTATDESC["badednsver"] = 0
-NSSTATDESC["tsigin"] = 0
-NSSTATDESC["sig0in"] = 0
-NSSTATDESC["invalidsig"] = 0
-NSSTATDESC["tcp"] = 0
-NSSTATDESC["authrej"] = 0
-NSSTATDESC["recurserej"] = 0
-NSSTATDESC["xfrrej"] = 0
-NSSTATDESC["updaterej"] = 0
-NSSTATDESC["response"] = 0
-NSSTATDESC["truncatedresp"] = 0
-NSSTATDESC["edns0out"] = 0
-NSSTATDESC["tsigout"] = 0
-NSSTATDESC["sig0out"] = 0
-NSSTATDESC["success"] = 0
-NSSTATDESC["authans"] = 0
-NSSTATDESC["nonauthans"] = 0
-NSSTATDESC["referral"] = 0
-NSSTATDESC["nxrrset"] = 0
-NSSTATDESC["servfail"] = 0
-NSSTATDESC["formerr"] = 0
-NSSTATDESC["nxdomain"] = 0
-NSSTATDESC["recursion"] = 0
-NSSTATDESC["duplicate"] = 0
-NSSTATDESC["dropped"] = 0
-NSSTATDESC["failure"] = 0
-NSSTATDESC["xfrdone"] = 0
-NSSTATDESC["updatereqfwd"] = 0
-NSSTATDESC["updaterespfwd"] = 0
-NSSTATDESC["updatefwdfail"] = 0
-NSSTATDESC["updatedone"] = 0
-NSSTATDESC["updatefail"] = 0
-NSSTATDESC["updatebadprereq"] = 0
-RESSTATDESC={}
-RESSTATDESC["counterid"] = 0
-RESSTATDESC["queryv4"] = 0
-RESSTATDESC["queryv6"] = 0
-RESSTATDESC["responsev4"] = 0
-RESSTATDESC["responsev6"] = 0
-RESSTATDESC["nxdomain"] = 0
-RESSTATDESC["servfail"] = 0
-RESSTATDESC["formerr"] = 0
-RESSTATDESC["othererror"] = 0
-RESSTATDESC["edns0fail"] = 0
-RESSTATDESC["mismatch"] = 0
-RESSTATDESC["truncated"] = 0
-RESSTATDESC["lame"] = 0
-RESSTATDESC["retry"] = 0
-RESSTATDESC["dispabort"] = 0
-RESSTATDESC["dispsockfail"] = 0
-RESSTATDESC["querytimeout"] = 0
-RESSTATDESC["gluefetchv4"] = 0
-RESSTATDESC["gluefetchv6"] = 0
-RESSTATDESC["gluefetchv4fail"] = 0
-RESSTATDESC["gluefetchv6fail"] = 0
-RESSTATDESC["val"] = 0
-RESSTATDESC["valsuccess"] = 0
-RESSTATDESC["valnegsuccess"] = 0
-RESSTATDESC["valfail"] = 0
-RESSTATDESC["queryrtt0"] = 0
-RESSTATDESC["queryrtt1"] = 0
-RESSTATDESC["queryrtt2"] = 0
-RESSTATDESC["queryrtt3"] = 0
-RESSTATDESC["queryrtt4"] = 0
-RESSTATDESC["queryrtt5"] = 0
-SOCKSTATDESC={}
-SOCKSTATDESC["counterid"] = 0
-SOCKSTATDESC["udp4open"] = 0
-SOCKSTATDESC["udp6open"] = 0
-SOCKSTATDESC["tcp4open"] = 0
-SOCKSTATDESC["tcp6open"] = 0
-SOCKSTATDESC["unixopen"] = 0
-SOCKSTATDESC["udp4openfail"] = 0
-SOCKSTATDESC["udp6openfail"] = 0
-SOCKSTATDESC["tcp4openfail"] = 0
-SOCKSTATDESC["tcp6openfail"] = 0
-SOCKSTATDESC["unixopenfail"] = 0
-SOCKSTATDESC["udp4close"] = 0
-SOCKSTATDESC["udp6close"] = 0
-SOCKSTATDESC["tcp4close"] = 0
-SOCKSTATDESC["tcp6close"] = 0
-SOCKSTATDESC["unixclose"] = 0
-SOCKSTATDESC["fdwatchclose"] = 0
-SOCKSTATDESC["udp4bindfail"] = 0
-SOCKSTATDESC["udp6bindfail"] = 0
-SOCKSTATDESC["tcp4bindfail"] = 0
-SOCKSTATDESC["tcp6bindfail"] = 0
-SOCKSTATDESC["unixbindfail"] = 0
-SOCKSTATDESC["fdwatchbindfail"] = 0
-SOCKSTATDESC["udp4connectfail"] = 0
-SOCKSTATDESC["udp6connectfail"] = 0
-SOCKSTATDESC["tcp4connectfail"] = 0
-SOCKSTATDESC["tcp6connectfail"] = 0
-SOCKSTATDESC["unixconnectfail"] = 0
-SOCKSTATDESC["fdwatchconnectfail"] = 0
-SOCKSTATDESC["udp4connect"] = 0
-SOCKSTATDESC["udp6connect"] = 0
-SOCKSTATDESC["tcp4connect"] = 0
-SOCKSTATDESC["tcp6connect"] = 0
-SOCKSTATDESC["unixconnect"] = 0
-SOCKSTATDESC["fdwatchconnect"] = 0
-SOCKSTATDESC["tcp4acceptfail"] = 0
-SOCKSTATDESC["tcp6acceptfail"] = 0
-SOCKSTATDESC["unixacceptfail"] = 0
-SOCKSTATDESC["tcp4accept"] = 0
-SOCKSTATDESC["tcp6accept"] = 0
-SOCKSTATDESC["unixaccept"] = 0
-SOCKSTATDESC["udp4sendfail"] = 0
-SOCKSTATDESC["udp6sendfail"] = 0
-SOCKSTATDESC["tcp4sendfail"] = 0
-SOCKSTATDESC["tcp6sendfail"] = 0
-SOCKSTATDESC["unixsendfail"] = 0
-SOCKSTATDESC["fdwatchsendfail"] = 0
-SOCKSTATDESC["udp4recvfail"] = 0
-SOCKSTATDESC["udp6recvfail"] = 0
-SOCKSTATDESC["tcp4recvfail"] = 0
-SOCKSTATDESC["tcp6recvfail"] = 0
-SOCKSTATDESC["unixrecvfail"] = 0
-SOCKSTATDESC["fdwatchrecvfail"] = 0
-SYSSTATDESC={}
-SYSSTATDESC['sockets'] = 0
-SYSSTATDESC['memory'] = 0
-
-sent = -1
-last_sent = -1
-loop = 0
-
-while 1:
-    NSSTATDESC["requestv4"] += random.randint(1,1000)
-    wait = sent + step_time - time.time()
-    if wait <= 0:
-        last_sent = sent;
-        sent = time.time();
-        msg = {'component':'auth', 'version':1, 'timestamp':time.time(),'stats':{'NSSTATDESC':NSSTATDESC,'RESSTATDESC':RESSTATDESC,'SOCKSTATDESC':SOCKSTATDESC,'SYSSTATDESC':SYSSTATDESC}}
-        print (msg)
-        print (cc.group_sendmsg(msg, statgroup))
-        wait = last_sent + step_time - time.time()
-        if wait < 0:
-            wait = step_time
-        loop += 1
-    r,w,e = select.select([cc._socket],[],[], wait)
-    for sock in r:
-        if sock == cc._socket:
-            data,envelope = cc.group_recvmsg(False)
-            print (data)
-            if (envelope["group"] == "Boss"):
-                if ("shutdown" in data):
-                    exit()
-            else:
-                print ("Unknown data: ", envelope,data)

+ 0 - 54
src/bin/stats/test_total.py

@@ -1,54 +0,0 @@
-import sys
-sys.path.insert(0, '.')
-from statsd import *
-
-def test_total():
-    stats = {
-              'auth': {
-                     'from1': {
-                               'component':'auth',
-                               'version':1,
-                               'from':'from1',
-                               'timestamp':20100125,
-                               'stats': {
-                                   'AUTH': {
-                                       'counterid': 1,
-                                       'requestv4': 2,
-                                       'requestv6': 4,
-                                   },
-                                   'SYS': {
-                                       'sockets': 8,
-                                       'memory': 16,
-                                   },
-                                },
-                     },
-                     'from2': {
-                               'component':'auth',
-                               'version':1,
-                               'from':'from1',
-                               'timestamp':20100126,
-                               'stats': {
-                                   'AUTH': {
-                                       'counterid': 256,
-                                       'requestv4': 512,
-                                       'requestv6': 1024,
-                                   },
-                                   'SYS': {
-                                       'sockets': 2048,
-                                       'memory': 4096,
-                                   },
-                                },
-                     },
-              },
-            };
-    t = {}
-    for key in stats:
-        t[key] = total(stats[key])
-    print (stats)
-    print (dicttoxml(stats))
-    print (t)
-    print (dicttoxml(t))
-
-
-if __name__ == "__main__":
-    test_total()

+ 15 - 0
src/bin/stats/tests/Makefile.am

@@ -0,0 +1,15 @@
+SUBDIRS = isc testdata
+PYTESTS = b10-stats_test.py b10-stats_stub_test.py
+EXTRA_DIST = $(PYTESTS) fake_time.py
+CLEANFILES = fake_time.pyc
+
+# later will have configure option to choose this, like: coverage run --branch
+PYCOVERAGE = $(PYTHON)
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
+	B10_FROM_BUILD=$(abs_top_builddir) \
+	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	done

+ 116 - 0
src/bin/stats/tests/b10-stats_stub_test.py

@@ -0,0 +1,116 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+__version__ = "$Revision$"
+
+#
+# Tests for the stats stub module
+#
+import unittest
+import time
+import os
+import imp
+import stats_stub
+from isc.cc.session import Session
+from stats_stub import CCSessionStub, BossModuleStub, AuthModuleStub
+from stats import get_datetime
+
+class TestStats(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session()
+        self.stub = CCSessionStub(session=self.session, verbose=True)
+        self.boss = BossModuleStub(session=self.session, verbose=True)
+        self.auth = AuthModuleStub(session=self.session, verbose=True)
+        self.env = {'from': self.session.lname, 'group': 'Stats',
+                    'instance': '*', 'to':'*',
+                    'type':'send','seq':0}
+        self.result_ok = {'result': [0]}
+
+    def tearDown(self):
+        self.session.close()
+
+    def test_stub(self):
+        """
+        Test for send_command of CCSessionStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(('status', None, env),
+                         self.stub.send_command('status', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('shutdown', None, env),
+                         self.stub.send_command('shutdown', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('show', None, env),
+                         self.stub.send_command('show', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('set', {'atest': 100.0}, env),
+                         self.stub.send_command('set', {'atest': 100.0}))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_boss_stub(self):
+        """
+        Test for send_command of BossModuleStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(('set', {"stats_data":
+                                      {"bind10.boot_time": get_datetime()}
+                                  }, env), self.boss.send_boottime())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_auth_stub(self):
+        """
+        Test for send_command of AuthModuleStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.udp": 1}}, env),
+            self.auth.send_udp_query_count())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.tcp": 1}}, env),
+            self.auth.send_tcp_query_count())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.udp": 100}}, env),
+            self.auth.send_udp_query_count(cmd='set', cnt=100))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.tcp": 99}}, env),
+            self.auth.send_tcp_query_count(cmd='set', cnt=99))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_func_main(self):
+        # explicitly make failed
+        self.session.close()
+        stats_stub.main(session=self.session)
+
+    def test_osenv(self):
+        """
+        test for not having environ "B10_FROM_BUILD"
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            path = os.environ["B10_FROM_BUILD"]
+            os.environ.pop("B10_FROM_BUILD")
+            imp.reload(stats_stub)
+            os.environ["B10_FROM_BUILD"] = path
+            imp.reload(stats_stub)
+
+if __name__ == "__main__":
+    unittest.main()

+ 646 - 0
src/bin/stats/tests/b10-stats_test.py

@@ -0,0 +1,646 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+__version__ = "$Revision$"
+
+#
+# Tests for the stats module
+#
+import os
+import sys
+import time
+import unittest
+import imp
+from isc.cc.session import Session, SessionError
+from isc.config.ccsession import ModuleCCSession, ModuleCCSessionError
+import stats
+from stats import SessionSubject, CCSessionListener, get_timestamp, get_datetime
+from fake_time import _TEST_TIME_SECS, _TEST_TIME_STRF
+
+# setting Constant
+if sys.path[0] == '':
+    TEST_SPECFILE_LOCATION = "./testdata/stats_test.spec"
+else:
+    TEST_SPECFILE_LOCATION = sys.path[0] + "/testdata/stats_test.spec"
+
+class TestStats(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session()
+        self.subject = SessionSubject(session=self.session, verbose=True)
+        self.listener = CCSessionListener(self.subject, verbose=True)
+        self.stats_spec = self.listener.cc_session.get_module_spec().get_config_spec()
+        self.module_name = self.listener.cc_session.get_module_spec().get_module_name()
+        self.stats_data = {
+                'report_time' : get_datetime(),
+                'bind10.boot_time' : "1970-01-01T00:00:00Z",
+                'stats.timestamp' : get_timestamp(),
+                'stats.lname' : self.session.lname,
+                'auth.queries.tcp': 0,
+                'auth.queries.udp': 0,
+                "stats.boot_time": get_datetime(),
+                "stats.start_time": get_datetime(),
+                "stats.last_update_time": get_datetime()
+                }
+        # check starting
+        self.assertFalse(self.subject.running)
+        self.subject.start()
+        self.assertTrue(self.subject.running)
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.module_name, 'Stats')
+
+    def tearDown(self):
+        # check closing
+        self.subject.stop()
+        self.assertFalse(self.subject.running)
+        self.subject.detach(self.listener)
+        self.listener.stop()
+        self.session.close()
+
+    def test_local_func(self):
+        """
+        Test for local function
+        
+        """
+        # test for result_ok
+        self.assertEqual(type(result_ok()), dict)
+        self.assertEqual(result_ok(), {'result': [0]})
+        self.assertEqual(result_ok(1), {'result': [1]})
+        self.assertEqual(result_ok(0,'OK'), {'result': [0, 'OK']})
+        self.assertEqual(result_ok(1,'Not good'), {'result': [1, 'Not good']})
+        self.assertEqual(result_ok(None,"It's None"), {'result': [None, "It's None"]})
+        self.assertNotEqual(result_ok(), {'RESULT': [0]})
+
+        # test for get_timestamp
+        self.assertEqual(get_timestamp(), _TEST_TIME_SECS)
+
+        # test for get_datetime
+        self.assertEqual(get_datetime(), _TEST_TIME_STRF)
+
+    def test_show_command(self):
+        """
+        Test for show command
+        
+        """
+        # test show command without arg
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        # ignore under 0.9 seconds
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command with arg
+        self.session.group_sendmsg({"command": [ "show", {"stats_item_name": "stats.lname"}]}, "Stats")
+        self.assertEqual(len(self.subject.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.subject.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'stats.lname': self.stats_data['stats.lname']}),
+                         result_data)
+        self.assertEqual(len(self.subject.session.message_queue), 0)
+
+        # test show command with arg which has wrong name
+        self.session.group_sendmsg({"command": [ "show", {"stats_item_name": "stats.dummy"}]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        # ignore under 0.9 seconds
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_set_command(self):
+        """
+        Test for set command
+        
+        """
+        # test set command
+        self.stats_data['auth.queries.udp'] = 54321
+        self.assertEqual(self.stats_data['auth.queries.udp'], 54321)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 0)
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'auth.queries.udp': 54321 }
+                                      } ] },
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 2
+        self.stats_data['auth.queries.udp'] = 0
+        self.assertEqual(self.stats_data['auth.queries.udp'], 0)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 0)
+        self.session.group_sendmsg({ "command": [ "set", {'stats_data': {'auth.queries.udp': 0}} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command 2
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 3
+        self.stats_data['auth.queries.tcp'] = 54322
+        self.assertEqual(self.stats_data['auth.queries.udp'], 0)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 54322)
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'auth.queries.tcp': 54322 }
+                                      } ] },
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command 3
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_remove_command(self):
+        """
+        Test for remove command
+        
+        """
+        self.session.group_sendmsg({"command":
+                                   [ "remove", {"stats_item_name": 'bind10.boot_time' }]},
+                              "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.stats_data.pop('bind10.boot_time'), "1970-01-01T00:00:00Z")
+        self.assertFalse('bind10.boot_time' in self.stats_data)
+
+        # test show command with arg
+        self.session.group_sendmsg({"command":
+                                    [ "show", {"stats_item_name": 'bind10.boot_time'}]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertFalse('bind10.boot_time' in result_data['result'][1])
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_reset_command(self):
+        """
+        Test for reset command
+        
+        """
+        self.session.group_sendmsg({"command": [ "reset" ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command
+        self.session.group_sendmsg({"command": [ "show" ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_status_command(self):
+        """
+        Test for status command
+        
+        """
+        self.session.group_sendmsg({"command": [ "status" ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(0, "I'm alive."),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_unknown_command(self):
+        """
+        Test for unknown command
+        
+        """
+        self.session.group_sendmsg({"command": [ "hoge", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(1, "Unknown command: 'hoge'"),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_shutdown_command(self):
+        """
+        Test for shutdown command
+        
+        """
+        self.session.group_sendmsg({"command": [ "shutdown", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.assertTrue(self.subject.running)
+        self.subject.check()
+        self.assertFalse(self.subject.running)
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+
+    def test_some_commands(self):
+        """
+        Test for some commands in a row
+        
+        """
+        # test set command
+        self.stats_data['bind10.boot_time'] = '2010-08-02T14:47:56Z'
+        self.assertEqual(self.stats_data['bind10.boot_time'], '2010-08-02T14:47:56Z')
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'bind10.boot_time': '2010-08-02T14:47:56Z' }
+                                      }]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'bind10.boot_time' }
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'bind10.boot_time': '2010-08-02T14:47:56Z'}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'bind10.boot_time': self.stats_data['bind10.boot_time']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 2nd
+        self.stats_data['auth.queries.udp'] = 98765
+        self.assertEqual(self.stats_data['auth.queries.udp'], 98765)
+        self.session.group_sendmsg({ "command": [
+                                      "set", { 'stats_data': {
+                                            'auth.queries.udp':
+                                              self.stats_data['auth.queries.udp']
+                                            } } 
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({"command": [
+				      "show", {'stats_item_name': 'auth.queries.udp'}
+                                    ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': 98765}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': self.stats_data['auth.queries.udp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 3
+        self.stats_data['auth.queries.tcp'] = 4321
+        self.session.group_sendmsg({"command": [
+                                      "set",
+                                      {'stats_data': {'auth.queries.tcp': 4321 }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check value
+        self.session.group_sendmsg({"command": [ "show", {'stats_item_name': 'auth.queries.tcp'} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.tcp': 4321}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.tcp': self.stats_data['auth.queries.tcp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        self.session.group_sendmsg({"command": [ "show", {'stats_item_name': 'auth.queries.udp'} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': 98765}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': self.stats_data['auth.queries.udp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 4
+        self.stats_data['auth.queries.tcp'] = 67890
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'auth.queries.tcp': 67890 }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command for all values
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_some_commands2(self):
+        """
+        Test for some commands in a row using list-type value
+        
+        """
+        self.stats_data['listtype'] = [1, 2, 3]
+        self.assertEqual(self.stats_data['listtype'], [1, 2, 3])
+        self.session.group_sendmsg({ "command": [
+                                      "set", {'stats_data': {'listtype': [1, 2, 3] }}
+                                      ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'listtype'}
+                                     ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'listtype': [1, 2, 3]}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'listtype': self.stats_data['listtype']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set list-type value
+        self.assertEqual(self.stats_data['listtype'], [1, 2, 3])
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'listtype': [3, 2, 1, 0] }}
+                                    ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'listtype' }
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'listtype': [3, 2, 1, 0]}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_some_commands3(self):
+        """
+        Test for some commands in a row using dictionary-type value
+        
+        """
+        self.stats_data['dicttype'] = {"a": 1, "b": 2, "c": 3}
+        self.assertEqual(self.stats_data['dicttype'], {"a": 1, "b": 2, "c": 3})
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'dicttype': {"a": 1, "b": 2, "c": 3} }
+                                      }]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [ "show", { 'stats_item_name': 'dicttype' } ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'dicttype': {"a": 1, "b": 2, "c": 3}}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'dicttype': self.stats_data['dicttype']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set list-type value
+        self.assertEqual(self.stats_data['dicttype'], {"a": 1, "b": 2, "c": 3})
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'dicttype': {"a": 3, "b": 2, "c": 1, "d": 0} }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [ "show", { 'stats_item_name': 'dicttype' }]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'dicttype': {"a": 3, "b": 2, "c": 1, "d": 0} }),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_config_update(self):
+        """
+        Test for config update
+        
+        """
+        # test show command without arg
+        self.session.group_sendmsg({"command": [ "config_update", {"x-version":999} ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+
+class TestStats2(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session(verbose=True)
+        self.subject = SessionSubject(session=self.session, verbose=True)
+        self.listener = CCSessionListener(self.subject, verbose=True)
+        self.module_name = self.listener.cc_session.get_module_spec().get_module_name()
+        # check starting
+        self.assertFalse(self.subject.running)
+        self.subject.start()
+        self.assertTrue(self.subject.running)
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.module_name, 'Stats')
+
+    def tearDown(self):
+        # check closing
+        self.subject.stop()
+        self.assertFalse(self.subject.running)
+        self.subject.detach(self.listener)
+        self.listener.stop()
+
+    def test_specfile(self):
+        """
+        Test for specfile
+        
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            self.assertEqual(stats.SPECFILE_LOCATION,
+                             os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec")
+        imp.reload(stats)
+        # change path of SPECFILE_LOCATION
+        stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
+        self.assertEqual(stats.SPECFILE_LOCATION, TEST_SPECFILE_LOCATION)
+        self.subject = stats.SessionSubject(session=self.session, verbose=True)
+        self.session = self.subject.session
+        self.listener = stats.CCSessionListener(self.subject, verbose=True)
+
+        self.assertEqual(self.listener.stats_spec, [])
+        self.assertEqual(self.listener.stats_data, {})
+
+        self.assertEqual(self.listener.commands_spec, [
+                {
+                    "command_name": "status",
+                    "command_description": "identify whether stats module is alive or not",
+                    "command_args": []
+                },
+                {
+                    "command_name": "the_dummy",
+                    "command_description": "this is for testing",
+                    "command_args": []
+                }])
+
+    def test_func_initialize_data(self):
+        """
+        Test for initialize_data function 
+        
+        """
+        # prepare for sample data set
+        stats_spec = [
+            {
+                "item_name": "none_sample",
+                "item_type": "null",
+                "item_default": "None"
+            },
+            {
+                "item_name": "boolean_sample",
+                "item_type": "boolean",
+                "item_default": True
+            },
+            {
+                "item_name": "string_sample",
+                "item_type": "string",
+                "item_default": "A something"
+            },
+            {
+                "item_name": "int_sample",
+                "item_type": "integer",
+                "item_default": 9999999
+            },
+            {
+                "item_name": "real_sample",
+                "item_type": "real",
+                "item_default": 0.0009
+            },
+            {
+                "item_name": "list_sample",
+                "item_type": "list",
+                "item_default": [0, 1, 2, 3, 4],
+                "list_item_spec": []
+            },
+            {
+                "item_name": "map_sample",
+                "item_type": "map",
+                "item_default": {'name':'value'},
+                "map_item_spec": []
+            },
+            {
+                "item_name": "other_sample",
+                "item_type": "__unknown__",
+                "item_default": "__unknown__"
+            }
+        ]
+        # data for comparison
+        stats_data = {
+            'none_sample': None,
+            'boolean_sample': True,
+            'string_sample': 'A something',
+            'int_sample': 9999999,
+            'real_sample': 0.0009,
+            'list_sample': [0, 1, 2, 3, 4],
+            'map_sample': {'name':'value'},
+            'other_sample': '__unknown__'
+        }
+        self.assertEqual(self.listener.initialize_data(stats_spec), stats_data)
+
+    def test_func_main(self):
+        # explicitly make failed
+        self.session.close()
+        stats.main(session=self.session)
+
+    def test_osenv(self):
+        """
+        test for not having environ "B10_FROM_BUILD"
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            path = os.environ["B10_FROM_BUILD"]
+            os.environ.pop("B10_FROM_BUILD")
+            imp.reload(stats)
+            os.environ["B10_FROM_BUILD"] = path
+            imp.reload(stats)
+
+def result_ok(*args):
+    if args:
+        return { 'result': list(args) }
+    else:
+        return { 'result': [ 0 ] }
+
+if __name__ == "__main__":
+    unittest.main()

+ 48 - 0
src/bin/stats/tests/fake_time.py

@@ -0,0 +1,48 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+__version__ = "$Revision$"
+
+# This is a dummy time class against a Python standard time class.
+# It is just testing use only.
+# Other methods which time class has is not implemented.
+# (This class isn't orderloaded for time class.)
+
+# These variables are constant. These are example.
+_TEST_TIME_SECS = 1283364938.229088
+_TEST_TIME_STRF = '2010-09-01T18:15:38Z'
+
+def time():
+    """
+    This is a dummy time() method against time.time()
+    """
+    # return float constant value
+    return _TEST_TIME_SECS
+
+def gmtime():
+    """
+    This is a dummy gmtime() method against time.gmtime()
+    """
+    # always return nothing
+    return None
+
+def strftime(*arg):
+    """
+    This is a dummy gmtime() method against time.gmtime()
+    """
+    return _TEST_TIME_STRF
+
+

+ 3 - 0
src/bin/stats/tests/isc/Makefile.am

@@ -0,0 +1,3 @@
+SUBDIRS = cc config util
+EXTRA_DIST = __init__.py
+CLEANFILES = __init__.pyc

+ 0 - 0
src/bin/stats/tests/isc/__init__.py


+ 2 - 0
src/bin/stats/tests/isc/cc/Makefile.am

@@ -0,0 +1,2 @@
+EXTRA_DIST = __init__.py session.py
+CLEANFILES = __init__.pyc session.pyc

+ 1 - 0
src/bin/stats/tests/isc/cc/__init__.py

@@ -0,0 +1 @@
+from isc.cc.session import *

+ 127 - 0
src/bin/stats/tests/isc/cc/session.py

@@ -0,0 +1,127 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+# This module is a mock-up class of isc.cc.session
+
+__version__ = "$Revision$"
+
+import sys
+
+# set a dummy lname
+_TEST_LNAME = '123abc@xxxx'
+
+class Queue():
+    def __init__(self, msg=None, env={}):
+        self.msg = msg
+        self.env = env
+
+    def dump(self):
+        return { 'msg': self.msg, 'env': self.env }
+               
+class SessionError(Exception):
+    pass
+
+class Session:
+    def __init__(self, socket_file=None, verbose=False):
+        self._lname = _TEST_LNAME
+        self.message_queue = []
+        self.old_message_queue = []
+        self._socket = True
+        self.verbose = verbose
+
+    @property
+    def lname(self):
+        return self._lname
+
+    def close(self):
+        self._socket = False
+
+    def _next_sequence(self, que=None):
+        return len(self.message_queue)
+
+    def enqueue(self, msg=None, env={}):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        seq = self._next_sequence()
+        env.update({"seq": 0}) # fixed here
+        que = Queue(msg=msg, env=env)
+        self.message_queue.append(que)
+        if self.verbose:
+            sys.stdout.write("[Session] enqueue: " + str(que.dump()) + "\n")
+        return seq
+
+    def dequeue(self, seq=0):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        que = None
+        try:
+            que = self.message_queue.pop(seq)
+            self.old_message_queue.append(que)
+        except IndexError:
+            que = Queue()
+        if self.verbose:
+            sys.stdout.write("[Session] dequeue: " + str(que.dump()) + "\n")
+        return que
+
+    def get_queue(self, seq=None):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        if seq is None:
+            seq = len(self.message_queue) - 1
+        que = None
+        try:
+            que = self.message_queue[seq]
+        except IndexError:
+            raise IndexError
+            que = Queue()
+        if self.verbose:
+            sys.stdout.write("[Session] get_queue: " + str(que.dump()) + "\n")
+        return que
+
+    def group_sendmsg(self, msg, group, instance="*", to="*"):
+        return self.enqueue(msg=msg, env={
+                "type": "send",
+                "from": self._lname,
+                "to": to,
+                "group": group,
+                "instance": instance })
+
+    def group_recvmsg(self, nonblock=True, seq=0):
+        que = self.dequeue(seq)
+        return que.msg, que.env
+        
+    def group_reply(self, routing, msg):
+        return self.enqueue(msg=msg, env={
+                "type": "send",
+                "from": self._lname,
+                "to": routing["from"],
+                "group": routing["group"],
+                "instance": routing["instance"],
+                "reply": routing["seq"] })
+
+    def get_message(self, group, to='*'):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        que = Queue()
+        for q in self.message_queue:
+            if q.env['group'] == group:
+                self.message_queue.remove(q)
+                self.old_message_queue.append(q)
+                que = q
+        if self.verbose:
+            sys.stdout.write("[Session] get_message: " + str(que.dump()) + "\n")
+        return q.msg
+

+ 2 - 0
src/bin/stats/tests/isc/config/Makefile.am

@@ -0,0 +1,2 @@
+EXTRA_DIST = __init__.py ccsession.py
+CLEANFILES = __init__.pyc ccsession.pyc

+ 1 - 0
src/bin/stats/tests/isc/config/__init__.py

@@ -0,0 +1 @@
+from isc.config.ccsession import *

+ 114 - 0
src/bin/stats/tests/isc/config/ccsession.py

@@ -0,0 +1,114 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+# This module is a mock-up class of isc.cc.session
+
+__version__ = "$Revision$"
+
+import json
+from isc.cc.session import Session
+
+COMMAND_CONFIG_UPDATE = "config_update"
+
+def parse_answer(msg):
+    try:
+        return msg['result'][0], msg['result'][1]
+    except IndexError:
+        return msg['result'][0], None
+
+def create_answer(rcode, arg = None):
+    if arg is None:
+        return { 'result': [ rcode ] }
+    else:
+        return { 'result': [ rcode, arg ] }
+
+def parse_command(msg):
+    try:
+        return msg['command'][0], msg['command'][1]
+    except IndexError:
+        return msg['command'][0], None
+
+def create_command(command_name, params = None):
+    if params is None:
+        return {"command": [command_name]}
+    else:
+        return {"command": [command_name, params]}
+
+def module_spec_from_file(spec_file, check = True):
+    file = open(spec_file)
+    module_spec = json.loads(file.read())
+    return ModuleSpec(module_spec['module_spec'], check)
+
+class ModuleSpec:
+    def __init__(self, module_spec, check = True):
+        self._module_spec = module_spec
+
+    def get_config_spec(self):
+        return self._module_spec['config_data']
+
+    def get_commands_spec(self):
+        return self._module_spec['commands']
+
+    def get_module_name(self):
+        return self._module_spec['module_name']
+
+class ModuleCCSessionError(Exception):
+    pass
+
+class ConfigData:
+    def __init__(self, specification):
+        self.specification = specification
+
+class ModuleCCSession(ConfigData):
+    def __init__(self, spec_file_name, config_handler, command_handler, cc_session = None):
+        module_spec = module_spec_from_file(spec_file_name)
+        ConfigData.__init__(self, module_spec)
+        self._module_name = module_spec.get_module_name()
+        self.set_config_handler(config_handler)
+        self.set_command_handler(command_handler)
+        if not cc_session:
+            self._session = Session(verbose=True)
+        else:
+            self._session = cc_session
+
+    def start(self):
+        pass
+
+    def close(self):
+        self._session.close()
+
+    def check_command(self, nonblock=True):
+        msg, env = self._session.group_recvmsg(nonblock)
+        if not msg or 'result' in msg:
+            return
+        cmd, arg = parse_command(msg)
+        answer = None
+        if cmd == COMMAND_CONFIG_UPDATE and self._config_handler:
+            answer = self._config_handler(arg)
+        elif env['group'] == self._module_name and self._command_handler:
+            answer = self._command_handler(cmd, arg)
+        if answer:
+            self._session.group_reply(env, answer)
+
+    def set_config_handler(self, config_handler):
+        self._config_handler = config_handler
+        # should we run this right now since we've changed the handler?
+
+    def set_command_handler(self, command_handler):
+        self._command_handler = command_handler
+
+    def get_module_spec(self):
+        return self.specification

+ 2 - 0
src/bin/stats/tests/isc/util/Makefile.am

@@ -0,0 +1,2 @@
+EXTRA_DIST = __init__.py process.py
+CLEANFILES = __init__.pyc process.pyc

+ 0 - 0
src/bin/stats/tests/isc/util/__init__.py


+ 20 - 0
src/bin/stats/tests/isc/util/process.py

@@ -0,0 +1,20 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+
+# A dummy function of isc.util.process.rename()
+def rename(name=None):
+    pass

+ 2 - 0
src/bin/stats/tests/isc/utils/Makefile.am

@@ -0,0 +1,2 @@
+EXTRA_DIST = __init__.py process.py
+CLEANFILES = __init__.pyc process.pyc

+ 0 - 0
src/bin/stats/tests/isc/utils/__init__.py


+ 20 - 0
src/bin/stats/tests/isc/utils/process.py

@@ -0,0 +1,20 @@
+# Copyright (C) 2010  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.
+
+# $Id$
+
+# A dummy function of isc.utils.process.rename()
+def rename(name=None):
+    pass

+ 31 - 0
src/bin/stats/tests/stats_test.in

@@ -0,0 +1,31 @@
+#! /bin/sh
+
+# Copyright (C) 2010  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
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/bin/stats:@abs_top_srcdir@/src/bin/stats/tests
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+TEST_PATH=@abs_top_srcdir@/src/bin/stats/tests
+
+cd ${TEST_PATH}
+${PYTHON_EXEC} -O b10-stats_test.py $*
+${PYTHON_EXEC} -O b10-stats_stub_test.py $*

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

@@ -0,0 +1 @@
+EXTRA_DIST = stats_test.spec

+ 19 - 0
src/bin/stats/tests/testdata/stats_test.spec

@@ -0,0 +1,19 @@
+{
+  "module_spec": {
+    "module_name": "Stats",
+    "module_description": "Stats daemon",
+    "config_data": [],
+    "commands": [
+      {
+        "command_name": "status",
+        "command_description": "identify whether stats module is alive or not",
+        "command_args": []
+      },
+      {
+        "command_name": "the_dummy",
+        "command_description": "this is for testing",
+        "command_args": []
+      }
+    ]
+  }
+}

+ 3 - 3
src/bin/tests/process_rename_test.py.in

@@ -17,7 +17,7 @@
 import unittest
 import os
 import os.path
-import isc.utils.process
+import isc.util.process
 import re
 
 class TestRename(unittest.TestCase):
@@ -28,7 +28,7 @@ class TestRename(unittest.TestCase):
         data = ''.join(open(filename).readlines())
         prettyname = 'src' + filename[filename.rfind('../') + 2:]
         self.assertTrue(fun.search(data),
-            "Didn't find a call to isc.utils.process.rename in " + prettyname)
+            "Didn't find a call to isc.util.process.rename in " + prettyname)
 
     def test_calls(self):
         """
@@ -45,7 +45,7 @@ class TestRename(unittest.TestCase):
         # Script name regular expression
         scripts = re.compile(r'((\w|[-.0-9])+)')
         # Line with the call
-        fun = re.compile(r'^\s*isc\.utils\.process\.rename\s*\(.*\)\s*(|#.*)$',
+        fun = re.compile(r'^\s*isc\.util\.process\.rename\s*\(.*\)\s*(|#.*)$',
             re.MULTILINE)
 
         # Find all Makefile and extract names of scripts

+ 2 - 2
src/bin/usermgr/b10-cmdctl-usermgr.py.in

@@ -25,9 +25,9 @@ import csv
 import getpass
 import getopt
 import sys
-import isc.utils.process
+import isc.util.process
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 VERSION_NUMBER = 'bind10'
 DEFAULT_FILE = 'cmdctl-accounts.csv'

+ 2 - 4
src/bin/xfrin/Makefile.am

@@ -7,10 +7,11 @@ pkglibexec_SCRIPTS = b10-xfrin
 b10_xfrindir = $(DESTDIR)$(pkgdatadir)
 b10_xfrin_DATA = xfrin.spec
 
-CLEANFILES = b10-xfrin xfrin.pyc xfrin.spec
+CLEANFILES = b10-xfrin xfrin.pyc 
 
 man_MANS = b10-xfrin.8
 EXTRA_DIST = $(man_MANS) b10-xfrin.xml
+EXTRA_DIST += xfrin.spec
 
 if ENABLE_MAN
 
@@ -19,9 +20,6 @@ b10-xfrin.8: b10-xfrin.xml
 
 endif
 
-xfrin.spec: xfrin.spec.pre
-	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" xfrin.spec.pre >$@
-
 # TODO: does this need $$(DESTDIR) also?
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
 b10-xfrin: xfrin.py

+ 0 - 1
src/bin/xfrin/b10-xfrin.xml

@@ -149,7 +149,6 @@
       the authoritative server to transfer from,
       and <varname>port</varname> to define the port number on the
       authoritative server (defaults to 53).
-<!-- TODO: note: not documenting db_file since that will be removed. -->
      </para>
 <!-- TODO: later hostname for master? -->
 

+ 7 - 7
src/bin/xfrin/tests/xfrin_test.py

@@ -135,9 +135,9 @@ class MockXfrinConnection(XfrinConnection):
         resp.set_opcode(Opcode.QUERY())
         resp.set_rcode(rcode)
         if response:
-            resp.set_header_flag(MessageFlag.QR())
+            resp.set_header_flag(Message.HEADERFLAG_QR)
         [resp.add_question(q) for q in questions]
-        [resp.add_rrset(Section.ANSWER(), a) for a in answers]
+        [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
 
         renderer = MessageRenderer()
         resp.to_wire(renderer)
@@ -421,21 +421,21 @@ class TestXfrin(unittest.TestCase):
         name, rrclass = self._do_parse_zone_name_class()
         master_addrinfo = self._do_parse_master_port()
         db_file = self.args.get('db_file')
-        self.assertEqual(master_addrinfo[4][1], int(TEST_MASTER_PORT))
+        self.assertEqual(master_addrinfo[2][1], int(TEST_MASTER_PORT))
         self.assertEqual(name, TEST_ZONE_NAME)
         self.assertEqual(rrclass, TEST_RRCLASS)
-        self.assertEqual(master_addrinfo[4][0], TEST_MASTER_IPV4_ADDRESS)
+        self.assertEqual(master_addrinfo[2][0], TEST_MASTER_IPV4_ADDRESS)
         self.assertEqual(db_file, TEST_DB_FILE)
 
     def test_parse_cmd_params_default_port(self):
         del self.args['port']
         master_addrinfo = self._do_parse_master_port()
-        self.assertEqual(master_addrinfo[4][1], 53)
+        self.assertEqual(master_addrinfo[2][1], 53)
 
     def test_parse_cmd_params_ip6master(self):
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
         master_addrinfo = self._do_parse_master_port()
-        self.assertEqual(master_addrinfo[4][0], TEST_MASTER_IPV6_ADDRESS)
+        self.assertEqual(master_addrinfo[2][0], TEST_MASTER_IPV6_ADDRESS)
 
     def test_parse_cmd_params_chclass(self):
         self.args['zone_class'] = 'CH'
@@ -454,7 +454,7 @@ class TestXfrin(unittest.TestCase):
         # master address is mandatory.
         del self.args['master']
         master_addrinfo = self._do_parse_master_port()
-        self.assertEqual(master_addrinfo[4][0], DEFAULT_MASTER)
+        self.assertEqual(master_addrinfo[2][0], DEFAULT_MASTER)
 
     def test_parse_cmd_params_bad_ip4(self):
         self.args['master'] = '3.3.3.3.3'

+ 44 - 39
src/bin/xfrin/xfrin.py.in

@@ -1,6 +1,7 @@
 #!@PYTHON@
 
 # Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010  CZ NIC
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -29,7 +30,8 @@ import random
 from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
 from isc.notify import notify_out
-import isc.utils.process
+import isc.util.process
+import isc.net.parse
 try:
     from pydnspp import *
 except ImportError as e:
@@ -37,7 +39,7 @@ except ImportError as e:
     # must keep running, so we warn about it and move forward.
     sys.stderr.write('[b10-xfrin] failed to import DNS module: %s\n' % str(e))
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 # If B10_FROM_BUILD is set in the environment, we use data files
 # from a directory relative to that, otherwise we use the ones
@@ -93,7 +95,7 @@ class XfrinConnection(asyncore.dispatcher):
         self.setblocking(1)
         self._shutdown_event = shutdown_event
         self._verbose = verbose
-        self._master_address = master_addrinfo[4]
+        self._master_address = master_addrinfo[2]
 
     def connect_to_master(self):
         '''Connect to master in TCP.'''
@@ -238,7 +240,7 @@ class XfrinConnection(asyncore.dispatcher):
         if msg_rcode != Rcode.NOERROR():
             raise XfrinException('error response: %s' % msg_rcode.to_text())
 
-        if not msg.get_header_flag(MessageFlag.QR()):
+        if not msg.get_header_flag(Message.HEADERFLAG_QR):
             raise XfrinException('response is not a response ')
 
         if msg.get_qid() != self._query_id:
@@ -249,10 +251,10 @@ class XfrinConnection(asyncore.dispatcher):
 
         self._check_response_header(msg)
 
-        if msg.get_rr_count(Section.ANSWER()) == 0:
+        if msg.get_rr_count(Message.SECTION_ANSWER) == 0:
             raise XfrinException('answer section is empty')
 
-        if msg.get_rr_count(Section.QUESTION()) > 1:
+        if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
             raise XfrinException('query section count greater than 1')
 
     def _handle_answer_section(self, answer_section):
@@ -292,7 +294,7 @@ class XfrinConnection(asyncore.dispatcher):
             msg.from_wire(recvdata)
             self._check_response_status(msg)
             
-            answer_section = msg.get_section(Section.ANSWER())
+            answer_section = msg.get_section(Message.SECTION_ANSWER)
             for rr in self._handle_answer_section(answer_section):
                 yield rr
 
@@ -329,10 +331,14 @@ def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
     sock_map = {}
     conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
                            shutdown_event, master_addrinfo, verbose)
+    ret = XFRIN_FAIL
     if conn.connect_to_master():
         ret = conn.do_xfrin(check_soa)
-        server.publish_xfrin_news(zone_name, rrclass, ret)
-
+    
+    # Publish the zone transfer result news, so zonemgr can reset the
+    # zone timer, and xfrout can notify the zone's slaves if the result
+    # is success.
+    server.publish_xfrin_news(zone_name, rrclass, ret)
     xfrin_recorder.decrement(zone_name)
 
 
@@ -396,20 +402,20 @@ class Xfrin:
         '''This is a straightforward wrapper for cc.check_command, 
         but provided as a separate method for the convenience 
         of unit tests.'''
-        self._module_cc.check_command()
+        self._module_cc.check_command(False)
 
     def config_handler(self, new_config):
         self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
         if ('master_addr' in new_config) or ('master_port' in new_config):
-            # Check if the new master is valid, there should be library for check it.
-            # and user should change the port and address together.
+            # User should change the port and address together.
             try:
                 addr = new_config.get('master_addr') or self._master_addr
                 port = new_config.get('master_port') or self._master_port
-                check_addr_port(addr, port)
+                isc.net.parse.addr_parse(addr)
+                isc.net.parse.port_parse(port)
                 self._master_addr = addr
                 self._master_port = port
-            except:
+            except ValueError:
                 errmsg = "bad format for zone's master: " + str(new_config)
                 log_error(errmsg)
                 return create_answer(1, errmsg)
@@ -438,7 +444,7 @@ class Xfrin:
                 # specify the notifyfrom address and port, according the RFC1996, zone
                 # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
-                (master_addr) = check_addr_port(self._master_addr, self._master_port)
+                (master_addr) = build_addr_info(self._master_addr, self._master_port)
                 ret = self.xfrin_start(zone_name, 
                                        rrclass, 
                                        self._get_db_file(),
@@ -486,7 +492,7 @@ class Xfrin:
     def _parse_master_and_port(self, args):
         port = args.get('port') or self._master_port
         master = args.get('master') or self._master_addr
-        return check_addr_port(master, port)
+        return build_addr_info(master, port)
  
     def _get_db_file(self):
         #TODO, the db file path should be got in auth server's configuration
@@ -513,11 +519,21 @@ class Xfrin:
         param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
         if xfr_result == XFRIN_OK:
             msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
-            self._send_cc_session.group_sendmsg(msg, XFROUT_MODULE_NAME)
-            self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+            # catch the exception, in case msgq has been killed.
+            try:
+                self._send_cc_session.group_sendmsg(msg, XFROUT_MODULE_NAME)
+                self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+            except socket.error as err: 
+                log_error("Fail to send message to %s and %s, msgq may has been killed" 
+                          % (XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME))
         else:
             msg = create_command(ZONE_XFRIN_FAILED, param)
-            self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+            # catch the exception, in case msgq has been killed.
+            try:
+                self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
+            except socket.error as err:
+                log_error("Fail to send message to %s, msgq may has been killed" 
+                          % ZONE_MANAGER_MODULE_NAME)
 
     def startup(self):
         while not self._shutdown_event.is_set():
@@ -559,31 +575,20 @@ def set_signal_handler():
     signal.signal(signal.SIGTERM, signal_handler)
     signal.signal(signal.SIGINT, signal_handler)
 
-def check_addr_port(addrstr, portstr):
-    # XXX: Linux (glibc)'s getaddrinfo incorrectly accepts numeric port
-    # string larger than 65535.  So we need to explicit validate it separately.
+def build_addr_info(addrstr, portstr):
+    """
+    Return tuple (family, socktype, sockaddr) for given address and port.
+    IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
+    (address, port). The socktype is socket.SOCK_STREAM for now.
+    """
     try:
-        portnum = int(portstr)
-        if portnum < 0 or portnum > 65535:
-            raise ValueError("invalid port number (out of range): " + portstr)
+        port = isc.net.parse.port_parse(portstr)
+        addr = isc.net.parse.addr_parse(addrstr)
+        return (addr.family, socket.SOCK_STREAM, (addrstr, port))
     except ValueError as err:
         raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
                              (addrstr, portstr, str(err)))
 
-    try:
-        addrinfo = socket.getaddrinfo(addrstr, portstr, socket.AF_UNSPEC,
-                                      socket.SOCK_STREAM, socket.IPPROTO_TCP,
-                                      socket.AI_NUMERICHOST|
-                                      socket.AI_NUMERICSERV)
-    except socket.gaierror as err:
-        raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
-                             (addrstr, portstr, str(err)))
-    if len(addrinfo) != 1:
-        # with the parameters above the result must be uniquely determined.
-        errmsg = "unexpected result for address/port resolution for %s:%s"
-        raise XfrinException(errmsg % (addrstr, portstr))
-    return addrinfo[0]
-
 def set_cmd_options(parser):
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
             help="display more about what is going on")

+ 0 - 6
src/bin/xfrin/xfrin.spec.pre.in

@@ -48,12 +48,6 @@
             "item_type": "integer",
             "item_optional": true,
             "item_default": 53
-          },
-          {
-            "item_name": "db_file",
-            "item_type": "string",
-            "item_optional": true,
-            "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
           }
         ]
       },

+ 2 - 7
src/bin/xfrout/b10-xfrout.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-xfrout
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: September 8, 2010
+.\"      Date: December 1, 2010
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-XFROUT" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-XFROUT" "8" "December 1, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -67,11 +67,6 @@ receives its configurations from
 The configurable settings are:
 .PP
 
-\fIdb_file\fR
-defines the path to the SQLite3 data store file\&. The default is
-/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
-.PP
-
 \fItransfers_out\fR
 defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
 .if n \{\

+ 1 - 8
src/bin/xfrout/b10-xfrout.xml

@@ -21,7 +21,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>September 8, 2010</date>
+    <date>December 1, 2010</date>
   </refentryinfo>
 
   <refmeta>
@@ -94,13 +94,6 @@
       The configurable settings are:
     </para>
     <para>
-      <varname>db_file</varname>
-      defines the path to the SQLite3 data store file.
-      The default is
-      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
-<!-- TODO: db_file will be removed -->
-    </para>
-    <para>
       <varname>transfers_out</varname>
       defines the maximum number of outgoing zone transfers
       that can run concurrently. The default is 10.

+ 52 - 36
src/bin/xfrout/tests/xfrout_test.py

@@ -47,22 +47,29 @@ class MySocket():
         result = self.sendqueue[:size]
         self.sendqueue = self.sendqueue[size:]
         return result
-    
+
     def read_msg(self):
         sent_data = self.readsent()
         get_msg = Message(Message.PARSE)
         get_msg.from_wire(bytes(sent_data[2:]))
         return get_msg
-    
+
     def clear_send(self):
         del self.sendqueue[:]
 
 # We subclass the Session class we're testing here, only
-# to override the __init__() method, which wants a socket,
+# to override the handle() and _send_data() method
 class MyXfroutSession(XfroutSession):
     def handle(self):
         pass
-    
+
+    def _send_data(self, sock, data):
+        size = len(data)
+        total_count = 0
+        while total_count < size:
+            count = sock.send(data[total_count:])
+            total_count += count
+
 class Dbserver:
     def __init__(self):
         self._shutdown_event = threading.Event()
@@ -80,12 +87,21 @@ class TestXfroutSession(unittest.TestCase):
     def setUp(self):
         request = MySocket(socket.AF_INET,socket.SOCK_STREAM)
         self.log = isc.log.NSLogger('xfrout', '',  severity = 'critical', log_to_console = False )
-        self.xfrsess = MyXfroutSession(request, None, None, self.log)
+        (self.write_sock, self.read_sock) = socket.socketpair()
+        self.xfrsess = MyXfroutSession(request, None, None, self.log, self.read_sock)
         self.xfrsess.server = Dbserver()
         self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
         self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
         self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
 
+    def test_receive_query_message(self):
+        send_msg = b"\xd6=\x00\x00\x00\x01\x00"
+        msg_len = struct.pack('H', socket.htons(len(send_msg)))
+        self.write_sock.send(msg_len)
+        self.write_sock.send(send_msg)
+        recv_msg = self.xfrsess._receive_query_message(self.read_sock)
+        self.assertEqual(recv_msg, send_msg)
+
     def test_parse_query_message(self):
         [get_rcode, get_msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(get_rcode.to_text(), "NOERROR")
@@ -93,7 +109,7 @@ class TestXfroutSession(unittest.TestCase):
     def test_get_query_zone_name(self):
         msg = self.getmsg()
         self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
-  
+
     def test_send_data(self):
         self.xfrsess._send_data(self.sock, self.mdata)
         senddata = self.sock.readsent()
@@ -103,8 +119,8 @@ class TestXfroutSession(unittest.TestCase):
         msg = self.getmsg()
         self.xfrsess._reply_query_with_error_rcode(msg, self.sock, Rcode(3))
         get_msg = self.sock.read_msg()
-        self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN") 
-     
+        self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
+
     def test_clear_message(self):
         msg = self.getmsg()
         qid = msg.get_qid()
@@ -115,10 +131,10 @@ class TestXfroutSession(unittest.TestCase):
         self.assertEqual(msg.get_qid(), qid)
         self.assertEqual(msg.get_opcode(), opcode)
         self.assertEqual(msg.get_rcode(), rcode)
-        self.assertTrue(msg.get_header_flag(MessageFlag.AA()))
+        self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
 
     def test_reply_query_with_format_error(self):
-         
+
         msg = self.getmsg()
         self.xfrsess._reply_query_with_format_error(msg, self.sock)
         get_msg = self.sock.read_msg()
@@ -140,12 +156,12 @@ class TestXfroutSession(unittest.TestCase):
         self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 0)
         get_msg = self.sock.read_msg()
 
-        self.assertEqual(get_msg.get_rr_count(Section.QUESTION()), 1)
-        self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
-        self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
 
         #answer_rrset_iter = section_iter(get_msg, section.ANSWER())
-        answer = get_msg.get_section(Section.ANSWER())[0]#answer_rrset_iter.get_rrset()
+        answer = get_msg.get_section(Message.SECTION_ANSWER)[0]#answer_rrset_iter.get_rrset()
         self.assertEqual(answer.get_name().to_text(), "example.com.")
         self.assertEqual(answer.get_class(), RRClass("IN"))
         self.assertEqual(answer.get_type().to_text(), "SOA")
@@ -160,7 +176,7 @@ class TestXfroutSession(unittest.TestCase):
         msg = self.getmsg()
         msg.make_response()
 
-        msg.add_rrset(Section.ANSWER(), rrset_a)
+        msg.add_rrset(Message.SECTION_ANSWER, rrset_a)
         # give the function a value that is larger than MAX-len(rrset)
         self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 65520)
 
@@ -168,11 +184,11 @@ class TestXfroutSession(unittest.TestCase):
         # (1 with the rrset we added manually, and 1 that triggered
         # the sending in _with_last_soa)
         get_msg = self.sock.read_msg()
-        self.assertEqual(get_msg.get_rr_count(Section.QUESTION()), 1)
-        self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
-        self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
 
-        answer = get_msg.get_section(Section.ANSWER())[0]
+        answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
         self.assertEqual(answer.get_name().to_text(), "example.com.")
         self.assertEqual(answer.get_class(), RRClass("IN"))
         self.assertEqual(answer.get_type().to_text(), "A")
@@ -180,12 +196,12 @@ class TestXfroutSession(unittest.TestCase):
         self.assertEqual(rdata[0].to_text(), "192.0.2.1")
 
         get_msg = self.sock.read_msg()
-        self.assertEqual(get_msg.get_rr_count(Section.QUESTION()), 0)
-        self.assertEqual(get_msg.get_rr_count(Section.ANSWER()), 1)
-        self.assertEqual(get_msg.get_rr_count(Section.AUTHORITY()), 0)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 0)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+        self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
 
-        #answer_rrset_iter = section_iter(get_msg, section.ANSWER())
-        answer = get_msg.get_section(Section.ANSWER())[0]
+        #answer_rrset_iter = section_iter(get_msg, Message.SECTION_ANSWER)
+        answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
         self.assertEqual(answer.get_name().to_text(), "example.com.")
         self.assertEqual(answer.get_class(), RRClass("IN"))
         self.assertEqual(answer.get_type().to_text(), "SOA")
@@ -217,7 +233,7 @@ class TestXfroutSession(unittest.TestCase):
         sqlite3_ds.get_zone_soa = zone_soa
         self.assertEqual(self.xfrsess._zone_exist(True), True)
         self.assertEqual(self.xfrsess._zone_exist(False), False)
-    
+
     def test_check_xfrout_available(self):
         def zone_exist(zone):
             return zone
@@ -243,7 +259,7 @@ class TestXfroutSession(unittest.TestCase):
         self.xfrsess.dns_xfrout_start(self.sock, b"\xd6=\x00\x00\x00\x01\x00")
         sent_data = self.sock.readsent()
         self.assertEqual(len(sent_data), 0)
-    
+
     def default(self, param):
         return "example.com"
 
@@ -255,20 +271,20 @@ class TestXfroutSession(unittest.TestCase):
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         get_msg = self.sock.read_msg()
         self.assertEqual(get_msg.get_rcode().to_text(), "NOTAUTH")
-    
+
     def test_dns_xfrout_start_noerror(self):
         self.xfrsess._get_query_zone_name = self.default
         def noerror(form):
-            return Rcode.NOERROR() 
+            return Rcode.NOERROR()
         self.xfrsess._check_xfrout_available = noerror
-        
+
         def myreply(msg, sock, zonename):
             self.sock.send(b"success")
-        
+
         self.xfrsess._reply_xfrout_query = myreply
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.assertEqual(self.sock.readsent(), b"success")
-    
+
     def test_reply_xfrout_query_noerror(self):
         global sqlite3_ds
         def get_zone_soa(zonename, file):
@@ -281,7 +297,7 @@ class TestXfroutSession(unittest.TestCase):
         sqlite3_ds.get_zone_datas = get_zone_datas
         self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock, "example.com.")
         reply_msg = self.sock.read_msg()
-        self.assertEqual(reply_msg.get_rr_count(Section.ANSWER()), 2)
+        self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 2)
 
 class MyCCSession():
     def __init__(self):
@@ -292,7 +308,7 @@ class MyCCSession():
             return "initdb.file", False
         else:
             return "unknown", False
-    
+
 
 class MyUnixSockServer(UnixSockServer):
     def __init__(self):
@@ -306,7 +322,7 @@ class MyUnixSockServer(UnixSockServer):
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
         self.unix = MyUnixSockServer()
-     
+
     def test_updata_config_data(self):
         self.unix.update_config_data({'transfers_out':10 })
         self.assertEqual(self.unix._max_transfers_out, 10)
@@ -324,7 +340,7 @@ class TestUnixSockServer(unittest.TestCase):
         count = self.unix._transfers_counter
         self.assertEqual(self.unix.increase_transfers_counter(), False)
         self.assertEqual(count, self.unix._transfers_counter)
- 
+
     def test_decrease_transfers_counter(self):
         count = self.unix._transfers_counter
         self.unix.decrease_transfers_counter()
@@ -335,7 +351,7 @@ class TestUnixSockServer(unittest.TestCase):
             os.remove(sock_file)
         except OSError:
             pass
- 
+
     def test_sock_file_in_use_file_exist(self):
         sock_file = 'temp.sock.file'
         self._remove_file(sock_file)

+ 134 - 113
src/bin/xfrout/xfrout.py.in

@@ -1,6 +1,7 @@
 #!@PYTHON@
 
 # Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010  CZ NIC
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -29,11 +30,13 @@ from isc.config.ccsession import *
 from isc.log.log import *
 from isc.cc import SessionError, SessionTimeout
 from isc.notify import notify_out
-import isc.utils.process
+import isc.util.process
 import socket
 import select
 import errno
 from optparse import OptionParser, OptionValueError
+from isc.util import socketserver_mixin
+
 try:
     from libxfr_python import *
     from pydnspp import *
@@ -42,7 +45,7 @@ except ImportError as e:
     # must keep running, so we warn about it and move forward.
     sys.stderr.write('[b10-xfrout] failed to import DNS or XFR module: %s\n' % str(e))
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 if "B10_FROM_BUILD" in os.environ:
     SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrout"
@@ -60,6 +63,7 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
 MAX_TRANSFERS_OUT = 10
 VERBOSE_MODE = False
 
+
 XFROUT_MAX_MESSAGE_SIZE = 65535
 
 def get_rrset_len(rrset):
@@ -70,46 +74,78 @@ def get_rrset_len(rrset):
 
 
 class XfroutSession(BaseRequestHandler):
-    def __init__(self, request, client_address, server, log):
+    def __init__(self, request, client_address, server, log, sock):
         # The initializer for the superclass may call functions
         # that need _log to be set, so we set it first
         self._log = log
+        self._shutdown_sock = sock
         BaseRequestHandler.__init__(self, request, client_address, server)
 
     def handle(self):
-        fd = recv_fd(self.request.fileno())
-        
-        if fd < 0:
-            # This may happen when one xfrout process try to connect to
-            # xfrout unix socket server, to check whether there is another
-            # xfrout running. 
-            self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
-            return
-
-        data_len = self.request.recv(2)
-        msg_len = struct.unpack('!H', data_len)[0]
-        msgdata = self.request.recv(msg_len)
-        sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
-        try:
-            self.dns_xfrout_start(sock, msgdata)
-            #TODO, avoid catching all exceptions
-        except Exception as e:
-            self._log.log_message("error", str(e))
+        '''Handle a request until shutdown or xfrout client is closed.'''
+        # check self.server._shutdown_event to ensure the real shutdown comes.
+        # Linux could trigger a spurious readable event on the _shutdown_sock 
+        # due to a bug, so we need perform a double check. 
+        while not self.server._shutdown_event.is_set(): # Check if xfrout is shutdown
+            try:
+                (rlist, wlist, xlist) = select.select([self._shutdown_sock, self.request], [], [])
+            except select.error as e:
+                if e.args[0] == errno.EINTR:
+                    (rlist, wlist, xlist) = ([], [], [])
+                    continue
+                else:
+                    self._log.log_message("error", "Error with select(): %s" %e)
+                    break
+            # self.server._shutdown_evnet will be set by now, if it is not a false
+            # alarm
+            if self._shutdown_sock in rlist:
+                continue
 
-        try:
-            sock.shutdown(socket.SHUT_RDWR)
-        except socket.error:
-            # Avoid socket error caused by shutting down 
-            # one non-connected socket.
-            pass
+            sock_fd = recv_fd(self.request.fileno())
+
+            if sock_fd < 0:
+                # This may happen when one xfrout process try to connect to
+                # xfrout unix socket server, to check whether there is another
+                # xfrout running.
+                if sock_fd == XFR_FD_RECEIVE_FAIL:
+                    self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
+                break
 
-        sock.close()
-        os.close(fd)
-        pass
+            # receive query msg
+            msgdata = self._receive_query_message(self.request)
+            if not msgdata:
+                break
+
+            try:
+                self.dns_xfrout_start(sock_fd, msgdata)
+                #TODO, avoid catching all exceptions
+            except Exception as e:
+                self._log.log_message("error", str(e))
+
+            os.close(sock_fd)
+
+    def _receive_query_message(self, sock):
+        ''' receive query message from sock'''
+        # receive data length
+        data_len = sock.recv(2)
+        if not data_len:
+            return None
+        msg_len = struct.unpack('!H', data_len)[0]
+        # receive data
+        recv_size = 0
+        msgdata = b''
+        while recv_size < msg_len:
+            data = sock.recv(msg_len - recv_size)
+            if not data:
+                return None
+            recv_size += len(data)
+            msgdata += data
+
+        return msgdata
 
     def _parse_query_message(self, mdata):
         ''' parse query message to [socket,message]'''
-        #TODO, need to add parseHeader() in case the message header is invalid 
+        #TODO, need to add parseHeader() in case the message header is invalid
         try:
             msg = Message(Message.PARSE)
             Message.from_wire(msg, mdata)
@@ -124,37 +160,37 @@ class XfroutSession(BaseRequestHandler):
         return question.get_name().to_text()
 
 
-    def _send_data(self, sock, data):
+    def _send_data(self, sock_fd, data):
         size = len(data)
         total_count = 0
         while total_count < size:
-            count = sock.send(data[total_count:])
+            count = os.write(sock_fd, data[total_count:])
             total_count += count
 
 
-    def _send_message(self, sock, msg):
+    def _send_message(self, sock_fd, msg):
         render = MessageRenderer()
         render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
         msg.to_wire(render)
         header_len = struct.pack('H', socket.htons(render.get_length()))
-        self._send_data(sock, header_len)
-        self._send_data(sock, render.get_data())
+        self._send_data(sock_fd, header_len)
+        self._send_data(sock_fd, render.get_data())
 
 
-    def _reply_query_with_error_rcode(self, msg, sock, rcode_):
+    def _reply_query_with_error_rcode(self, msg, sock_fd, rcode_):
         msg.make_response()
         msg.set_rcode(rcode_)
-        self._send_message(sock, msg)
+        self._send_message(sock_fd, msg)
 
 
-    def _reply_query_with_format_error(self, msg, sock):
+    def _reply_query_with_format_error(self, msg, sock_fd):
         '''query message format isn't legal.'''
         if not msg:
-            return # query message is invalid. send nothing back. 
+            return # query message is invalid. send nothing back.
 
         msg.make_response()
         msg.set_rcode(Rcode.FORMERR())
-        self._send_message(sock, msg)
+        self._send_message(sock_fd, msg)
 
 
     def _zone_is_empty(self, zone):
@@ -164,24 +200,24 @@ class XfroutSession(BaseRequestHandler):
         return True
 
     def _zone_exist(self, zonename):
-        # Find zone in datasource, should this works? maybe should ask 
+        # Find zone in datasource, should this works? maybe should ask
         # config manager.
         soa = sqlite3_ds.get_zone_soa(zonename, self.server.get_db_file())
         if soa:
             return True
         return False
 
-    
+
     def _check_xfrout_available(self, zone_name):
         '''Check if xfr request can be responsed.
            TODO, Get zone's configuration from cfgmgr or some other place
-           eg. check allow_transfer setting, 
+           eg. check allow_transfer setting,
         '''
         if not self._zone_exist(zone_name):
             return Rcode.NOTAUTH()
 
         if self._zone_is_empty(zone_name):
-            return Rcode.SERVFAIL() 
+            return Rcode.SERVFAIL()
 
         #TODO, check allow_transfer
         if not self.server.increase_transfers_counter():
@@ -190,45 +226,45 @@ class XfroutSession(BaseRequestHandler):
         return Rcode.NOERROR()
 
 
-    def dns_xfrout_start(self, sock, msg_query):
+    def dns_xfrout_start(self, sock_fd, msg_query):
         rcode_, msg = self._parse_query_message(msg_query)
         #TODO. create query message and parse header
         if rcode_ != Rcode.NOERROR():
-            return self._reply_query_with_format_error(msg, sock)
+            return self._reply_query_with_format_error(msg, sock_fd)
 
         zone_name = self._get_query_zone_name(msg)
         rcode_ = self._check_xfrout_available(zone_name)
         if rcode_ != Rcode.NOERROR():
             self._log.log_message("info", "transfer of '%s/IN' failed: %s",
                                   zone_name, rcode_.to_text())
-            return self. _reply_query_with_error_rcode(msg, sock, rcode_)
+            return self. _reply_query_with_error_rcode(msg, sock_fd, rcode_)
 
         try:
             self._log.log_message("info", "transfer of '%s/IN': AXFR started" % zone_name)
-            self._reply_xfrout_query(msg, sock, zone_name)
+            self._reply_xfrout_query(msg, sock_fd, zone_name)
             self._log.log_message("info", "transfer of '%s/IN': AXFR end" % zone_name)
         except Exception as err:
             self._log.log_message("error", str(err))
 
         self.server.decrease_transfers_counter()
-        return    
+        return
 
 
     def _clear_message(self, msg):
         qid = msg.get_qid()
         opcode = msg.get_opcode()
         rcode = msg.get_rcode()
-        
+
         msg.clear(Message.RENDER)
         msg.set_qid(qid)
         msg.set_opcode(opcode)
         msg.set_rcode(rcode)
-        msg.set_header_flag(MessageFlag.AA())
-        msg.set_header_flag(MessageFlag.QR())
+        msg.set_header_flag(Message.HEADERFLAG_AA)
+        msg.set_header_flag(Message.HEADERFLAG_QR)
         return msg
 
     def _create_rrset_from_db_record(self, record):
-        '''Create one rrset from one record of datasource, if the schema of record is changed, 
+        '''Create one rrset from one record of datasource, if the schema of record is changed,
         This function should be updated first.
         '''
         rrtype_ = RRType(record[5])
@@ -236,36 +272,37 @@ class XfroutSession(BaseRequestHandler):
         rrset_ = RRset(Name(record[2]), RRClass("IN"), rrtype_, RRTTL( int(record[4])))
         rrset_.add_rdata(rdata_)
         return rrset_
-         
-    def _send_message_with_last_soa(self, msg, sock, rrset_soa, message_upper_len):
+
+    def _send_message_with_last_soa(self, msg, sock_fd, rrset_soa, message_upper_len):
         '''Add the SOA record to the end of message. If it can't be
         added, a new message should be created to send out the last soa .
         '''
         rrset_len = get_rrset_len(rrset_soa)
 
         if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
-            msg.add_rrset(Section.ANSWER(), rrset_soa)
+            msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
         else:
-            self._send_message(sock, msg)
+            self._send_message(sock_fd, msg)
             msg = self._clear_message(msg)
-            msg.add_rrset(Section.ANSWER(), rrset_soa)
+            msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
 
-        self._send_message(sock, msg)
+        self._send_message(sock_fd, msg)
 
 
-    def _reply_xfrout_query(self, msg, sock, zone_name):
+    def _reply_xfrout_query(self, msg, sock_fd, zone_name):
         #TODO, there should be a better way to insert rrset.
         msg.make_response()
-        msg.set_header_flag(MessageFlag.AA())
+        msg.set_header_flag(Message.HEADERFLAG_AA)
         soa_record = sqlite3_ds.get_zone_soa(zone_name, self.server.get_db_file())
         rrset_soa = self._create_rrset_from_db_record(soa_record)
-        msg.add_rrset(Section.ANSWER(), rrset_soa)
+        msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
 
         message_upper_len = get_rrset_len(rrset_soa)
 
         for rr_data in sqlite3_ds.get_zone_datas(zone_name, self.server.get_db_file()):
             if  self.server._shutdown_event.is_set(): # Check if xfrout is shutdown
-                self._log.log_message("error", "shutdown!")
+                self._log.log_message("info", "xfrout process is being shutdown")
+                return
 
             # TODO: RRType.SOA() ?
             if RRType(rr_data[5]) == RRType("SOA"): #ignore soa record
@@ -278,43 +315,45 @@ class XfroutSession(BaseRequestHandler):
             # may have reached the limit
             rrset_len = get_rrset_len(rrset_)
             if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
-                msg.add_rrset(Section.ANSWER(), rrset_)
+                msg.add_rrset(Message.SECTION_ANSWER, rrset_)
                 message_upper_len += rrset_len
                 continue
 
-            self._send_message(sock, msg)
+            self._send_message(sock_fd, msg)
             msg = self._clear_message(msg)
-            msg.add_rrset(Section.ANSWER(), rrset_) # Add the rrset to the new message
+            msg.add_rrset(Message.SECTION_ANSWER, rrset_) # Add the rrset to the new message
             message_upper_len = rrset_len
 
-        self._send_message_with_last_soa(msg, sock, rrset_soa, message_upper_len)
+        self._send_message_with_last_soa(msg, sock_fd, rrset_soa, message_upper_len)
 
-class UnixSockServer(ThreadingUnixStreamServer):
+class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
     '''The unix domain socket server which accept xfr query sent from auth server.'''
 
     def __init__(self, sock_file, handle_class, shutdown_event, config_data, cc, log):
         self._remove_unused_sock_file(sock_file)
         self._sock_file = sock_file
+        socketserver_mixin.NoPollMixIn.__init__(self)
         ThreadingUnixStreamServer.__init__(self, sock_file, handle_class)
         self._lock = threading.Lock()
         self._transfers_counter = 0
         self._shutdown_event = shutdown_event
+        self._write_sock, self._read_sock = socket.socketpair()
         self._log = log
         self.update_config_data(config_data)
         self._cc = cc
-        
+
     def finish_request(self, request, client_address):
         '''Finish one request by instantiating RequestHandlerClass.'''
-        self.RequestHandlerClass(request, client_address, self, self._log)
+        self.RequestHandlerClass(request, client_address, self, self._log, self._read_sock)
 
     def _remove_unused_sock_file(self, sock_file):
-        '''Try to remove the socket file. If the file is being used 
-        by one running xfrout process, exit from python. 
+        '''Try to remove the socket file. If the file is being used
+        by one running xfrout process, exit from python.
         If it's not a socket file or nobody is listening
         , it will be removed. If it can't be removed, exit from python. '''
         if self._sock_file_in_use(sock_file):
-            sys.stderr.write("[b10-xfrout] Fail to start xfrout process, unix socket" 
-                  " file '%s' is being used by another xfrout process\n" % sock_file)
+            self._log.log_message("error", "Fail to start xfrout process, unix socket file '%s'"
+                                 " is being used by another xfrout process\n" % sock_file)
             sys.exit(0)
         else:
             if not os.path.exists(sock_file):
@@ -323,12 +362,12 @@ class UnixSockServer(ThreadingUnixStreamServer):
             try:
                 os.unlink(sock_file)
             except OSError as err:
-                sys.stderr.write('[b10-xfrout] Fail to remove file %s: %s\n' % (sock_file, err))
+                self._log.log_message("error", '[b10-xfrout] Fail to remove file %s: %s\n' % (sock_file, err))
                 sys.exit(0)
-   
+
     def _sock_file_in_use(self, sock_file):
-        '''Check whether the socket file 'sock_file' exists and 
-        is being used by one running xfrout process. If it is, 
+        '''Check whether the socket file 'sock_file' exists and
+        is being used by one running xfrout process. If it is,
         return True, or else return False. '''
         try:
             sock = socket.socket(socket.AF_UNIX)
@@ -336,10 +375,11 @@ class UnixSockServer(ThreadingUnixStreamServer):
         except socket.error as err:
             return False
         else:
-            return True 
+            return True
 
     def shutdown(self):
-        ThreadingUnixStreamServer.shutdown(self)
+        self._write_sock.send(b"shutdown") #terminate the xfrout session thread
+        super().shutdown() # call the shutdown() of class socketserver_mixin.NoPollMixIn
         try:
             os.unlink(self._sock_file)
         except Exception as e:
@@ -381,30 +421,11 @@ class UnixSockServer(ThreadingUnixStreamServer):
         self._transfers_counter -= 1
         self._lock.release()
 
-def listen_on_xfr_query(unix_socket_server):
-    '''Listen xfr query in one single thread. Polls for shutdown 
-    every 0.1 seconds, is there a better time?
-    '''
-
-    while True:
-        try:
-            unix_socket_server.serve_forever(poll_interval = 0.1)
-        except select.error as err:
-            # serve_forever() calls select.select(), which can be 
-            # interrupted.
-            # If it is interrupted, it raises select.error with the 
-            # errno set to EINTR. We ignore this case, and let the
-            # normal program flow continue by trying serve_forever()
-            # again.
-            if err.args[0] != errno.EINTR: raise
-
-   
-
 class XfroutServer:
     def __init__(self):
         self._unix_socket_server = None
         self._log = None
-        self._listen_sock_file = UNIX_SOCKET_FILE 
+        self._listen_sock_file = UNIX_SOCKET_FILE
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
@@ -418,18 +439,16 @@ class XfroutServer:
 
     def _start_xfr_query_listener(self):
         '''Start a new thread to accept xfr query. '''
-        self._unix_socket_server = UnixSockServer(self._listen_sock_file, XfroutSession, 
+        self._unix_socket_server = UnixSockServer(self._listen_sock_file, XfroutSession,
                                                   self._shutdown_event, self._config_data,
                                                   self._cc, self._log);
-        listener = threading.Thread(target = listen_on_xfr_query, args = (self._unix_socket_server,))
+        listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
-        
+
     def _start_notifier(self):
         datasrc = self._unix_socket_server.get_db_file()
         self._notifier = notify_out.NotifyOut(datasrc, self._log)
-        td = threading.Thread(target = notify_out.dispatcher, args = (self._notifier,))
-        td.daemon = True
-        td.start()
+        self._notifier.dispatcher()
 
     def send_notify(self, zone_name, zone_class):
         self._notifier.send_notify(zone_name, zone_class)
@@ -442,7 +461,7 @@ class XfroutServer:
                 answer = create_answer(1, "Unknown config data: " + str(key))
                 continue
             self._config_data[key] = new_config[key]
-        
+
         if self._log:
             self._log.update_config(new_config)
 
@@ -460,9 +479,11 @@ class XfroutServer:
         global xfrout_server
         xfrout_server = None #Avoid shutdown is called twice
         self._shutdown_event.set()
+        self._notifier.shutdown()
         if self._unix_socket_server:
             self._unix_socket_server.shutdown()
 
+        # Wait for all threads to terminate
         main_thread = threading.currentThread()
         for th in threading.enumerate():
             if th is main_thread:
@@ -474,7 +495,7 @@ class XfroutServer:
             self._log.log_message("info", "Received shutdown command.")
             self.shutdown()
             answer = create_answer(0)
-        
+
         elif cmd == notify_out.ZONE_NEW_DATA_READY_CMD:
             zone_name = args.get('zone_name')
             zone_class = args.get('zone_class')
@@ -486,15 +507,15 @@ class XfroutServer:
             else:
                 answer = create_answer(1, "Bad command parameter:" + str(args))
 
-        else: 
+        else:
             answer = create_answer(1, "Unknown command:" + str(cmd))
 
-        return answer    
+        return answer
 
     def run(self):
         '''Get and process all commands sent from cfgmgr or other modules. '''
         while not self._shutdown_event.is_set():
-            self._cc.check_command()
+            self._cc.check_command(False)
 
 
 xfrout_server = None
@@ -528,7 +549,7 @@ if '__main__' == __name__:
         sys.stderr.write("[b10-xfrout] Error creating xfrout, "
                            "is the command channel daemon running?\n")
     except SessionTimeout as e:
-        sys.stderr.write("[b10-xfrout] Error creating xfrout, " 
+        sys.stderr.write("[b10-xfrout] Error creating xfrout, "
                            "is the configuration manager running?\n")
     except ModuleCCSessionError as e:
         sys.stderr.write("[b10-xfrout] exit xfrout process:%s\n" % str(e))

+ 0 - 6
src/bin/xfrout/xfrout.spec.pre.in

@@ -9,12 +9,6 @@
          "item_default": 10
        },
        {
-         "item_name": "db_file",
-         "item_type": "string",
-         "item_optional": false,
-         "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
-       },
-       {
          "item_name": "log_name",
          "item_type": "string",
          "item_optional": false,

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

@@ -1,6 +1,8 @@
 PYTESTS = zonemgr_test.py
 EXTRA_DIST = $(PYTESTS)
 
+CLEANFILES = initdb.file
+
 # later will have configure option to choose this, like: coverage run --branch
 PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS

+ 94 - 36
src/bin/zonemgr/tests/zonemgr_test.py

@@ -27,6 +27,11 @@ ZONE_NAME_CLASS3_IN = ("example.com", "IN")
 ZONE_NAME_CLASS1_CH = ("sd.cn.", "CH")
 ZONE_NAME_CLASS2_IN = ("tw.cn", "IN")
 
+MAX_TRANSFER_TIMEOUT = 14400
+LOWERBOUND_REFRESH = 10
+LOWERBOUND_RETRY = 5 
+JITTER_SCOPE = 0.10
+
 class ZonemgrTestException(Exception):
     pass
 
@@ -40,25 +45,39 @@ class MySession():
 
 class MyZonemgrRefresh(ZonemgrRefresh):
     def __init__(self):
-        self._cc = MySession()
-        self._db_file = "initdb.file"
+        class FakeConfig:
+            def get(self, name):
+                if name == 'lowerbound_refresh':
+                    return LOWERBOUND_REFRESH
+                elif name == 'lowerbound_retry':
+                    return LOWERBOUND_RETRY
+                elif name == 'max_transfer_timeout':
+                    return MAX_TRANSFER_TIMEOUT
+                elif name == 'jitter_scope':
+                    return JITTER_SCOPE
+                else:
+                    raise ValueError('Uknown config option')
+        self._master_socket, self._slave_socket = socket.socketpair()
+        ZonemgrRefresh.__init__(self, MySession(), "initdb.file",
+            self._slave_socket, FakeConfig())
+        current_time = time.time()
         self._zonemgr_refresh_info = { 
          ('sd.cn.', 'IN'): {
-         'last_refresh_time': 1280474398.822142,
-         'next_refresh_time': 1280481598.822153, 
+         'last_refresh_time': current_time,
+         'next_refresh_time': current_time + 6500, 
          'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600', 
          'zone_state': 0},
          ('tw.cn', 'CH'): {
-         'last_refresh_time': 1280474399.116421, 
-         'next_refresh_time': 1280481599.116433, 
+         'last_refresh_time': current_time, 
+         'next_refresh_time': current_time + 6900, 
          'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600', 
          'zone_state': 0}
         } 
 
 class TestZonemgrRefresh(unittest.TestCase):
     def setUp(self):
-        self.stdout_backup = sys.stdout
-        sys.stdout = open(os.devnull, 'w')
+        self.stderr_backup = sys.stderr
+        sys.stderr = open(os.devnull, 'w')
         self.zone_refresh = MyZonemgrRefresh()
 
     def test_random_jitter(self):
@@ -91,7 +110,7 @@ class TestZonemgrRefresh(unittest.TestCase):
         time2 = time.time()
         self.assertTrue((time1 + 7200 * 3 / 4) <= zone_timeout)
         self.assertTrue(zone_timeout <= time2 + 7200)
-        
+
     def test_set_zone_retry_timer(self):
         time1 = time.time()
         self.zone_refresh._set_zone_retry_timer(ZONE_NAME_CLASS1_IN)
@@ -137,6 +156,8 @@ class TestZonemgrRefresh(unittest.TestCase):
          
     def test_zonemgr_reload_zone(self):
         soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        # We need to restore this not to harm other tests
+        old_get_zone_soa = sqlite3_ds.get_zone_soa
         def get_zone_soa(zone_name, db_file):
             return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None, 
                     'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
@@ -144,6 +165,7 @@ class TestZonemgrRefresh(unittest.TestCase):
 
         self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
         self.assertEqual(soa_rdata, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"])
+        sqlite3_ds.get_zone_soa = old_get_zone_soa
 
     def test_get_zone_notifier_master(self):
         notify_master = "192.168.1.1"
@@ -221,6 +243,9 @@ class TestZonemgrRefresh(unittest.TestCase):
 
     def test_zonemgr_add_zone(self):
         soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        # This needs to be restored. The following test actually failed if we left
+        # this unclean
+        old_get_zone_soa = sqlite3_ds.get_zone_soa
 
         def get_zone_soa(zone_name, db_file):
             return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None, 
@@ -241,7 +266,8 @@ class TestZonemgrRefresh(unittest.TestCase):
             return None
         sqlite3_ds.get_zone_soa = get_zone_soa2
         self.assertRaises(ZonemgrException, self.zone_refresh.zonemgr_add_zone, \
-                                          ZONE_NAME_CLASS1_IN)
+                                         ZONE_NAME_CLASS1_IN)
+        sqlite3_ds.get_zone_soa = old_get_zone_soa
 
     def test_build_zonemgr_refresh_info(self):
         soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
@@ -311,6 +337,11 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertTrue((time1 + 3 * 3600 / 4) <= next_refresh_time)
         self.assertTrue(next_refresh_time <= time2 + 3600)
         self.assertEqual(ZONE_OK, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
+
+        self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["last_refresh_time"] = time1 - 2419200 
+        self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
+        self.assertEqual(ZONE_EXPIRED, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
+
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ("org.cn.", "CH"))
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_IN) 
 
@@ -332,17 +363,6 @@ class TestZonemgrRefresh(unittest.TestCase):
         zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
         self.assertEqual(ZONE_NAME_CLASS1_IN, zone_need_refresh)
 
-        self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["last_refresh_time"] = time1 - 2419200
-        self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_EXPIRED
-        zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
-        self.assertEqual(None, zone_need_refresh)
-
-        self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
-        self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"] = "192.168.0.1"
-        zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
-        self.assertEqual(ZONE_NAME_CLASS1_IN, zone_need_refresh)
-        self.assertEqual(ZONE_EXPIRED, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
-
         self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS2_CH]["refresh_timeout"] = time1 
         zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
         self.assertEqual(ZONE_NAME_CLASS2_CH, zone_need_refresh)
@@ -378,7 +398,7 @@ class TestZonemgrRefresh(unittest.TestCase):
         """This case will run timer in daemon thread. 
         The zone's next_refresh_time is less than now, so zonemgr will do zone refresh 
         immediately. The zone's state will become "refreshing". 
-        Then closing the socket ,the timer will stop, and throw a ZonemgrException."""
+        """
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info = {
                 ("sd.cn.", "IN"):{
@@ -387,24 +407,38 @@ class TestZonemgrRefresh(unittest.TestCase):
                     'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600', 
                     'zone_state': ZONE_OK}
                 }
-        master_socket, slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
-        self.zone_refresh._socket = master_socket 
-        master_socket.close()
-        self.assertRaises(ZonemgrException, self.zone_refresh.run_timer)
-
-        self.zone_refresh._socket = slave_socket
-        listener = threading.Thread(target = self.zone_refresh.run_timer, args = ())
-        listener.setDaemon(True)
-        listener.start()
-        time.sleep(1)
-
+        self.zone_refresh._check_sock = self.zone_refresh._master_socket 
+        listener = self.zone_refresh.run_timer(daemon=True)
+        # Shut down the timer thread
+        self.zone_refresh.shutdown()
+        # After running timer, the zone's state should become "refreshing".
         zone_state = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"]
         self.assertTrue("refresh_timeout" in self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN].keys())
         self.assertTrue(zone_state == ZONE_REFRESHING)
 
+    def test_update_config_data(self):
+        config_data = {
+                    "lowerbound_refresh" : 60,
+                    "lowerbound_retry" : 30,
+                    "max_transfer_timeout" : 19800,
+                    "jitter_scope" : 0.25
+                }
+        self.zone_refresh.update_config_data(config_data)
+        self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+        self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+        self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+        self.assertEqual(0.25, self.zone_refresh._jitter_scope)
+
+    def test_shutdown(self):
+        self.zone_refresh._check_sock = self.zone_refresh._master_socket 
+        listener = self.zone_refresh.run_timer()
+        self.assertTrue(listener.is_alive())
+        # Shut down the timer thread
+        self.zone_refresh.shutdown()
+        self.assertFalse(listener.is_alive())
 
     def tearDown(self):
-        sys.stdout = self.stdout_backup
+        sys.stderr= self.stderr_backup
 
 
 class MyCCSession():
@@ -422,10 +456,16 @@ class MyZonemgr(Zonemgr):
 
     def __init__(self):
         self._db_file = "initdb.file"
+        self._zone_refresh = None
         self._shutdown_event = threading.Event()
         self._cc = MySession()
         self._module_cc = MyCCSession()
-        self._config_data = {"zone_name" : "org.cn", "zone_class" : "CH", "master" : "127.0.0.1"}
+        self._config_data = {
+                    "lowerbound_refresh" : 10, 
+                    "lowerbound_retry" : 5, 
+                    "max_transfer_timeout" : 14400,
+                    "jitter_scope" : 0.1
+                    }
 
     def _start_zone_refresh_timer(self):
         pass
@@ -436,12 +476,21 @@ class TestZonemgr(unittest.TestCase):
         self.zonemgr = MyZonemgr()
 
     def test_config_handler(self):
-        config_data1 = {"zone_name" : "sd.cn.", "zone_class" : "CH", "master" : "192.168.1.1"}
+        config_data1 = {
+                    "lowerbound_refresh" : 60, 
+                    "lowerbound_retry" : 30, 
+                    "max_transfer_timeout" : 14400,
+                    "jitter_scope" : 0.1
+                    }
         self.zonemgr.config_handler(config_data1)
         self.assertEqual(config_data1, self.zonemgr._config_data)
         config_data2 = {"zone_name" : "sd.cn.", "port" : "53", "master" : "192.168.1.1"}
         self.zonemgr.config_handler(config_data2)
         self.assertEqual(config_data1, self.zonemgr._config_data)
+        # jitter should not be bigger than half of the original value
+        config_data3 = {"jitter_scope" : 0.7}
+        self.zonemgr.config_handler(config_data3)
+        self.assertEqual(0.5, self.zonemgr._config_data.get("jitter_scope"))
 
     def test_get_db_file(self):
         self.assertEqual("initdb.file", self.zonemgr.get_db_file())
@@ -457,6 +506,15 @@ class TestZonemgr(unittest.TestCase):
         params1 = {"zone_class" : "CH"}
         self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
 
+    def test_config_data_check(self):
+        # jitter should not be bigger than half of the original value
+        config_data2 = {"jitter_scope" : 0.2}
+        config_data3 = {"jitter_scope" : 0.6}
+        self.zonemgr._config_data_check(config_data2)
+        self.assertEqual(0.2, config_data2.get("jitter_scope"))
+        self.zonemgr._config_data_check(config_data3)
+        self.assertEqual(0.5, config_data3.get("jitter_scope"))
+
     def tearDown(self):
         pass
 

+ 158 - 97
src/bin/zonemgr/zonemgr.py.in

@@ -1,6 +1,7 @@
 #!@PYTHON@
 
 # Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010  CZ NIC
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +16,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-"""\
+"""
 This file implements the Secondary Manager program.
 
 The secondary manager is one of the co-operating processes
@@ -36,9 +37,9 @@ import errno
 from isc.datasrc import sqlite3_ds
 from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
-import isc.utils.process
+import isc.util.process
 
-isc.utils.process.rename()
+isc.util.process.rename()
 
 # If B10_FROM_BUILD is set in the environment, we use data files
 # from a directory relative to that, otherwise we use the ones
@@ -72,13 +73,6 @@ ZONE_OK = 0
 ZONE_REFRESHING = 1
 ZONE_EXPIRED = 2
 
-# smallest refresh timeout
-LOWERBOUND_REFRESH = 10
-# smallest retry timeout
-LOWERBOUND_RETRY = 5
-# max zone transfer timeout
-MAX_TRANSFER_TIMEOUT = 14400
-
 # offsets of fields in the SOA RDATA
 REFRESH_OFFSET = 3
 RETRY_OFFSET = 4
@@ -96,21 +90,26 @@ class ZonemgrException(Exception):
 
 class ZonemgrRefresh:
     """This class will maintain and manage zone refresh info.
-    It also provides methods to keep track of zone timers and 
+    It also provides methods to keep track of zone timers and
     do zone refresh.
+    Zone timers can be started by calling run_timer(), and it
+    can be stopped by calling shutdown() in another thread.
+
     """
 
-    def __init__(self, cc, db_file, slave_socket):
+    def __init__(self, cc, db_file, slave_socket, config_data):
         self._cc = cc
-        self._socket = slave_socket 
+        self._check_sock = slave_socket
         self._db_file = db_file
-        self._zonemgr_refresh_info = {} 
+        self.update_config_data(config_data)
+        self._zonemgr_refresh_info = {}
         self._build_zonemgr_refresh_info()
-    
+        self._running = False
+
     def _random_jitter(self, max, jitter):
         """Imposes some random jitters for refresh and
         retry timers to avoid many zones need to do refresh
-        at the same time. 
+        at the same time.
         The value should be between (max - jitter) and max.
         """
         if 0 == jitter:
@@ -121,29 +120,30 @@ class ZonemgrRefresh:
         return time.time()
 
     def _set_zone_timer(self, zone_name_class, max, jitter):
-        """Set zone next refresh time."""
+        """Set zone next refresh time.
+        jitter should not be bigger than half the original value."""
         self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
                                             self._random_jitter(max, jitter))
 
     def _set_zone_refresh_timer(self, zone_name_class):
         """Set zone next refresh time after zone refresh success.
-           now + refresh*3/4 <= next_refresh_time <= now + refresh
+           now + refresh - jitter  <= next_refresh_time <= now + refresh
            """
         zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
-        zone_refresh_time = max(LOWERBOUND_REFRESH, zone_refresh_time)
-        self._set_zone_timer(zone_name_class, zone_refresh_time, (1 * zone_refresh_time) / 4)
+        zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
+        self._set_zone_timer(zone_name_class, zone_refresh_time, self._jitter_scope * zone_refresh_time)
 
     def _set_zone_retry_timer(self, zone_name_class):
         """Set zone next refresh time after zone refresh fail.
-           now + retry*3/4 <= next_refresh_time <= now + retry
+           now + retry - jitter <= next_refresh_time <= now + retry
            """
         zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
-        zone_retry_time = max(LOWERBOUND_RETRY, zone_retry_time)
-        self._set_zone_timer(zone_name_class, zone_retry_time, (1 * zone_retry_time) / 4)
+        zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
+        self._set_zone_timer(zone_name_class, zone_retry_time, self._jitter_scope * zone_retry_time)
 
     def _set_zone_notify_timer(self, zone_name_class):
         """Set zone next refresh time after receiving notify
-           next_refresh_time = now 
+           next_refresh_time = now
         """
         self._set_zone_timer(zone_name_class, 0, 0)
 
@@ -170,7 +170,11 @@ class ZonemgrRefresh:
             raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
                                    "belong to zonemgr" % zone_name_class)
             return
-        self._set_zone_state(zone_name_class, ZONE_OK)
+        # Is zone expired?
+        if (self._zone_is_expired(zone_name_class)):
+            self._set_zone_state(zone_name_class, ZONE_EXPIRED)
+        else:
+            self._set_zone_state(zone_name_class, ZONE_OK)
         self._set_zone_retry_timer(zone_name_class)
 
     def zone_handle_notify(self, zone_name_class, master):
@@ -195,7 +199,7 @@ class ZonemgrRefresh:
             raise ZonemgrException("[b10-zonemgr] zone (%s, %s) doesn't have soa." % zone_name_class)
         zone_info["zone_soa_rdata"] = zone_soa[7]
         zone_info["zone_state"] = ZONE_OK
-        zone_info["last_refresh_time"] = self._get_current_time() 
+        zone_info["last_refresh_time"] = self._get_current_time()
         zone_info["next_refresh_time"] = self._get_current_time() + \
                                          float(zone_soa[7].split(" ")[REFRESH_OFFSET])
         self._zonemgr_refresh_info[zone_name_class] = zone_info
@@ -229,7 +233,7 @@ class ZonemgrRefresh:
 
     def _get_zone_notifier_master(self, zone_name_class):
         if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
-            return self._zonemgr_refresh_info[zone_name_class]["notify_master"] 
+            return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
 
         return None
 
@@ -244,7 +248,7 @@ class ZonemgrRefresh:
         return self._zonemgr_refresh_info[zone_name_class]["zone_state"]
 
     def _set_zone_state(self, zone_name_class, zone_state):
-        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state 
+        self._zonemgr_refresh_info[zone_name_class]["zone_state"] = zone_state
 
     def _get_zone_refresh_timeout(self, zone_name_class):
         return self._zonemgr_refresh_info[zone_name_class]["refresh_timeout"]
@@ -264,7 +268,7 @@ class ZonemgrRefresh:
         try:
             self._cc.group_sendmsg(msg, module_name)
         except socket.error:
-            sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name) 
+            sys.stderr.write("[b10-zonemgr] Failed to send to module %s, the session has been closed." % module_name)
 
     def _find_need_do_refresh_zone(self):
         """Find the first zone need do refresh, if no zone need
@@ -272,26 +276,15 @@ class ZonemgrRefresh:
         """
         zone_need_refresh = None
         for zone_name_class in self._zonemgr_refresh_info.keys():
-            # Does the zone expired?
-            if (ZONE_EXPIRED != self._get_zone_state(zone_name_class) and 
-                self._zone_is_expired(zone_name_class)):
-                log_msg("Zone (%s, %s) is expired." % zone_name_class)
-                self._set_zone_state(zone_name_class, ZONE_EXPIRED)
-
             zone_state = self._get_zone_state(zone_name_class)
-            # If zone is expired and doesn't receive notify, skip the zone
-            if (ZONE_EXPIRED == zone_state and 
-                (not self._get_zone_notifier_master(zone_name_class))):
-                continue
-
             # If hasn't received refresh response but are within refresh timeout, skip the zone
             if (ZONE_REFRESHING == zone_state and
                 (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
                 continue
-                    
-            # Get the zone with minimum next_refresh_time 
-            if ((None == zone_need_refresh) or 
-                (self._get_zone_next_refresh_time(zone_name_class) < 
+
+            # Get the zone with minimum next_refresh_time
+            if ((zone_need_refresh is None) or
+                (self._get_zone_next_refresh_time(zone_name_class) <
                  self._get_zone_next_refresh_time(zone_need_refresh))):
                 zone_need_refresh = zone_name_class
 
@@ -299,14 +292,14 @@ class ZonemgrRefresh:
             if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
                 break
 
-        return zone_need_refresh 
+        return zone_need_refresh
+
 
-    
     def _do_refresh(self, zone_name_class):
         """Do zone refresh."""
         log_msg("Do refresh for zone (%s, %s)." % zone_name_class)
         self._set_zone_state(zone_name_class, ZONE_REFRESHING)
-        self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + MAX_TRANSFER_TIMEOUT) 
+        self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
         notify_master = self._get_zone_notifier_master(zone_name_class)
         # If the zone has notify master, send notify command to xfrin module
         if notify_master:
@@ -314,7 +307,7 @@ class ZonemgrRefresh:
                      "zone_class" : zone_name_class[1],
                      "master" : notify_master
                      }
-            self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param) 
+            self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param)
             self._clear_zone_notifier_master(zone_name_class)
         # Send refresh command to xfrin module
         else:
@@ -330,62 +323,117 @@ class ZonemgrRefresh:
 
         return False
 
-    def run_timer(self):
-        """Keep track of zone timers."""
-        while True:
-            # Zonemgr has no zone.
+    def _run_timer(self, start_event):
+        while self._running:
+            # Notify run_timer that we already started and are inside the loop.
+            # It is set only once, but when it was outside the loop, there was
+            # a race condition and _running could be set to false before we
+            # could enter it
+            if start_event:
+                start_event.set()
+                start_event = None
+            # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
             if self._zone_mgr_is_empty():
-                time.sleep(LOWERBOUND_RETRY) # A better time?
-                continue
-
-            zone_need_refresh = self._find_need_do_refresh_zone()
-            # If don't get zone with minimum next refresh time, set timer timeout = LOWERBOUND_REFRESH
-            if not zone_need_refresh:
-                timeout = LOWERBOUND_RETRY
+                timeout = self._lowerbound_retry
             else:
-                timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
-                if (timeout < 0):
-                    self._do_refresh(zone_need_refresh)
-                    continue
+                zone_need_refresh = self._find_need_do_refresh_zone()
+                # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
+                if not zone_need_refresh:
+                    timeout = self._lowerbound_retry
+                else:
+                    timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
+                    if (timeout < 0):
+                        self._do_refresh(zone_need_refresh)
+                        continue
 
-            """ Wait for the socket notification for a maximum time of timeout 
+            """ Wait for the socket notification for a maximum time of timeout
             in seconds (as float)."""
             try:
-                (rlist, wlist, xlist) = select.select([self._socket], [], [], timeout)
-                if rlist:
-                    self._socket.recv(32)
-            except ValueError as e:
-                raise ZonemgrException("[b10-zonemgr] Socket has been closed\n")
-                break
+                rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
             except select.error as e:
                 if e.args[0] == errno.EINTR:
                     (rlist, wlist, xlist) = ([], [], [])
                 else:
-                    raise ZonemgrException("[b10-zonemgr] Error with select(): %s\n" % e)
+                    sys.stderr.write("[b10-zonemgr] Error with select(); %s\n" % e)
                     break
 
+            for fd in rlist:
+                if fd == self._read_sock: # awaken by shutdown socket
+                    # self._running will be False by now, if it is not a false
+                    # alarm (linux kernel is said to trigger spurious wakeup
+                    # on a filehandle that is not really readable).
+                    continue
+                if fd == self._check_sock: # awaken by check socket
+                    self._check_sock.recv(32)
+
+    def run_timer(self, daemon=False):
+        """
+        Keep track of zone timers. Spawns and starts a thread. The thread object is returned.
+
+        You can stop it by calling shutdown().
+        """
+        # Small sanity check
+        if self._running:
+            raise RuntimeError("Trying to run the timers twice at the same time")
+
+        # Prepare the launch
+        self._running = True
+        (self._read_sock, self._write_sock) = socket.socketpair()
+        start_event = threading.Event()
+
+        # Start the thread
+        self._thread = threading.Thread(target = self._run_timer,
+            args = (start_event,))
+        if daemon:
+            self._thread.setDaemon(True)
+        self._thread.start()
+        start_event.wait()
+
+        # Return the thread to anyone interested
+        return self._thread
+
+    def shutdown(self):
+        """
+        Stop the run_timer() thread. Block until it finished. This must be
+        called from a different thread.
+        """
+        if not self._running:
+            raise RuntimeError("Trying to shutdown, but not running")
+
+        # Ask the thread to stop
+        self._running = False
+        self._write_sock.send(b'shutdown') # make self._read_sock readble
+        # Wait for it to actually finnish
+        self._thread.join()
+        # Wipe out what we do not need
+        self._thread = None
+        self._read_sock = None
+        self._write_sock = None
+
+    def update_config_data(self, new_config):
+        """ update ZonemgrRefresh config """
+        self._lowerbound_refresh = new_config.get('lowerbound_refresh')
+        self._lowerbound_retry = new_config.get('lowerbound_retry')
+        self._max_transfer_timeout = new_config.get('max_transfer_timeout')
+        self._jitter_scope = new_config.get('jitter_scope')
 
 class Zonemgr:
     """Zone manager class."""
     def __init__(self):
+        self._zone_refresh = None
         self._setup_session()
         self._db_file = self.get_db_file()
-        # Create socket pair for communicating between main thread and zonemgr timer thread 
+        # Create socket pair for communicating between main thread and zonemgr timer thread
         self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
-        self._zone_refresh= ZonemgrRefresh(self._cc, self._db_file, self._slave_socket)
-        self._start_zone_refresh_timer()
+        self._zone_refresh = ZonemgrRefresh(self._cc, self._db_file, self._slave_socket, self._config_data)
+        self._zone_refresh.run_timer()
 
         self._lock = threading.Lock()
         self._shutdown_event = threading.Event()
-
-    def _start_zone_refresh_timer(self):
-        """Start a new thread to keep track of zone timers"""
-        listener = threading.Thread(target = self._zone_refresh.run_timer, args = ())
-        listener.setDaemon(True)
-        listener.start()
+        self.running = False
 
     def _setup_session(self):
-        """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving 
+        """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
         commands and config data sent from other modules, another one (self._cc)
         is used to send commands to proper modules."""
         self._cc = isc.cc.Session()
@@ -394,6 +442,7 @@ class Zonemgr:
                                                   self.command_handler)
         self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         self._config_data = self._module_cc.get_full_config()
+        self._config_data_check(self._config_data)
         self._module_cc.start()
 
     def get_db_file(self):
@@ -408,27 +457,38 @@ class Zonemgr:
     def shutdown(self):
         """Shutdown the zonemgr process. the thread which is keeping track of zone
         timers should be terminated.
-        """ 
+        """
+        self._zone_refresh.shutdown()
+
         self._slave_socket.close()
         self._master_socket.close()
-
         self._shutdown_event.set()
-        main_thread = threading.currentThread()
-        for th in threading.enumerate():
-            if th is main_thread:
-                continue
-            th.join()
+        self.running = False
 
     def config_handler(self, new_config):
-        """Update config data."""
+        """ Update config data. """
         answer = create_answer(0)
         for key in new_config:
             if key not in self._config_data:
                 answer = create_answer(1, "Unknown config data: " + str(key))
                 continue
             self._config_data[key] = new_config[key]
+
+        self._config_data_check(self._config_data)
+        if (self._zone_refresh):
+            self._zone_refresh.update_config_data(self._config_data)
+
         return answer
 
+    def _config_data_check(self, config_data):
+        """Check whether the new config data is valid or 
+        not. """ 
+        # jitter should not be bigger than half of the original value
+        if config_data.get('jitter_scope') > 0.5:
+            config_data['jitter_scope'] = 0.5
+            log_msg("[b10-zonemgr] jitter_scope is too big, its value will "
+                      "be set to 0.5") 
+
     def _parse_cmd_params(self, args, command):
         zone_name = args.get("zone_name")
         if not zone_name:
@@ -450,7 +510,7 @@ class Zonemgr:
 
     def command_handler(self, command, args):
         """Handle command receivd from command channel.
-        ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND 
+        ZONE_NOTIFY_COMMAND is issued by Auth process; ZONE_XFRIN_SUCCESS_COMMAND
         and ZONE_XFRIN_FAILED_COMMAND are issued by Xfrin process; shutdown is issued
         by a user or Boss process. """
         answer = create_answer(0)
@@ -462,21 +522,21 @@ class Zonemgr:
             with self._lock:
                 self._zone_refresh.zone_handle_notify(zone_name_class, master)
             # Send notification to zonemgr timer thread
-            self._master_socket.send(b" ")
+            self._master_socket.send(b" ")# make self._slave_socket readble
 
         elif command == ZONE_XFRIN_SUCCESS_COMMAND:
             """ Handle xfrin success command"""
             zone_name_class = self._parse_cmd_params(args, command)
             with self._lock:
                 self._zone_refresh.zone_refresh_success(zone_name_class)
-            self._master_socket.send(b" ")
+            self._master_socket.send(b" ")# make self._slave_socket readble
 
         elif command == ZONE_XFRIN_FAILED_COMMAND:
             """ Handle xfrin fail command"""
             zone_name_class = self._parse_cmd_params(args, command)
             with self._lock:
                 self._zone_refresh.zone_refresh_fail(zone_name_class)
-            self._master_socket.send(b" ")
+            self._master_socket.send(b" ")# make self._slave_socket readble
 
         elif command == "shutdown":
             self.shutdown()
@@ -487,8 +547,9 @@ class Zonemgr:
         return answer
 
     def run(self):
+        self.running = True
         while not self._shutdown_event.is_set():
-            self._module_cc.check_command()
+            self._module_cc.check_command(False)
 
 zonemgrd = None
 
@@ -518,14 +579,14 @@ if '__main__' == __name__:
     except KeyboardInterrupt:
         sys.stderr.write("[b10-zonemgr] exit zonemgr process\n")
     except isc.cc.session.SessionError as e:
-        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, " 
+        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
                            "is the command channel daemon running?\n")
     except isc.cc.session.SessionTimeout as e:
-        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, " 
+        sys.stderr.write("[b10-zonemgr] Error creating zonemgr, "
                            "is the configuration manager running?\n")
     except isc.config.ModuleCCSessionError as e:
         sys.stderr.write("[b10-zonemgr] exit zonemgr process: %s\n" % str(e))
 
-    if zonemgrd:
+    if zonemgrd and zonemgrd.running:
         zonemgrd.shutdown()
 

+ 24 - 0
src/bin/zonemgr/zonemgr.spec.pre.in

@@ -2,6 +2,30 @@
   "module_spec": {
      "module_name": "Zonemgr",
       "config_data":[
+       {
+         "item_name": "lowerbound_refresh",
+         "item_type": "integer",
+         "item_optional": false,
+         "item_default": 10
+       },
+       {
+         "item_name": "lowerbound_retry",
+         "item_type": "integer",
+         "item_optional": false,
+         "item_default": 5 
+       },
+       {
+         "item_name": "max_transfer_timeout",
+         "item_type": "integer",
+         "item_optional": false,
+         "item_default": 14400 
+       },
+       {
+         "item_name": "jitter_scope",
+         "item_type": "real",
+         "item_optional": false,
+         "item_default": 0.25
+       }
       ],
       "commands": [
         {

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

@@ -1,6 +1,7 @@
 SUBDIRS = . tests example
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 CLEANFILES = *.gcno *.gcda

+ 2 - 2
src/lib/bench/benchmark.h

@@ -200,7 +200,7 @@ private:
     BenchMark(const BenchMark& source);
     BenchMark& operator=(const BenchMark& source);
 public:
-    /// \bench Constructor for immediate run.
+    /// \brief Constructor for immediate run.
     ///
     /// This is the constructor that is expected to be used normally.
     /// It runs the benchmark within the constructor and prints the result,
@@ -217,7 +217,7 @@ public:
         initialize(true);
     }
 
-    /// \bench Constructor for finer-grained control.
+    /// \brief Constructor for finer-grained control.
     ///
     /// This constructor takes the third parameter, \c immediate, to control
     /// whether to run the benchmark within the constructor.

+ 6 - 3
src/lib/bench/example/search_bench.cc

@@ -16,6 +16,7 @@
 
 #include <unistd.h>             // for getpid
 
+#include <cassert>
 #include <cstdlib>              // for rand
 #include <algorithm>
 #include <iostream>
@@ -42,9 +43,11 @@ public:
         vector<int>::const_iterator end_key = keys_.end();
         for (iter = keys_.begin(); iter != end_key; ++iter) {
             if (Sorted) {
-                binary_search(data_.begin(), data_.end(), *iter);
+                // perform simple sanity check with assert() to ensure
+                // compiler optimization won't skip the search.
+                assert(binary_search(data_.begin(), data_.end(), *iter));
             } else {
-                find(data_.begin(), data_.end(), *iter);
+                assert(find(data_.begin(), data_.end(), *iter) != data_.end());
             }
         }
         return (keys_.size());
@@ -63,7 +66,7 @@ public:
         vector<int>::const_iterator iter;
         vector<int>::const_iterator end_key = keys_.end();
         for (iter = keys_.begin(); iter != end_key; ++iter) {
-            data_.find(*iter);
+            assert(data_.find(*iter) != data_.end());
         }        
         return (keys_.size());
     }

+ 1 - 0
src/lib/bench/tests/Makefile.am

@@ -1,4 +1,5 @@
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 

+ 6 - 6
src/lib/bench/tests/loadquery_unittest.cc

@@ -82,12 +82,12 @@ public:
         EXPECT_EQ(0, message.getQid());
         EXPECT_EQ(Opcode::QUERY(), message.getOpcode());
         EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
-        EXPECT_FALSE(message.getHeaderFlag(MessageFlag::QR()));
-        EXPECT_FALSE(message.getHeaderFlag(MessageFlag::AA()));
-        EXPECT_EQ(1, message.getRRCount(Section::QUESTION()));
-        EXPECT_EQ(0, message.getRRCount(Section::ANSWER()));
-        EXPECT_EQ(0, message.getRRCount(Section::AUTHORITY()));
-        EXPECT_EQ(0, message.getRRCount(Section::ADDITIONAL()));
+        EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+        EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_AA));
+        EXPECT_EQ(1, message.getRRCount(Message::SECTION_QUESTION));
+        EXPECT_EQ(0, message.getRRCount(Message::SECTION_ANSWER));
+        EXPECT_EQ(0, message.getRRCount(Message::SECTION_AUTHORITY));
+        EXPECT_EQ(0, message.getRRCount(Message::SECTION_ADDITIONAL));
 
         // Check if the question matches our original data, if the expected
         // data is given.

+ 10 - 3
src/lib/cc/Makefile.am

@@ -1,16 +1,23 @@
 SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
-# ASIO header files used in session.cc will trigger "unused-parameter"
-# error.  Unfortunately there doesn't seem to be an easy way to selectively
+if USE_GXX
+# ASIO header files used in session.cc will trigger the "unused-parameter"
+# warning.  Unfortunately there doesn't seem to be an easy way to selectively
 # avoid the error.  As a short term workaround we suppress this warning
 # for the entire this module.  See also src/bin/auth/Makefile.am.
-if USE_GXX
 AM_CXXFLAGS += -Wno-unused-parameter
 endif
+if USE_CLANGPP
+# Likewise, ASIO header files will trigger various warnings with clang++.
+# Worse, there doesn't seem to be any option to disable one of the warnings
+# in any way, so we need to turn off -Werror.
+AM_CXXFLAGS += -Wno-error
+endif
 
 lib_LTLIBRARIES = libcc.la
 libcc_la_SOURCES = data.cc data.h session.cc session.h

+ 35 - 37
src/lib/cc/data.cc

@@ -62,84 +62,82 @@ Element::toWire(std::ostream& ss) const {
 // installed files we define the methods here.
 //
 bool
-Element::getValue(long int& t UNUSED_PARAM) {
+Element::getValue(long int&) {
     return (false);
 }
 
 bool
-Element::getValue(double& t UNUSED_PARAM) {
+Element::getValue(double&) {
     return (false);
 }
 
 bool
-Element::getValue(bool& t UNUSED_PARAM) {
+Element::getValue(bool&) {
     return (false);
 }
 
 bool
-Element::getValue(std::string& t UNUSED_PARAM) {
+Element::getValue(std::string&) {
     return (false);
 }
 
 bool
-Element::getValue(std::vector<ConstElementPtr>& t UNUSED_PARAM) {
+Element::getValue(std::vector<ConstElementPtr>&) {
     return (false);
 }
 
 bool
-Element::getValue(std::map<std::string, ConstElementPtr>& t UNUSED_PARAM) {
+Element::getValue(std::map<std::string, ConstElementPtr>&) {
     return (false);
 }
 
 bool
-Element::setValue(const long int v UNUSED_PARAM) {
+Element::setValue(const long int) {
     return (false);
 }
 
 bool
-Element::setValue(const double v UNUSED_PARAM) {
+Element::setValue(const double) {
     return (false);
 }
 
 bool
-Element::setValue(const bool t UNUSED_PARAM) {
+Element::setValue(const bool) {
     return (false);
 }
 
 bool
-Element::setValue(const std::string& v UNUSED_PARAM) {
+Element::setValue(const std::string&) {
     return (false);
 }
 
 bool
-Element::setValue(const std::vector<ConstElementPtr>& v UNUSED_PARAM) {
+Element::setValue(const std::vector<ConstElementPtr>&) {
     return (false);
 }
 
 bool
-Element::setValue(const std::map<std::string,
-                  ConstElementPtr>& v UNUSED_PARAM)
-{
+Element::setValue(const std::map<std::string, ConstElementPtr>&) {
     return (false);
 }
 
 ConstElementPtr
-Element::get(const int i UNUSED_PARAM) const {
+Element::get(const int) const {
     isc_throw(TypeError, "get(int) called on a non-list Element");
 }
 
 void
-Element::set(const size_t i UNUSED_PARAM, ConstElementPtr element UNUSED_PARAM) {
+Element::set(const size_t, ConstElementPtr) {
     isc_throw(TypeError, "set(int, element) called on a non-list Element");
 }
 
 void
-Element::add(ConstElementPtr element UNUSED_PARAM) {
+Element::add(ConstElementPtr) {
     isc_throw(TypeError, "add() called on a non-list Element");
 }
 
 void
-Element::remove(const int i UNUSED_PARAM) {
+Element::remove(const int) {
     isc_throw(TypeError, "remove(int) called on a non-list Element");
 }
 
@@ -149,42 +147,39 @@ Element::size() const {
 }
 
 ConstElementPtr
-Element::get(const std::string& name UNUSED_PARAM) const {
+Element::get(const std::string&) const {
     isc_throw(TypeError, "get(string) called on a non-map Element");
 }
 
 void
-Element::set(const std::string& name UNUSED_PARAM,
-             ConstElementPtr element UNUSED_PARAM)
-{
+Element::set(const std::string&, ConstElementPtr) {
     isc_throw(TypeError, "set(name, element) called on a non-map Element");
 }
 
 void
-Element::remove(const std::string& name UNUSED_PARAM) {
+Element::remove(const std::string&) {
     isc_throw(TypeError, "remove(string) called on a non-map Element");
 }
 
 bool
-Element::contains(const std::string& name UNUSED_PARAM) const {
+Element::contains(const std::string&) const {
     isc_throw(TypeError, "contains(string) called on a non-map Element");
 }
 
 ConstElementPtr
-Element::find(const std::string& identifier UNUSED_PARAM) const {
+Element::find(const std::string&) const {
     isc_throw(TypeError, "find(string) called on a non-map Element");
 }
 
 bool
-Element::find(const std::string& identifier UNUSED_PARAM,
-              ConstElementPtr t UNUSED_PARAM) const
-{
+Element::find(const std::string&, ConstElementPtr) const {
     return (false);
 }
 
 namespace {
 inline void
-throwJSONError(const std::string& error, const std::string& file, int line, int pos)
+throwJSONError(const std::string& error, const std::string& file, int line,
+               int pos)
 {
     std::stringstream ss;
     ss << error << " in " + file + ":" << line << ":" << pos;
@@ -427,13 +422,15 @@ from_stringstream_null(std::istream &in, const std::string& file,
 }
 
 ElementPtr
-from_stringstream_string(std::istream& in, const std::string& file, int& line, int& pos)
+from_stringstream_string(std::istream& in, const std::string& file, int& line,
+                         int& pos)
 {
     return (Element::create(str_from_stringstream(in, file, line, pos)));
 }
 
 ElementPtr
-from_stringstream_list(std::istream &in, const std::string& file, int& line, int& pos)
+from_stringstream_list(std::istream &in, const std::string& file, int& line,
+                       int& pos)
 {
     char c = 0;
     ElementPtr list = Element::createList();
@@ -484,8 +481,7 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
 }
 
 std::string
-Element::typeToName(Element::types type)
-{
+Element::typeToName(Element::types type) {
     switch (type) {
     case Element::integer:
         return (std::string("integer"));
@@ -538,14 +534,16 @@ Element::fromJSON(std::istream& in) throw(JSONError) {
 }
 
 ElementPtr
-Element::fromJSON(std::istream& in, const std::string& file_name) throw(JSONError)
+Element::fromJSON(std::istream& in, const std::string& file_name)
+    throw(JSONError)
 {
     int line = 1, pos = 1;
     return (fromJSON(in, file_name, line, pos));
 }
 
 ElementPtr
-Element::fromJSON(std::istream &in, const std::string& file, int& line, int& pos) throw(JSONError)
+Element::fromJSON(std::istream &in, const std::string& file, int& line,
+                  int& pos) throw(JSONError)
 {
     char c = 0;
     ElementPtr element;
@@ -721,7 +719,7 @@ Element::fromWire(const std::string& s) {
 }
 
 ElementPtr
-Element::fromWire(std::stringstream& in, int length) {
+Element::fromWire(std::stringstream& in, int) {
     //
     // Check protocol version
     //
@@ -892,7 +890,7 @@ merge(ElementPtr element, ConstElementPtr other) {
         isc_throw(TypeError, "merge arguments not MapElements");
     }
     
-    std::map<std::string, ConstElementPtr> m = other->mapValue();
+    const std::map<std::string, ConstElementPtr>& m = other->mapValue();
     for (std::map<std::string, ConstElementPtr>::const_iterator it = m.begin();
          it != m.end() ; ++it) {
         if ((*it).second && (*it).second->getType() != Element::null) {

+ 4 - 0
src/lib/cc/tests/Makefile.am

@@ -1,10 +1,14 @@
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_GXX			#XXX: see ../Makefile.am
 AM_CXXFLAGS += -Wno-unused-parameter
 endif
+if USE_CLANGPP
+AM_CXXFLAGS += -Wno-error
+endif
 
 if USE_STATIC_LINK
 AM_LDFLAGS = -static

+ 1 - 1
src/lib/cc/tests/data_unittests.cc

@@ -247,7 +247,7 @@ TEST(Element, create_and_value_throws) {
     EXPECT_EQ(b, true);
     b = false;
     EXPECT_TRUE(el->setValue(b));
-    EXPECT_EQ(false, el->boolValue());
+    EXPECT_FALSE(el->boolValue());
 
     el = Element::create("foo");
     EXPECT_THROW(el->intValue(), TypeError);

+ 1 - 2
src/lib/cc/tests/run_unittests.cc

@@ -17,8 +17,7 @@
 #include <gtest/gtest.h>
 
 int
-main(int argc, char* argv[])
-{
+main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     return (RUN_ALL_TESTS());
 }

+ 0 - 0
src/lib/cc/tests/session_unittests.cc


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