Browse Source

Merge branch 'master' into trac384

zhanglikun 14 years ago
parent
commit
db105a36be
83 changed files with 2793 additions and 931 deletions
  1. 83 8
      ChangeLog
  2. 2 1
      README
  3. 24 8
      configure.ac
  4. 1 1
      src/bin/Makefile.am
  5. 1 0
      src/bin/auth/Makefile.am
  6. 23 1
      src/bin/auth/auth.spec.pre.in
  7. 46 2
      src/bin/auth/auth_srv.cc
  8. 32 7
      src/bin/auth/auth_srv.h
  9. 252 0
      src/bin/auth/command.cc
  10. 61 0
      src/bin/auth/command.h
  11. 28 0
      src/bin/auth/config.cc
  12. 8 52
      src/bin/auth/main.cc
  13. 68 24
      src/bin/auth/query.cc
  14. 39 5
      src/bin/auth/query.h
  15. 1 0
      src/bin/auth/statistics.h
  16. 4 1
      src/bin/auth/tests/Makefile.am
  17. 9 0
      src/bin/auth/tests/auth_srv_unittest.cc
  18. 290 0
      src/bin/auth/tests/command_unittest.cc
  19. 46 0
      src/bin/auth/tests/config_unittest.cc
  20. 259 24
      src/bin/auth/tests/query_unittest.cc
  21. 1 10
      src/bin/bind10/bind10.8
  22. 17 17
      src/bin/bind10/bind10.py.in
  23. 1 1
      src/bin/bind10/bob.spec
  24. 1 1
      src/bin/bind10/run_bind10.sh.in
  25. 20 20
      src/bin/bind10/tests/bind10_test.py
  26. 17 7
      src/bin/bindctl/bindcmd.py
  27. 0 2
      src/bin/cfgmgr/b10-cfgmgr.py.in
  28. 2 1
      src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
  29. 0 57
      src/bin/recurse/Makefile.am
  30. 57 0
      src/bin/resolver/Makefile.am
  31. 10 10
      src/bin/recurse/b10-recurse.8
  32. 8 8
      src/bin/recurse/b10-recurse.xml
  33. 17 17
      src/bin/recurse/main.cc
  34. 56 46
      src/bin/recurse/recursor.cc
  35. 11 11
      src/bin/recurse/recursor.h
  36. 1 1
      src/bin/recurse/recurse.spec.pre.in
  37. 1 1
      src/bin/recurse/spec_config.h.pre.in
  38. 3 3
      src/bin/recurse/tests/Makefile.am
  39. 14 14
      src/bin/recurse/tests/recursor_config_unittest.cc
  40. 16 16
      src/bin/recurse/tests/recursor_unittest.cc
  41. 0 0
      src/bin/resolver/tests/run_unittests.cc
  42. 12 10
      src/bin/xfrout/tests/xfrout_test.py
  43. 22 11
      src/bin/xfrout/xfrout.py.in
  44. 1 1
      src/bin/xfrout/xfrout.spec.pre.in
  45. 1 1
      src/lib/asiolink/README
  46. 21 2
      src/lib/asiolink/asiolink.cc
  47. 24 1
      src/lib/asiolink/asiolink.h
  48. 32 1
      src/lib/asiolink/tests/asiolink_unittest.cc
  49. 1 1
      src/lib/asiolink/udpdns.cc
  50. 49 20
      src/lib/config/ccsession.cc
  51. 36 2
      src/lib/config/ccsession.h
  52. 41 11
      src/lib/config/module_spec.cc
  53. 51 8
      src/lib/config/module_spec.h
  54. 41 25
      src/lib/config/tests/ccsession_unittests.cc
  55. 73 40
      src/lib/config/tests/module_spec_unittests.cc
  56. 1 0
      src/lib/config/tests/testdata/Makefile.am
  57. 35 0
      src/lib/config/tests/testdata/spec29.spec
  58. 14 2
      src/lib/datasrc/memory_datasrc.cc
  59. 27 6
      src/lib/datasrc/memory_datasrc.h
  60. 338 252
      src/lib/datasrc/rbtree.h
  61. 54 0
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  62. 102 49
      src/lib/datasrc/tests/rbtree_unittest.cc
  63. 4 4
      src/lib/datasrc/zonetable.cc
  64. 2 2
      src/lib/datasrc/zonetable.h
  65. 2 2
      src/lib/log/dummylog.cc
  66. 1 1
      src/lib/log/dummylog.h
  67. 1 1
      src/lib/nsas/README
  68. 1 1
      src/lib/nsas/asiolink.h
  69. 1 1
      src/lib/python/isc/config/ccsession.py
  70. 25 27
      src/lib/python/isc/config/cfgmgr.py
  71. 13 1
      src/lib/python/isc/config/tests/ccsession_test.py
  72. 60 0
      src/lib/python/isc/config/tests/cfgmgr_test.py
  73. 102 68
      src/lib/python/isc/datasrc/sqlite3_ds.py
  74. 6 2
      src/lib/python/isc/datasrc/tests/Makefile.am
  75. 43 0
      src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
  76. BIN
      src/lib/python/isc/datasrc/tests/testdata/brokendb.sqlite3
  77. BIN
      src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3
  78. 7 1
      src/lib/testutils/testdata/Makefile.am
  79. 5 0
      src/lib/testutils/testdata/test1-broken.zone.in
  80. 4 0
      src/lib/testutils/testdata/test1-new.zone.in
  81. 3 0
      src/lib/testutils/testdata/test1.zone.in
  82. 4 0
      src/lib/testutils/testdata/test2-new.zone.in
  83. 3 0
      src/lib/testutils/testdata/test2.zone.in

+ 83 - 8
ChangeLog

@@ -1,3 +1,74 @@
+  153.	[bug]		jelte
+	b10-cfgmgr: Fixed a bug where configuration updates sometimes
+	lost previous settings in the configuration manager.
+	(Trac #427, git 2df894155657754151e0860e2ca9cdbed7317c70)
+
+  152.	[func]*		jinmei
+	b10-auth: Added new configuration variable "statistics-interval"
+	to allow the user to change the timer interval for periodic
+	statistics updates.  The update can also be disabled by setting
+	the value to 0.  Disabling statistics updates will also work as
+	a temporary workaround of a known issue that b10-auth can block in
+	sending statistics and stop responding to queries as a result.
+	(Trac #513, git 285c5ee)
+
+  151.  [bug]		smann
+	lib/log/dummylog.h: 
+	lib/log/dummylog.cc: Modify dlog so that it takes an optional 2nd
+        argument of type bool (true or false). This flag, if set, will cause
+        the message to be printed whether or not -v is chosen.
+        (trac #432, git 880220478c3e8702d56d761b1e0b21b77d08ee5a)
+
+  150.  [bug]		jelte
+	b10-cfgmgr: No longer save the configuration on exit. Configuration
+	is already saved if it is changed successfully, so writing it on
+	exit (and hence, when nothing has changed too) is unnecessary and
+	may even cause problems.
+	(Trac #435, git fd7baa38c08d54d5b5f84930c1684c436d2776dc)
+
+  149.  [bug]		jelte
+	bindctl: Check if the user session has disappeared (either by a
+	timeout or by a server restart), and reauthenticate if so. This
+	fixes the 'cmdctl not running' problem.
+        (trac #431, git b929be82fec5f92e115d8985552f84b4fdd385b9)
+
+  148.	[func]		jelte
+	bindctl: Command results are now pretty-printed (i.e. printed in
+	a more readable form). Empty results are no longer printed at all
+	(used to print '{}'), and the message
+	'send the command to cmd-ctrl' has also been removed.
+	(git 3954c628c13ec90722a2d8816f52a380e0065bae)
+
+  147.	[bug]		jinmei
+	python/isc/config: Fixed a bug that importing custom configuration
+	(in b10-config.db) of a remote module didn't work.
+	(Trac #478, git ea4a481)
+
+  146.	[func]		jelte
+	Command arguments were not validated internally against their
+	specifications. This change fixes that (on the C++ side, Python
+	side depends on an as yet planned addition). Note: this is only
+	an added internal check, the cli already checks format.
+	(Trac #473, git 5474eba181cb2fdd80e2b2200e072cd0a13a4e52)
+
+  145.	[func]*		jinmei
+	b10-auth: added a new command 'loadzone' for (re)loading a
+	specific zone.  The command syntax is generic but it is currently
+	only feasible for class IN in memory data source.  To reload a
+	zone "example.com" via bindctl, execute the command as follows:
+	> Auth loadzone origin = example.com
+	(Trac #467)
+
+  144.	[build]		jinmei
+	Introduced a workaround for clang++ build on FreeBSD (and probably
+	some other OSes).  If building BIND 10 fails with clang++ due to
+	a link error about "__dso_handle", try again from the configure
+	script with CXX_LIBTOOL_LDFLAGS=-L/usr/lib (the path actually
+	doesn't matter; the important part is the -L flag).  This
+	workaround is not automatically enabled as it's difficult to
+	detect the need for it dynamically, and must be enabled via the
+	variable by hand. (Trac #474, git cfde436)
+
   143.	[build]		jinmei
   143.	[build]		jinmei
 	Fixed build problems with clang++ in unit tests due to recent
 	Fixed build problems with clang++ in unit tests due to recent
 	changes.  No behavior change. (Trac #448, svn r4133)
 	changes.  No behavior change. (Trac #448, svn r4133)
@@ -62,21 +133,21 @@
 	(Trac #202, svn r3967)
 	(Trac #202, svn r3967)
 
 
   135.  [func]      each
   135.  [func]      each
-	Add b10-recurse. This is an example recursive server that
+	Add b10-resolver. This is an example recursive server that
 	currently does forwarding only and no caching.
 	currently does forwarding only and no caching.
 	(Trac #327, svn r3903)
 	(Trac #327, svn r3903)
 
 
   134.  [func]      vorner
   134.  [func]      vorner
-	b10-recurse supports timeouts and retries in forwarder mode.
+	b10-resolver supports timeouts and retries in forwarder mode.
 	(Trac #401, svn r3660)
 	(Trac #401, svn r3660)
 
 
   133.  [func]      vorner
   133.  [func]      vorner
 	New temporary logging function available in isc::log. It is used by
 	New temporary logging function available in isc::log. It is used by
-	b10-recurse.
+	b10-resolver.
 	(Trac #393, r3602)
 	(Trac #393, r3602)
 
 
   132.  [func]      vorner
   132.  [func]      vorner
-	The b10-recurse is configured through config manager.
+	The b10-resolver is configured through config manager.
 	It has "listen_on" and "forward_addresses" options.
 	It has "listen_on" and "forward_addresses" options.
 	(Trac #389, r3448)
 	(Trac #389, r3448)
 
 
@@ -139,7 +210,7 @@ bind10-devel-20101201 released on December 01, 2010
   122.  [func]		stephen
   122.  [func]		stephen
 	src/bin/bind10: Added configuration options to Boss to determine
 	src/bin/bind10: Added configuration options to Boss to determine
 	whether to start the authoritative server, recursive server (or
 	whether to start the authoritative server, recursive server (or
-	both). A dummy recursor has been provided for test purposes.
+	both). A dummy program has been provided for test purposes.
 	(Trac #412, svn r3676)
 	(Trac #412, svn r3676)
 
 
   121.  [func]		jinmei
   121.  [func]		jinmei
@@ -200,7 +271,7 @@ bind10-devel-20101201 released on December 01, 2010
 
 
   112.	[func]		zhang likun
   112.	[func]		zhang likun
 	Add one mixin class to override the naive serve_forever() provided
 	Add one mixin class to override the naive serve_forever() provided
-	in python library socketserver. Instead of polling for shutdwon
+	in python library socketserver. Instead of polling for shutdown
 	every poll_interval seconds, one socketpair is used to wake up
 	every poll_interval seconds, one socketpair is used to wake up
 	the waiting server. (Trac #352, svn r3366)
 	the waiting server. (Trac #352, svn r3366)
 
 
@@ -781,8 +852,12 @@ bind10-devel-20100421 released on April 21, 2010
 bind10-devel-20100319 released on March 19, 2010
 bind10-devel-20100319 released on March 19, 2010
 
 
 For complete code revision history, see http://bind10.isc.org/browser
 For complete code revision history, see http://bind10.isc.org/browser
-Specific subversion changesets can be accessed at:
-	http://bind10.isc.org/changeset/rrrr
+Specific git changesets can be accessed at:
+	http://bind10.isc.org/changeset/?reponame=&old=rrrr^&new=rrrr
+or after cloning the original git repository by executing:
+	% git diff rrrr^ rrrr
+Subversion changesets are not accessible any more.  The subversion
+revision numbers will be replaced with corresponding git revisions.
 Trac tickets can be accessed at: https://bind10.isc.org/ticket/nnn
 Trac tickets can be accessed at: https://bind10.isc.org/ticket/nnn
 
 
 LEGEND
 LEGEND

+ 2 - 1
README

@@ -15,7 +15,7 @@ five year plan are described here:
 
 
 This release includes the bind10 master process, b10-msgq message
 This release includes the bind10 master process, b10-msgq message
 bus, b10-auth authoritative DNS server (with SQLite3 backend),
 bus, b10-auth authoritative DNS server (with SQLite3 backend),
-b10-recurse forwarding DNS server, b10-cmdctl remote control daemon,
+b10-resolver forwarding DNS server, b10-cmdctl remote control daemon,
 b10-cfgmgr configuration manager, b10-xfrin AXFR inbound service,
 b10-cfgmgr configuration manager, b10-xfrin AXFR inbound service,
 b10-xfrout outgoing AXFR service, b10-zonemgr secondary manager,
 b10-xfrout outgoing AXFR service, b10-zonemgr secondary manager,
 b10-stats statistics collection and reporting daemon, and a new
 b10-stats statistics collection and reporting daemon, and a new
@@ -193,6 +193,7 @@ config revert:	Revert all changes that have not been committed
 config commit: Commit all changes
 config commit: Commit all changes
 config diff: Show the changes that have not been committed yet
 config diff: Show the changes that have not been committed yet
 
 
+
 EXAMPLE SESSION
 EXAMPLE SESSION
 
 
 ~> bindctl
 ~> bindctl

+ 24 - 8
configure.ac

@@ -9,7 +9,23 @@ AC_CONFIG_HEADERS([config.h])
 
 
 # Checks for programs.
 # Checks for programs.
 AC_PROG_CXX
 AC_PROG_CXX
+
+# Libtool configuration
+#
+# On FreeBSD (and probably some others), clang++ does not meet an autoconf
+# assumption in identifying libtool configuration regarding shared library:
+# the configure script will execute "$CC -shared $CFLAGS -v -o" and expect
+# the output contains -Lxxx or -Ryyy.  This is the case for g++, but not for
+# clang++, and, as a result, it will cause various errors in linking programs
+# or running them with a shared object (such as some of our python scripts).
+# To work around this problem we define a temporary variable
+# "CXX_LIBTOOL_LDFLAGS".  It's expected to be defined as, e.g, "-L/usr/lib"
+# to temporarily fake the output so that it will be compatible with that of
+# g++.
+CFLAGS_SAVED=$CFLAGS
+CFLAGS="$CFLAGS $CXX_LIBTOOL_LDFLAGS"
 AC_PROG_LIBTOOL
 AC_PROG_LIBTOOL
+CFLAGS=$CFLAGS_SAVED
 
 
 # Use C++ language
 # Use C++ language
 AC_LANG([C++])
 AC_LANG([C++])
@@ -67,7 +83,7 @@ case "$host" in
 	CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 	CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 	;;
 	;;
 *-apple-darwin*)
 *-apple-darwin*)
-	# libtool doesn't work pefectly with Darwin: libtool embeds the
+	# libtool doesn't work perfectly with Darwin: libtool embeds the
 	# final install path in dynamic libraries and our loadable python
 	# final install path in dynamic libraries and our loadable python
 	# modules always refer to that path even if it's loaded within the
 	# modules always refer to that path even if it's loaded within the
 	# source tree.  This prevents pre-install tests from working.
 	# source tree.  This prevents pre-install tests from working.
@@ -186,7 +202,7 @@ fi
 
 
 # Compiler dependent settings: define some mandatory CXXFLAGS here.
 # Compiler dependent settings: define some mandatory CXXFLAGS here.
 # We also use a separate variable B10_CXXFLAGS.  This will (and should) be
 # We also use a separate variable B10_CXXFLAGS.  This will (and should) be
-# used as the default value for each specifc AM_CXXFLAGS:
+# used as the default value for each specific AM_CXXFLAGS:
 # AM_CXXFLAGS = $(B10_CXXFLAGS)
 # AM_CXXFLAGS = $(B10_CXXFLAGS)
 # AM_CXXFLAGS += ... # add module specific flags
 # AM_CXXFLAGS += ... # add module specific flags
 # We need this so that we can disable some specific compiler warnings per
 # We need this so that we can disable some specific compiler warnings per
@@ -539,7 +555,7 @@ fi
 # So, for the moment, we simply disable the use of /dev/poll.  Unless we
 # So, for the moment, we simply disable the use of /dev/poll.  Unless we
 # implement recursive DNS server with randomized ports, we don't need the
 # implement recursive DNS server with randomized ports, we don't need the
 # scalability that /dev/poll can provide, so this decision wouldn't affect
 # scalability that /dev/poll can provide, so this decision wouldn't affect
-# run time performance.  Hpefully we can find a better solution or the ASIO
+# run time performance.  Hopefully we can find a better solution or the ASIO
 # code will be updated by the time we really need it.
 # code will be updated by the time we really need it.
 AC_CHECK_HEADERS(sys/devpoll.h, ac_cv_have_devpoll=yes, ac_cv_have_devpoll=no)
 AC_CHECK_HEADERS(sys/devpoll.h, ac_cv_have_devpoll=yes, ac_cv_have_devpoll=no)
 if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
 if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then
@@ -577,8 +593,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/auth/Makefile
                  src/bin/auth/Makefile
                  src/bin/auth/tests/Makefile
                  src/bin/auth/tests/Makefile
                  src/bin/auth/benchmarks/Makefile
                  src/bin/auth/benchmarks/Makefile
-                 src/bin/recurse/Makefile
-                 src/bin/recurse/tests/Makefile
+                 src/bin/resolver/Makefile
+                 src/bin/resolver/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/Makefile
@@ -652,8 +668,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/run_b10-xfrout.sh
            src/bin/xfrout/run_b10-xfrout.sh
-           src/bin/recurse/recurse.spec.pre
-           src/bin/recurse/spec_config.h.pre
+           src/bin/resolver/resolver.spec.pre
+           src/bin/resolver/spec_config.h.pre
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/tests/zonemgr_test
@@ -696,7 +712,7 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
-           chmod +x src/bin/recurse/run_b10-recurse.sh
+           chmod +x src/bin/resolver/run_b10-resolver.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
            chmod +x src/bin/stats/tests/stats_test
            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.sh

+ 1 - 1
src/bin/Makefile.am

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

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

@@ -40,6 +40,7 @@ b10_auth_SOURCES = query.cc query.h
 b10_auth_SOURCES += auth_srv.cc auth_srv.h
 b10_auth_SOURCES += auth_srv.cc auth_srv.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += config.cc config.h
 b10_auth_SOURCES += config.cc config.h
+b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 b10_auth_SOURCES += main.cc

+ 23 - 1
src/bin/auth/auth.spec.pre.in

@@ -51,6 +51,11 @@
             }
             }
           }]
           }]
         }
         }
+      },
+      { "item_name": "statistics-interval",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 60
       }
       }
     ],
     ],
     "commands": [
     "commands": [
@@ -63,8 +68,25 @@
         "command_name": "sendstats",
         "command_name": "sendstats",
         "command_description": "Send data to a statistics module at once",
         "command_description": "Send data to a statistics module at once",
         "command_args": []
         "command_args": []
+      },
+      {
+        "command_name": "loadzone",
+        "command_description": "(Re)load a specified zone",
+        "command_args": [
+          {
+            "item_name": "class", "item_type": "string",
+            "item_optional": true, "item_default": "IN"
+          },
+	  {
+            "item_name": "origin", "item_type": "string",
+            "item_optional": false, "item_default": ""
+          },
+	  {
+            "item_name": "datasrc", "item_type": "string",
+            "item_optional": true, "item_default": "memory"
+          }
+        ]
       }
       }
     ]
     ]
   }
   }
 }
 }
-

+ 46 - 2
src/bin/auth/auth_srv.cc

@@ -23,6 +23,8 @@
 #include <iostream>
 #include <iostream>
 #include <vector>
 #include <vector>
 
 
+#include <boost/bind.hpp>
+
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 
 
 #include <config/ccsession.h>
 #include <config/ccsession.h>
@@ -87,6 +89,8 @@ public:
     bool processNotify(const IOMessage& io_message, MessagePtr message,
     bool processNotify(const IOMessage& io_message, MessagePtr message,
                        OutputBufferPtr buffer);
                        OutputBufferPtr buffer);
 
 
+    IOService io_service_;
+
     /// Currently non-configurable, but will be.
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
 
 
@@ -102,6 +106,9 @@ public:
     /// Hot spot cache
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
     isc::datasrc::HotCache cache_;
 
 
+    /// Interval timer for periodic submission of statistics counters.
+    IntervalTimer statistics_timer_;
+
     /// Query counters for statistics
     /// Query counters for statistics
     AuthCounters counters_;
     AuthCounters counters_;
 private:
 private:
@@ -125,6 +132,7 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     config_session_(NULL), verbose_mode_(false),
     config_session_(NULL), verbose_mode_(false),
     xfrin_session_(NULL),
     xfrin_session_(NULL),
     memory_datasrc_class_(RRClass::IN()),
     memory_datasrc_class_(RRClass::IN()),
+    statistics_timer_(io_service_),
     counters_(verbose_mode_),
     counters_(verbose_mode_),
     xfrout_connected_(false),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
     xfrout_client_(xfrout_client)
@@ -200,6 +208,11 @@ AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
     dns_answer_(new MessageAnswer(this))
     dns_answer_(new MessageAnswer(this))
 {}
 {}
 
 
+void
+AuthSrv::stop() {
+    impl_->io_service_.stop();
+}
+
 AuthSrv::~AuthSrv() {
 AuthSrv::~AuthSrv() {
     delete impl_;
     delete impl_;
     delete checkin_;
     delete checkin_;
@@ -269,6 +282,11 @@ AuthSrv::getVerbose() const {
     return (impl_->verbose_mode_);
     return (impl_->verbose_mode_);
 }
 }
 
 
+IOService&
+AuthSrv::getIOService() {
+    return (impl_->io_service_);
+}
+
 void
 void
 AuthSrv::setCacheSlots(const size_t slots) {
 AuthSrv::setCacheSlots(const size_t slots) {
     impl_->cache_.setSlots(slots);
     impl_->cache_.setSlots(slots);
@@ -299,8 +317,8 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
     return (impl_->config_session_);
 }
 }
 
 
-AuthSrv::ConstMemoryDataSrcPtr
-AuthSrv::getMemoryDataSrc(const RRClass& rrclass) const {
+AuthSrv::MemoryDataSrcPtr
+AuthSrv::getMemoryDataSrc(const RRClass& rrclass) {
     // XXX: for simplicity, we only support the IN class right now.
     // XXX: for simplicity, we only support the IN class right now.
     if (rrclass != impl_->memory_datasrc_class_) {
     if (rrclass != impl_->memory_datasrc_class_) {
         isc_throw(InvalidParameter,
         isc_throw(InvalidParameter,
@@ -332,6 +350,32 @@ AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
     impl_->memory_datasrc_ = memory_datasrc;
     impl_->memory_datasrc_ = memory_datasrc;
 }
 }
 
 
+uint32_t
+AuthSrv::getStatisticsTimerInterval() const {
+    return (impl_->statistics_timer_.getInterval());
+}
+
+void
+AuthSrv::setStatisticsTimerInterval(uint32_t interval) {
+    if (interval == impl_->statistics_timer_.getInterval()) {
+        return;
+    }
+    if (interval == 0) {
+        impl_->statistics_timer_.cancel();
+    } else {
+        impl_->statistics_timer_.setupTimer(
+            boost::bind(&AuthSrv::submitStatistics, this), interval);
+    }
+    if (impl_->verbose_mode_) {
+        if (interval == 0) {
+            cerr << "[b10-auth] Disabled statistics timer" << endl;
+        } else {
+            cerr << "[b10-auth] Set statistics timer to " << interval
+                 << " seconds" << endl;
+        }
+    }
+}
+
 void
 void
 AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
 AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
                         OutputBufferPtr buffer, DNSServer* server)
                         OutputBufferPtr buffer, DNSServer* server)

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

@@ -87,6 +87,14 @@ public:
     ~AuthSrv();
     ~AuthSrv();
     //@}
     //@}
 
 
+    /// Stop the server.
+    ///
+    /// It stops the internal event loop of the server and subsequently
+    /// returns the control to the top level context.
+    ///
+    /// This method should never throw an exception.
+    void stop();
+
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
     ///
     ///
     /// A DNS query (or other message) has been received by a \c DNSServer
     /// A DNS query (or other message) has been received by a \c DNSServer
@@ -185,11 +193,8 @@ public:
     /// control commands and configuration updates.
     /// control commands and configuration updates.
     void setConfigSession(isc::config::ModuleCCSession* config_session);
     void setConfigSession(isc::config::ModuleCCSession* config_session);
 
 
-    /// \brief Assign an ASIO IO Service queue to this Recursor object
-    void setIOService(asiolink::IOService& ios) { io_service_ = &ios; }
-
     /// \brief Return this object's ASIO IO Service queue
     /// \brief Return this object's ASIO IO Service queue
-    asiolink::IOService& getIOService() const { return (*io_service_); }
+    asiolink::IOService& getIOService();
 
 
     /// \brief Return pointer to the DNS Lookup callback function
     /// \brief Return pointer to the DNS Lookup callback function
     asiolink::DNSLookup* getDNSLookupProvider() const { return (dns_lookup_); }
     asiolink::DNSLookup* getDNSLookupProvider() const { return (dns_lookup_); }
@@ -265,8 +270,7 @@ public:
     /// \param rrclass The RR class of the requested in-memory data source.
     /// \param rrclass The RR class of the requested in-memory data source.
     /// \return A pointer to the in-memory data source, if configured;
     /// \return A pointer to the in-memory data source, if configured;
     /// otherwise NULL.
     /// otherwise NULL.
-    ConstMemoryDataSrcPtr
-    getMemoryDataSrc(const isc::dns::RRClass& rrclass) const;
+    MemoryDataSrcPtr getMemoryDataSrc(const isc::dns::RRClass& rrclass);
 
 
     /// Sets or replaces the in-memory data source of the specified RR class.
     /// Sets or replaces the in-memory data source of the specified RR class.
     ///
     ///
@@ -304,6 +308,28 @@ public:
     /// is shutdown.
     /// is shutdown.
     void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
     void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
 
 
+    /// Return the interval of periodic submission of statistics in seconds.
+    ///
+    /// If the statistics submission is disabled, it returns 0.
+    ///
+    /// This method never throws an exception.
+    uint32_t getStatisticsTimerInterval() const;
+
+    /// Set the interval of periodic submission of statistics.
+    ///
+    /// If the specified value is non 0, the \c AuthSrv object will submit
+    /// its statistics to the statistics module every \c interval seconds.
+    /// If it's 0, and \c AuthSrv currently submits statistics, the submission
+    /// will be disabled.
+    ///
+    /// This method should normally not throw an exception; however, its
+    /// underlying library routines may involve resource allocation, and
+    /// when it fails it would result in a corresponding standard exception.
+    ///
+    /// \param interval The submission interval in seconds if non 0;
+    /// or a value of 0 to disable the submission.
+    void setStatisticsTimerInterval(uint32_t interval);
+
     /// \brief Submit statistics counters to statistics module.
     /// \brief Submit statistics counters to statistics module.
     ///
     ///
     /// This function can throw an exception from
     /// This function can throw an exception from
@@ -330,7 +356,6 @@ public:
 
 
 private:
 private:
     AuthSrvImpl* impl_;
     AuthSrvImpl* impl_;
-    asiolink::IOService* io_service_;
     asiolink::SimpleCallback* checkin_;
     asiolink::SimpleCallback* checkin_;
     asiolink::DNSLookup* dns_lookup_;
     asiolink::DNSLookup* dns_lookup_;
     asiolink::DNSAnswer* dns_answer_;
     asiolink::DNSAnswer* dns_answer_;

+ 252 - 0
src/bin/auth/command.cc

@@ -0,0 +1,252 @@
+// 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 <string>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <config/ccsession.h>
+
+#include <auth/auth_srv.h>
+#include <auth/command.h>
+
+using namespace std;
+using boost::shared_ptr;
+using boost::scoped_ptr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+/// An exception that is thrown if an error occurs while handling a command
+/// on an \c AuthSrv object.
+///
+/// Currently it's only used internally, since \c execAuthServerCommand()
+/// (which is the only interface to this module) catches all \c isc::
+/// exceptions and converts them.
+class AuthCommandError : public isc::Exception {
+public:
+    AuthCommandError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// An abstract base class that represents a single command identifier
+/// for an \c AuthSrv object.
+///
+/// Each of derived classes of \c AuthCommand, which are hidden inside the
+/// implementation, corresponds to a command executed on \c AuthSrv, such as
+/// "shutdown".  The derived class is responsible to execute the corresponding
+/// command with the given command arguments (if any) in its \c exec()
+/// method.
+///
+/// In the initial implementation the existence of the command classes is
+/// hidden inside the implementation since the only public interface is
+/// \c execAuthServerCommand(), which does not expose this class.
+/// In future, we may want to make this framework more dynamic, i.e.,
+/// registering specific derived classes run time outside of this
+/// implementation.  If and when that happens the definition of the abstract
+/// class will be published.
+class AuthCommand {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    AuthCommand(const AuthCommand& source);
+    AuthCommand& operator=(const AuthCommand& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    AuthCommand() {}
+public:
+    /// The destructor.
+    virtual ~AuthCommand() {}
+    //@}
+
+    /// Execute a single control command.
+    ///
+    /// Specific derived methods can throw exceptions.  When called via
+    /// \c execAuthServerCommand(), all BIND 10 exceptions are caught
+    /// and converted into an error code.
+    /// The derived method may also throw an exception of class
+    /// \c AuthCommandError when it encounters an internal error, such as
+    /// semantics error on the command arguments.
+    ///
+    /// \param server The \c AuthSrv object on which the command is executed.
+    /// \param args Command specific argument.
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
+};
+
+// Handle the "shutdown" command.  No argument is assumed.
+class ShutdownCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+        server.stop();
+    }
+};
+
+// Handle the "sendstats" command.  No argument is assumed.
+class SendStatsCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] command 'sendstats' received" << endl;
+        }
+        server.submitStatistics();
+    }
+};
+
+// Handle the "loadzone" command.
+class LoadZoneCommand : public AuthCommand {
+public:
+    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
+        // parse and validate the args.
+        if (!validate(server, args)) {
+            return;
+        }
+
+        // Load a new zone and replace the current zone with the new one.
+        // TODO: eventually this should be incremental or done in some way
+        // that doesn't block other server operations.
+        // TODO: we may (should?) want to check the "last load time" and
+        // the timestamp of the file and skip loading if the file isn't newer.
+        shared_ptr<MemoryZone> newzone(new MemoryZone(oldzone->getClass(),
+                                                      oldzone->getOrigin()));
+        newzone->load(oldzone->getFileName());
+        oldzone->swap(*newzone);
+
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] Loaded zone '" << newzone->getOrigin()
+                 << "'/" << newzone->getClass() << endl;
+        }
+    }
+
+private:
+    shared_ptr<MemoryZone> oldzone; // zone to be updated with the new file.
+
+    // A helper private method to parse and validate command parameters.
+    // On success, it sets 'oldzone' to the zone to be updated.
+    // It returns true if everything is okay; and false if the command is
+    // valid but there's no need for further process.
+    bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
+        if (args == NULL) {
+            isc_throw(AuthCommandError, "Null argument");
+        }
+
+        // In this initial implementation, we assume memory data source
+        // for class IN by default.
+        ConstElementPtr datasrc_elem = args->get("datasrc");
+        if (datasrc_elem) {
+            if (datasrc_elem->stringValue() == "sqlite3") {
+                if (server.getVerbose()) {
+                    cerr << "[b10-auth] Nothing to do for loading sqlite3"
+                         << endl;
+                }
+                return (false);
+            } else if (datasrc_elem->stringValue() != "memory") {
+                // (note: at this point it's guaranteed that datasrc_elem
+                // is of string type)
+                isc_throw(AuthCommandError,
+                          "Data source type " << datasrc_elem->stringValue()
+                          << " is not supported");
+            }
+        }
+
+        ConstElementPtr class_elem = args->get("class");
+        const RRClass zone_class = class_elem ?
+            RRClass(class_elem->stringValue()) : RRClass::IN();
+
+        AuthSrv::MemoryDataSrcPtr datasrc(server.getMemoryDataSrc(zone_class));
+        if (datasrc == NULL) {
+            isc_throw(AuthCommandError, "Memory data source is disabled");
+        }
+
+        ConstElementPtr origin_elem = args->get("origin");
+        if (!origin_elem) {
+            isc_throw(AuthCommandError, "Zone origin is missing");
+        }
+        const Name origin(origin_elem->stringValue());
+
+        // Get the current zone
+        const MemoryDataSrc::FindResult result = datasrc->findZone(origin);
+        if (result.code != result::SUCCESS) {
+            isc_throw(AuthCommandError, "Zone " << origin <<
+                      " is not found in data source");
+        }
+
+        oldzone = boost::dynamic_pointer_cast<MemoryZone>(result.zone);
+
+        return (true);
+    }
+};
+
+// The factory of command objects.
+AuthCommand*
+createAuthCommand(const string& command_id) {
+    // For the initial implementation we use a naive if-else blocks
+    // (see also createAuthConfigParser())
+    if (command_id == "shutdown") {
+        return (new ShutdownCommand());
+    } else if (command_id == "sendstats") {
+        return (new SendStatsCommand());
+    } else if (command_id == "loadzone") {
+        return (new LoadZoneCommand());
+    } else if (false && command_id == "_throw_exception") {
+        // This is for testing purpose only and should not appear in the
+        // actual configuration syntax.
+        // XXX: ModuleCCSession doesn't seem to validate commands (unlike
+        // config), so we should disable this case for now.
+        throw runtime_error("throwing for test");
+    }
+
+    isc_throw(AuthCommandError, "Unknown command identifier: " << command_id);
+}
+} // end of unnamed namespace
+
+ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const string& command_id,
+                      ConstElementPtr args)
+{
+    if (server.getVerbose()) {
+        cerr << "[b10-auth] Received '" << command_id << "' command" << endl;
+    }
+
+    try {
+        scoped_ptr<AuthCommand>(createAuthCommand(command_id))->exec(server,
+                                                                     args);
+    } catch (const isc::Exception& ex) {
+        if (server.getVerbose()) {
+            cerr << "[b10-auth] Command '" << command_id
+                 << "' execution failed: " << ex.what() << endl;
+        }
+        return (createAnswer(1, ex.what()));
+    }
+
+    return (createAnswer());
+}

+ 61 - 0
src/bin/auth/command.h

@@ -0,0 +1,61 @@
+// 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 <string>
+
+#include <cc/data.h>
+
+#ifndef __COMMAND_H
+#define __COMMAND_H 1
+
+class AuthSrv;
+
+/// Execute a control command on \c AuthSrv
+///
+/// It executes the operation identified by \c command_id with arguments
+/// \c args on \c server, and returns the result in the form of the standard
+/// command/config answer message (see \c isc::config::createAnswer()).
+///
+/// This method internally performs minimal validation on \c command_id and
+/// \c args to not cause a surprising result such as a crash, but it is
+/// generally expected that the caller has performed syntax level validation
+/// based on the command specification for the authoritative server.
+/// For example, the caller is responsible \c command_id is a valid command
+/// name for the authoritative server.
+///
+/// The execution of the command may internally trigger an exception; for
+/// instance, if a string that is expected to be a valid domain name is bogus,
+/// the underlying DNS library will throw an exception.  These internal
+/// exceptions will be caught inside this function, and will be converted
+/// to a return value with a non 0 error code.
+/// However, exceptions thrown outside of BIND 10 modules (including standard
+/// exceptions) are expected to be handled at a higher layer, and will be
+/// propagated to the caller.  In particular if memory allocation fails and
+/// \c std::bad_alloc is thrown it will be propagated.
+///
+/// \param server The \c AuthSrv object on which the command is executed.
+/// \param command_id The identifier of the command (such as "shutdown")
+/// \param args Command specific argument in a map type of
+/// \c isc::data::Element, or a \c NULL \c ElementPtr if the argument isn't
+/// specified.
+/// \return The result of the command operation.
+isc::data::ConstElementPtr
+execAuthServerCommand(AuthSrv& server, const std::string& command_id,
+                      isc::data::ConstElementPtr args);
+
+#endif // __COMMAND_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 28 - 0
src/bin/auth/config.cc

@@ -169,6 +169,32 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
     }
     }
 }
 }
 
 
+/// A derived \c AuthConfigParser class for the "statistics-internal"
+/// configuration identifier.
+class StatisticsIntervalConfig : public AuthConfigParser {
+public:
+    StatisticsIntervalConfig(AuthSrv& server) :
+        server_(server), interval_(0)
+    {}
+    virtual void build(ConstElementPtr config_value) {
+        const int32_t config_interval = config_value->intValue();
+        if (config_interval < 0) {
+            isc_throw(AuthConfigError, "negative statistics-interval value: "
+                      << config_interval);
+        }
+        interval_ = config_interval;
+    }
+    virtual void commit() {
+        // setStatisticsTimerInterval() is not 100% exception free.  But
+        // exceptions should happen only in a very rare situation, so we
+        // let them be thrown and subsequently regard them as a fatal error.
+        server_.setStatisticsTimerInterval(interval_);
+    }
+private:
+    AuthSrv& server_;
+    uint32_t interval_;
+};
+
 /// A special parser for testing: it throws from commit() despite the
 /// A special parser for testing: it throws from commit() despite the
 /// suggested convention of the class interface.
 /// suggested convention of the class interface.
 class ThrowerCommitConfig : public AuthConfigParser {
 class ThrowerCommitConfig : public AuthConfigParser {
@@ -191,6 +217,8 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
     // that it can be dynamically customized.
     // that it can be dynamically customized.
     if (config_id == "datasources") {
     if (config_id == "datasources") {
         return (new DatasourcesConfig(server));
         return (new DatasourcesConfig(server));
+    } else if (config_id == "statistics-interval") {
+        return (new StatisticsIntervalConfig(server));
     } else if (internal && config_id == "datasources/memory") {
     } else if (internal && config_id == "datasources/memory") {
         return (new MemoryDatasourceConfig(server));
         return (new MemoryDatasourceConfig(server));
     } else if (config_id == "_commit_throw") {
     } else if (config_id == "_commit_throw") {

+ 8 - 52
src/bin/auth/main.cc

@@ -25,8 +25,6 @@
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
 
 
-#include <boost/bind.hpp>
-
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <dns/buffer.h>
 #include <dns/buffer.h>
@@ -42,6 +40,7 @@
 #include <auth/spec_config.h>
 #include <auth/spec_config.h>
 #include <auth/common.h>
 #include <auth/common.h>
 #include <auth/config.h>
 #include <auth/config.h>
+#include <auth/command.h>
 #include <auth/change_user.h>
 #include <auth/change_user.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
@@ -56,22 +55,15 @@ using namespace asiolink;
 
 
 namespace {
 namespace {
 
 
-static bool verbose_mode = false;
+bool verbose_mode = false;
 
 
 // Default port current 5300 for testing purposes
 // Default port current 5300 for testing purposes
-static const string PROGRAM = "Auth";
-static const char* DNSPORT = "5300";
-
-// Note: this value must be greater than 0.
-// TODO: make it configurable via command channel.
-const uint32_t STATISTICS_SEND_INTERVAL_SEC = 60;
+const char* DNSPORT = "5300";
 
 
 /* need global var for config/command handlers.
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
  * class itself? */
-static AuthSrv *auth_server;
-
-static IOService io_service;
+AuthSrv *auth_server;
 
 
 ConstElementPtr
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
 my_config_handler(ConstElementPtr new_config) {
@@ -80,23 +72,8 @@ my_config_handler(ConstElementPtr new_config) {
 
 
 ConstElementPtr
 ConstElementPtr
 my_command_handler(const string& command, ConstElementPtr args) {
 my_command_handler(const string& command, ConstElementPtr args) {
-    ConstElementPtr answer = createAnswer();
-
-    if (command == "print_message") {
-        cout << args << endl;
-        /* let's add that message to our answer as well */
-        answer = createAnswer(0, args);
-    } else if (command == "shutdown") {
-        io_service.stop();
-    } else if (command == "sendstats") {
-        if (verbose_mode) {
-            cerr << "[b10-auth] command 'sendstats' received" << endl;
-        }
-        assert(auth_server != NULL);
-        auth_server->submitStatistics();
-    }
-
-    return (answer);
+    assert(auth_server != NULL);
+    return (execAuthServerCommand(*auth_server, command, args));
 }
 }
 
 
 void
 void
@@ -113,12 +90,6 @@ usage() {
     cerr << "\t-v: verbose output" << endl;
     cerr << "\t-v: verbose output" << endl;
     exit(1);
     exit(1);
 }
 }
-
-void
-statisticsTimerCallback(AuthSrv* auth_server) {
-    assert(auth_server != NULL);
-    auth_server->submitStatistics();
-}
 } // end of anonymous namespace
 } // end of anonymous namespace
 
 
 int
 int
@@ -185,7 +156,6 @@ main(int argc, char* argv[]) {
     Session* cc_session = NULL;
     Session* cc_session = NULL;
     Session* xfrin_session = NULL;
     Session* xfrin_session = NULL;
     Session* statistics_session = NULL;
     Session* statistics_session = NULL;
-    IntervalTimer* itimer = NULL;
     bool xfrin_session_established = false; // XXX (see Trac #287)
     bool xfrin_session_established = false; // XXX (see Trac #287)
     bool statistics_session_established = false; // XXX (see Trac #287)
     bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
     ModuleCCSession* config_session = NULL;
@@ -210,6 +180,7 @@ main(int argc, char* argv[]) {
         cout << "[b10-auth] Server created." << endl;
         cout << "[b10-auth] Server created." << endl;
 
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();
         SimpleCallback* checkin = auth_server->getCheckinProvider();
+        IOService& io_service = auth_server->getIOService();
         DNSLookup* lookup = auth_server->getDNSLookupProvider();
         DNSLookup* lookup = auth_server->getDNSLookupProvider();
         DNSAnswer* answer = auth_server->getDNSAnswerProvider();
         DNSAnswer* answer = auth_server->getDNSAnswerProvider();
 
 
@@ -228,8 +199,7 @@ main(int argc, char* argv[]) {
                                          use_ipv6, checkin, lookup,
                                          use_ipv6, checkin, lookup,
                                          answer);
                                          answer);
         }
         }
-        auth_server->setIOService(io_service);
-        cout << "[b10-auth] IOService created." << endl;
+        cout << "[b10-auth] DNSServices created." << endl;
 
 
         cc_session = new Session(io_service.get_io_service());
         cc_session = new Session(io_service.get_io_service());
         cout << "[b10-auth] Configuration session channel created." << endl;
         cout << "[b10-auth] Configuration session channel created." << endl;
@@ -255,11 +225,6 @@ main(int argc, char* argv[]) {
         statistics_session_established = true;
         statistics_session_established = true;
         cout << "[b10-auth] Statistics session channel established." << endl;
         cout << "[b10-auth] Statistics session channel established." << endl;
 
 
-        // XXX: with the current interface to asiolink we have to create
-        // auth_server before io_service while Session needs io_service.
-        // In a next step of refactoring we should make asiolink independent
-        // from auth_server, and create io_service, auth_server, and
-        // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
         auth_server->setXfrinSession(xfrin_session);
         auth_server->setStatisticsSession(statistics_session);
         auth_server->setStatisticsSession(statistics_session);
 
 
@@ -271,14 +236,6 @@ main(int argc, char* argv[]) {
         configureAuthServer(*auth_server, config_session->getFullConfig());
         configureAuthServer(*auth_server, config_session->getFullConfig());
         auth_server->updateConfig(ElementPtr());
         auth_server->updateConfig(ElementPtr());
 
 
-        // create interval timer instance
-        itimer = new IntervalTimer(io_service);
-        // set up interval timer
-        // register function to send statistics with interval
-        itimer->setupTimer(boost::bind(statisticsTimerCallback, auth_server),
-                           STATISTICS_SEND_INTERVAL_SEC);
-        cout << "[b10-auth] Interval timer to send statistics set." << endl;
-
         cout << "[b10-auth] Server started." << endl;
         cout << "[b10-auth] Server started." << endl;
         io_service.run();
         io_service.run();
 
 
@@ -296,7 +253,6 @@ main(int argc, char* argv[]) {
         xfrin_session->disconnect();
         xfrin_session->disconnect();
     }
     }
 
 
-    delete itimer;
     delete statistics_session;
     delete statistics_session;
     delete xfrin_session;
     delete xfrin_session;
     delete config_session;
     delete config_session;

+ 68 - 24
src/bin/auth/query.cc

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include <vector>
+#include <boost/foreach.hpp>
+
 #include <dns/message.h>
 #include <dns/message.h>
 #include <dns/rcode.h>
 #include <dns/rcode.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
@@ -28,24 +31,24 @@ namespace isc {
 namespace auth {
 namespace auth {
 
 
 void
 void
-Query::getAdditional(const isc::datasrc::Zone& zone,
-                     const isc::dns::RRset& rrset) const
-{
-    if (rrset.getType() == RRType::NS()) {
-        // Need to perform the search in the "GLUE OK" mode.
-        RdataIteratorPtr rdata_iterator = rrset.getRdataIterator();
-        for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
-             const Rdata& rdata(rdata_iterator->getCurrent());
-             const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
-             findAddrs(zone, ns.getNSName(), Zone::FIND_GLUE_OK);
+Query::getAdditional(const Zone& zone, const RRset& rrset) const {
+    RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
+    for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
+        const Rdata& rdata(rdata_iterator->getCurrent());
+        if (rrset.getType() == RRType::NS()) {
+            // Need to perform the search in the "GLUE OK" mode.
+            const generic::NS& ns = dynamic_cast<const generic::NS&>(rdata);
+            findAddrs(zone, ns.getNSName(), Zone::FIND_GLUE_OK);
+        } else if (rrset.getType() == RRType::MX()) {
+            const generic::MX& mx(dynamic_cast<const generic::MX&>(rdata));
+            findAddrs(zone, mx.getMXName());
         }
         }
     }
     }
 }
 }
 
 
 void
 void
-Query::findAddrs(const isc::datasrc::Zone& zone,
-                 const isc::dns::Name& qname,
-                 const isc::datasrc::Zone::FindOptions options) const
+Query::findAddrs(const Zone& zone, const Name& qname,
+                 const Zone::FindOptions options) const
 {
 {
     // Out of zone name
     // Out of zone name
     NameComparisonResult result = zone.getOrigin().compare(qname);
     NameComparisonResult result = zone.getOrigin().compare(qname);
@@ -53,17 +56,31 @@ Query::findAddrs(const isc::datasrc::Zone& zone,
         (result.getRelation() != NameComparisonResult::EQUAL))
         (result.getRelation() != NameComparisonResult::EQUAL))
         return;
         return;
 
 
+    // Omit additional data which has already been provided in the answer
+    // section from the additional.
+    //
+    // All the address rrset with the owner name of qname have been inserted
+    // into ANSWER section.
+    if (qname_ == qname && qtype_ == RRType::ANY())
+        return;
+
     // Find A rrset
     // Find A rrset
-    Zone::FindResult a_result = zone.find(qname, RRType::A(), options);
-    if (a_result.code == Zone::SUCCESS) {
-        response_.addRRset(Message::SECTION_ADDITIONAL,
-                     boost::const_pointer_cast<RRset>(a_result.rrset));
+    if (qname_ != qname || qtype_ != RRType::A()) {
+        Zone::FindResult a_result = zone.find(qname, RRType::A(), options);
+        if (a_result.code == Zone::SUCCESS) {
+            response_.addRRset(Message::SECTION_ADDITIONAL,
+                    boost::const_pointer_cast<RRset>(a_result.rrset));
+        }
     }
     }
+
     // Find AAAA rrset
     // Find AAAA rrset
-    Zone::FindResult aaaa_result = zone.find(qname, RRType::AAAA(), options);
-    if (aaaa_result.code == Zone::SUCCESS) {
-        response_.addRRset(Message::SECTION_ADDITIONAL,
-                     boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+    if (qname_ != qname || qtype_ != RRType::AAAA()) {
+        Zone::FindResult aaaa_result =
+            zone.find(qname, RRType::AAAA(), options);
+        if (aaaa_result.code == Zone::SUCCESS) {
+            response_.addRRset(Message::SECTION_ADDITIONAL,
+                    boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+        }
     }
     }
 }
 }
 
 
@@ -86,6 +103,22 @@ Query::putSOA(const Zone& zone) const {
 }
 }
 
 
 void
 void
+Query::getAuthAdditional(const Zone& zone) const {
+    // Fill in authority and addtional sections.
+    Zone::FindResult ns_result = zone.find(zone.getOrigin(), RRType::NS());
+    // zone origin name should have NS records
+    if (ns_result.code != Zone::SUCCESS) {
+        isc_throw(NoApexNS, "There's no apex NS records in zone " <<
+                zone.getOrigin().toText());
+    } else {
+        response_.addRRset(Message::SECTION_AUTHORITY,
+            boost::const_pointer_cast<RRset>(ns_result.rrset));
+        // Handle additional for authority section
+        getAdditional(zone, *ns_result.rrset);
+    }
+}
+
+void
 Query::process() const {
 Query::process() const {
     bool keep_doing = true;
     bool keep_doing = true;
     response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
     response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
@@ -112,14 +145,24 @@ Query::process() const {
             case Zone::SUCCESS:
             case Zone::SUCCESS:
                 response_.setRcode(Rcode::NOERROR());
                 response_.setRcode(Rcode::NOERROR());
                 response_.addRRset(Message::SECTION_ANSWER,
                 response_.addRRset(Message::SECTION_ANSWER,
-                            boost::const_pointer_cast<RRset>(db_result.rrset));
-                // TODO : fill in authority and addtional sections.
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                // Handle additional for answer section
+                getAdditional(*result.zone, *db_result.rrset);
+                // If apex NS records haven't been provided in the answer
+                // section, insert apex NS records into the authority section
+                // and AAAA/A RRS of each of the NS RDATA into the additional
+                // section.
+                if (qname_ != result.zone->getOrigin() ||
+                    (qtype_ != RRType::NS() && qtype_ != RRType::ANY()))
+                {
+                    getAuthAdditional(*result.zone);
+                }
                 break;
                 break;
             case Zone::DELEGATION:
             case Zone::DELEGATION:
                 response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
                 response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
                 response_.setRcode(Rcode::NOERROR());
                 response_.setRcode(Rcode::NOERROR());
                 response_.addRRset(Message::SECTION_AUTHORITY,
                 response_.addRRset(Message::SECTION_AUTHORITY,
-                            boost::const_pointer_cast<RRset>(db_result.rrset));
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
                 getAdditional(*result.zone, *db_result.rrset);
                 getAdditional(*result.zone, *db_result.rrset);
                 break;
                 break;
             case Zone::NXDOMAIN:
             case Zone::NXDOMAIN:
@@ -139,5 +182,6 @@ Query::process() const {
         }
         }
     }
     }
 }
 }
+
 }
 }
 }
 }

+ 39 - 5
src/bin/auth/query.h

@@ -66,15 +66,19 @@ namespace auth {
 class Query {
 class Query {
 private:
 private:
 
 
-    /// \short Adds a SOA.
+    /// \brief Adds a SOA.
     ///
     ///
     /// Adds a SOA of the zone into the authority zone of response_.
     /// Adds a SOA of the zone into the authority zone of response_.
     /// Can throw NoSOA.
     /// Can throw NoSOA.
     ///
     ///
     void putSOA(const isc::datasrc::Zone& zone) const;
     void putSOA(const isc::datasrc::Zone& zone) const;
 
 
-    /// Look up additional data (i.e., address records for the names included
-    /// in NS or MX records).
+    /// \brief Look up additional data (i.e., address records for the names
+    /// included in NS or MX records).
+    ///
+    /// Note: Any additional data which has already been provided in the
+    /// answer section (i.e., if the original query happend to be for the
+    /// address of the DNS server), it should be omitted from the additional.
     ///
     ///
     /// This method may throw a exception because its underlying methods may
     /// This method may throw a exception because its underlying methods may
     /// throw exceptions.
     /// throw exceptions.
@@ -86,7 +90,7 @@ private:
     void getAdditional(const isc::datasrc::Zone& zone,
     void getAdditional(const isc::datasrc::Zone& zone,
                        const isc::dns::RRset& rrset) const;
                        const isc::dns::RRset& rrset) const;
 
 
-    /// Find address records for a specified name.
+    /// \brief Find address records for a specified name.
     ///
     ///
     /// Search the specified zone for AAAA/A RRs of each of the NS/MX RDATA
     /// Search the specified zone for AAAA/A RRs of each of the NS/MX RDATA
     /// (domain name), and insert the found ones into the additional section
     /// (domain name), and insert the found ones into the additional section
@@ -98,7 +102,7 @@ private:
     /// The glue records must exactly match the name in the NS RDATA, without
     /// The glue records must exactly match the name in the NS RDATA, without
     /// CNAME or wildcard processing.
     /// CNAME or wildcard processing.
     ///
     ///
-    /// \param zone The Zone wherein the address records is to be found.
+    /// \param zone The \c Zone wherein the address records is to be found.
     /// \param qname The name in rrset RDATA.
     /// \param qname The name in rrset RDATA.
     /// \param options The search options.
     /// \param options The search options.
     void findAddrs(const isc::datasrc::Zone& zone,
     void findAddrs(const isc::datasrc::Zone& zone,
@@ -106,6 +110,26 @@ private:
                    const isc::datasrc::Zone::FindOptions options
                    const isc::datasrc::Zone::FindOptions options
                    = isc::datasrc::Zone::FIND_DEFAULT) const;
                    = isc::datasrc::Zone::FIND_DEFAULT) const;
 
 
+    /// \brief Look up \c Zone's NS and address records for the NS RDATA
+    /// (domain name) for authoritative answer.
+    ///
+    /// On returning an authoritative answer, insert the \c Zone's NS into the
+    /// authority section and AAAA/A RRs of each of the NS RDATA into the
+    /// additional section.
+    ///
+    /// <b>Notes to developer:</b>
+    ///
+    /// We should omit address records which has already been provided in the
+    /// answer section from the additional.
+    ///
+    /// For now, in order to optimize the additional section processing, we
+    /// include AAAA/A RRs under a zone cut in additional section. (BIND 9
+    /// excludes under-cut RRs; NSD include them.)
+    ///
+    /// \param zone The \c Zone wherein the additional data to the query is to
+    /// be found.
+    void getAuthAdditional(const isc::datasrc::Zone& zone) const;
+
 public:
 public:
     /// Constructor from query parameters.
     /// Constructor from query parameters.
     ///
     ///
@@ -173,6 +197,16 @@ public:
         {}
         {}
     };
     };
 
 
+    /// \short Zone is missing its apex NS records.
+    ///
+    /// We tried to add apex NS records into the authority section, but the
+    /// zone does not contain any.
+    struct NoApexNS: public BadZone {
+        NoApexNS(const char* file, size_t line, const char* what) :
+            BadZone(file, line, what)
+        {}
+    };
+
 private:
 private:
     const isc::datasrc::MemoryDataSrc& memory_datasrc_;
     const isc::datasrc::MemoryDataSrc& memory_datasrc_;
     const isc::dns::Name& qname_;
     const isc::dns::Name& qname_;

+ 1 - 0
src/bin/auth/statistics.h

@@ -18,6 +18,7 @@
 #define __STATISTICS_H 1
 #define __STATISTICS_H 1
 
 
 #include <cc/session.h>
 #include <cc/session.h>
+#include <stdint.h>
 
 
 class AuthCountersImpl;
 class AuthCountersImpl;
 
 

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

@@ -2,8 +2,9 @@ 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/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
@@ -22,9 +23,11 @@ run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../config.h ../config.cc
 run_unittests_SOURCES += ../config.h ../config.cc
+run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
+run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc

+ 9 - 0
src/bin/auth/tests/auth_srv_unittest.cc

@@ -30,6 +30,7 @@
 
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/memory_datasrc.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
+#include <auth/common.h>
 #include <auth/statistics.h>
 #include <auth/statistics.h>
 
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
@@ -640,4 +641,12 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
                                        response_obuffer, &dnsserv),
                                        response_obuffer, &dnsserv),
                  isc::Unexpected);
                  isc::Unexpected);
 }
 }
+
+TEST_F(AuthSrvTest, stop) {
+    // normal case is covered in command_unittest.cc.  we should primarily
+    // test it here, but the current design of the stop test takes time,
+    // so we consolidate the cases in the command tests.
+    // If/when the interval timer has finer granularity we'll probably add
+    // our own tests here, so we keep this empty test case.
+}
 }
 }

+ 290 - 0
src/bin/auth/tests/command_unittest.cc

@@ -0,0 +1,290 @@
+// 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 <cassert>
+#include <cstdlib>
+#include <string>
+#include <stdexcept>
+
+#include <boost/bind.hpp>
+
+#include <gtest/gtest.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <cc/data.h>
+
+#include <config/ccsession.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+#include <auth/command.h>
+
+#include <asiolink/asiolink.h>
+
+#include <testutils/mockups.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::config;
+
+namespace {
+class AuthConmmandTest : public ::testing::Test {
+protected:
+    AuthConmmandTest() : server(false, xfrout), rcode(-1) {
+        server.setStatisticsSession(&statistics_session);
+    }
+    void checkAnswer(const int expected_code) {
+        parseAnswer(rcode, result);
+        EXPECT_EQ(expected_code, rcode);
+    }
+    MockSession statistics_session;
+    MockXfroutClient xfrout;
+    AuthSrv server;
+    AuthSrv::ConstMemoryDataSrcPtr memory_datasrc;
+    ConstElementPtr result;
+    int rcode;
+public:
+    void stopServer();          // need to be public for boost::bind
+};
+
+TEST_F(AuthConmmandTest, unknownCommand) {
+    result = execAuthServerCommand(server, "no_such_command",
+                                   ConstElementPtr());
+    parseAnswer(rcode, result);
+    EXPECT_EQ(1, rcode);
+}
+
+TEST_F(AuthConmmandTest, DISABLED_unexpectedException) {
+    // execAuthServerCommand() won't catch standard exceptions.
+    // Skip this test for now: ModuleCCSession doesn't seem to validate
+    // commands.
+    EXPECT_THROW(execAuthServerCommand(server, "_throw_exception",
+                                       ConstElementPtr()),
+                 runtime_error);
+}
+
+TEST_F(AuthConmmandTest, sendStatistics) {
+    result = execAuthServerCommand(server, "sendstats", ConstElementPtr());
+    // Just check some message has been sent.  Detailed tests specific to
+    // statistics are done in its own tests.
+    EXPECT_EQ("Stats", statistics_session.getMessageDest());
+    checkAnswer(0);
+}
+
+void
+AuthConmmandTest::stopServer() {
+    result = execAuthServerCommand(server, "shutdown", ConstElementPtr());
+    parseAnswer(rcode, result);
+    assert(rcode == 0); // make sure the test stops when something is wrong
+}
+
+TEST_F(AuthConmmandTest, shutdown) {
+    asiolink::IntervalTimer itimer(server.getIOService());
+    itimer.setupTimer(boost::bind(&AuthConmmandTest::stopServer, this), 1);
+    server.getIOService().run();
+    EXPECT_EQ(0, rcode);
+}
+
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the server with a memory data source containing two
+// zones, and checks the zones are correctly loaded.
+void
+zoneChecks(AuthSrv& server) {
+    EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::AAAA()).code);
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+void
+configureZones(AuthSrv& server) {
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test1.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test2.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+    configureAuthServer(server, Element::fromJSON(
+                            "{\"datasources\": "
+                            " [{\"type\": \"memory\","
+                            "   \"zones\": "
+                            "[{\"origin\": \"test1.example\","
+                            "  \"file\": \""
+                               TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+                            " {\"origin\": \"test2.example\","
+                            "  \"file\": \""
+                               TEST_DATA_BUILDDIR "/test2.zone.copied\"}"
+                            "]}]}"));
+    zoneChecks(server);
+}
+
+void
+newZoneChecks(AuthSrv& server) {
+    EXPECT_TRUE(server.getMemoryDataSrc(RRClass::IN()));
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::A()).code);
+    // now test1.example should have ns/AAAA
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test1.example")).zone->
+              find(Name("ns.test1.example"), RRType::AAAA()).code);
+
+    // test2.example shouldn't change
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::A()).code);
+    EXPECT_EQ(Zone::NXRRSET, server.getMemoryDataSrc(RRClass::IN())->
+              findZone(Name("ns.test2.example")).zone->
+              find(Name("ns.test2.example"), RRType::AAAA()).code);
+}
+
+TEST_F(AuthConmmandTest, loadZone) {
+    configureZones(server);
+
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+                        "/test1-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+                        "/test2-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(0);
+    newZoneChecks(server);
+}
+
+TEST_F(AuthConmmandTest, loadBrokenZone) {
+    configureZones(server);
+
+    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+                        "/test1-broken.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+    zoneChecks(server);     // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadUnreadableZone) {
+    configureZones(server);
+
+    // install the zone file as unreadable
+    ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
+                        "/test1.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+    zoneChecks(server);     // zone shouldn't be replaced
+}
+
+TEST_F(AuthConmmandTest, loadZoneWithoutDataSrc) {
+    // try to execute load command without configuring the zone beforehand.
+    // it should fail.
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\"}"));
+    checkAnswer(1);
+}
+
+TEST_F(AuthConmmandTest, loadSqlite3DataSrc) {
+    // For sqlite3 data source we don't have to do anything (the data source
+    // (re)loads itself automatically)
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": \"sqlite3\"}"));
+    checkAnswer(0);
+}
+
+TEST_F(AuthConmmandTest, loadZoneInvalidParams) {
+    configureZones(server);
+
+    // null arg
+    result = execAuthServerCommand(server, "loadzone", ElementPtr());
+    checkAnswer(1);
+
+    // zone class is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": \"no_such_class\"}"));
+    checkAnswer(1);
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": 1}"));
+    checkAnswer(1);
+
+    // unsupported zone class
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"class\": \"CH\"}"));
+    checkAnswer(1);
+
+    // unsupported data source class
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": \"not supported\"}"));
+    checkAnswer(1);
+
+    // data source is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"test1.example\","
+                                       " \"datasrc\": 0}"));
+    checkAnswer(1);
+
+    // origin is missing
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{}"));
+    checkAnswer(1);
+
+    // zone doesn't exist in the data source
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{\"origin\": \"xx\"}"));
+    checkAnswer(1);
+
+    // origin is bogus
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON(
+                                       "{\"origin\": \"...\"}"));
+    checkAnswer(1);
+
+    result = execAuthServerCommand(server, "loadzone",
+                                   Element::fromJSON("{\"origin\": 10}"));
+    checkAnswer(1);
+}
+}

+ 46 - 0
src/bin/auth/tests/config_unittest.cc

@@ -34,6 +34,7 @@
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
+using namespace asiolink;
 
 
 namespace {
 namespace {
 class AuthConfigTest : public ::testing::Test {
 class AuthConfigTest : public ::testing::Test {
@@ -320,4 +321,49 @@ TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
                                                  " {\"type\": \"memory\"}]")),
                                                  " {\"type\": \"memory\"}]")),
                  AuthConfigError);
                  AuthConfigError);
 }
 }
+
+class StatisticsIntervalConfigTest : public AuthConfigTest {
+protected:
+    StatisticsIntervalConfigTest() :
+        parser(createAuthConfigParser(server, "statistics-interval"))
+    {}
+    ~StatisticsIntervalConfigTest() {
+        delete parser;
+    }
+    AuthConfigParser* parser;
+};
+
+TEST_F(StatisticsIntervalConfigTest, setInterval) {
+    // initially the timer is not configured.
+    EXPECT_EQ(0, server.getStatisticsTimerInterval());
+
+    // initialize the timer
+    parser->build(Element::fromJSON("5"));
+    parser->commit();
+    EXPECT_EQ(5, server.getStatisticsTimerInterval());
+
+    // reset the timer with a new interval
+    delete parser;
+    parser = createAuthConfigParser(server, "statistics-interval");
+    ASSERT_NE(static_cast<void*>(NULL), parser);
+    parser->build(Element::fromJSON("10"));
+    parser->commit();
+    EXPECT_EQ(10, server.getStatisticsTimerInterval());
+
+    // disable the timer again
+    delete parser;
+    parser = createAuthConfigParser(server, "statistics-interval");
+    ASSERT_NE(static_cast<void*>(NULL), parser);
+    parser->build(Element::fromJSON("0"));
+    parser->commit();
+    EXPECT_EQ(0, server.getStatisticsTimerInterval());
+}
+
+TEST_F(StatisticsIntervalConfigTest, badInterval) {
+    EXPECT_THROW(parser->build(Element::fromJSON("\"should be integer\"")),
+                 isc::data::TypeError);
+    EXPECT_THROW(parser->build(Element::fromJSON("2.5")),
+                 isc::data::TypeError);
+    EXPECT_THROW(parser->build(Element::fromJSON("-1")), AuthConfigError);
+}
 }
 }

+ 259 - 24
src/bin/auth/tests/query_unittest.cc

@@ -47,26 +47,34 @@ RRsetPtr glue_aaaa_rrset(RRsetPtr(new RRset(Name("glue.ns.example.com"),
                                             RRClass::IN(), RRType::AAAA(),
                                             RRClass::IN(), RRType::AAAA(),
                                             RRTTL(3600))));
                                             RRTTL(3600))));
 RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
 RRsetPtr noglue_a_rrset(RRsetPtr(new RRset(Name("noglue.example.com"),
-                                         RRClass::IN(), RRType::A(),
-                                         RRTTL(3600))));
+                                           RRClass::IN(), RRType::A(),
+                                           RRTTL(3600))));
+RRsetPtr delegated_mx_a_rrset(RRsetPtr(new RRset(
+    Name("mx.delegation.example.com"), RRClass::IN(), RRType::A(),
+    RRTTL(3600))));
+
 // This is a mock Zone class for testing.
 // This is a mock Zone class for testing.
-// It is a derived class of Zone, and simply hardcode the results of find()
-// return SUCCESS for "www.example.com",
-// return NXDOMAIN for "nxdomain.example.com",
-// return NXRRSET for "nxrrset.example.com",
-// return CNAME for "cname.example.com",
-// otherwise return DNAME
+// It is a derived class of Zone, and simply hardcodes the results of find()
+// See the find() implementation if you want to know its content.
 class MockZone : public Zone {
 class MockZone : public Zone {
 public:
 public:
-    MockZone(bool has_SOA = true) :
+    MockZone(bool has_SOA = true, bool has_apex_NS = true) :
         origin_(Name("example.com")),
         origin_(Name("example.com")),
         has_SOA_(has_SOA),
         has_SOA_(has_SOA),
+        has_apex_NS_(has_apex_NS),
         delegation_rrset(RRsetPtr(new RRset(Name("delegation.example.com"),
         delegation_rrset(RRsetPtr(new RRset(Name("delegation.example.com"),
                                             RRClass::IN(), RRType::NS(),
                                             RRClass::IN(), RRType::NS(),
                                             RRTTL(3600)))),
                                             RRTTL(3600)))),
         cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
         cname_rrset(RRsetPtr(new RRset(Name("cname.example.com"),
                                        RRClass::IN(), RRType::CNAME(),
                                        RRClass::IN(), RRType::CNAME(),
-                                       RRTTL(3600))))
+                                       RRTTL(3600)))),
+        auth_ns_rrset(RRsetPtr(new RRset(Name("example.com"),
+                                         RRClass::IN(), RRType::NS(),
+                                         RRTTL(3600)))),
+        mx_cname_rrset_(new RRset(Name("cnamemailer.example.com"),
+            RRClass::IN(), RRType::CNAME(), RRTTL(3600))),
+        mx_rrset_(new RRset(Name("mx.example.com"), RRClass::IN(),
+            RRType::MX(), RRTTL(3600)))
     {
     {
         delegation_rrset->addRdata(rdata::generic::NS(
         delegation_rrset->addRdata(rdata::generic::NS(
                           Name("glue.ns.example.com")));
                           Name("glue.ns.example.com")));
@@ -78,6 +86,20 @@ public:
                           Name("example.org")));
                           Name("example.org")));
         cname_rrset->addRdata(rdata::generic::CNAME(
         cname_rrset->addRdata(rdata::generic::CNAME(
                           Name("www.example.com")));
                           Name("www.example.com")));
+        auth_ns_rrset->addRdata(rdata::generic::NS(
+                          Name("glue.ns.example.com")));
+        auth_ns_rrset->addRdata(rdata::generic::NS(
+                          Name("noglue.example.com")));
+        auth_ns_rrset->addRdata(rdata::generic::NS(
+                          Name("example.net")));
+        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(10,
+            Name("www.example.com")));
+        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(20,
+            Name("mailer.example.org")));
+        mx_rrset_->addRdata(isc::dns::rdata::generic::MX(30,
+            Name("mx.delegation.example.com")));
+        mx_cname_rrset_->addRdata(rdata::generic::CNAME(
+            Name("mx.example.com")));
     }
     }
     virtual const isc::dns::Name& getOrigin() const;
     virtual const isc::dns::Name& getOrigin() const;
     virtual const isc::dns::RRClass& getClass() const;
     virtual const isc::dns::RRClass& getClass() const;
@@ -89,8 +111,12 @@ public:
 private:
 private:
     Name origin_;
     Name origin_;
     bool has_SOA_;
     bool has_SOA_;
+    bool has_apex_NS_;
     RRsetPtr delegation_rrset;
     RRsetPtr delegation_rrset;
     RRsetPtr cname_rrset;
     RRsetPtr cname_rrset;
+    RRsetPtr auth_ns_rrset;
+    RRsetPtr mx_cname_rrset_;
+    RRsetPtr mx_rrset_;
 };
 };
 
 
 const Name&
 const Name&
@@ -108,21 +134,35 @@ MockZone::find(const Name& name, const RRType& type,
                const FindOptions options) const
                const FindOptions options) const
 {
 {
     // hardcode the find results
     // hardcode the find results
-    if (name == Name("www.example.com")) {
+    if (name == Name("www.example.com") && type == RRType::A()) {
         return (FindResult(SUCCESS, a_rrset));
         return (FindResult(SUCCESS, a_rrset));
+    } else if (name == Name("www.example.com")) {
+        return (FindResult(NXRRSET, RRsetPtr()));
     } else if (name == Name("glue.ns.example.com") && type == RRType::A() &&
     } else if (name == Name("glue.ns.example.com") && type == RRType::A() &&
-        options == FIND_GLUE_OK) {
+        (options & FIND_GLUE_OK) != 0) {
         return (FindResult(SUCCESS, glue_a_rrset));
         return (FindResult(SUCCESS, glue_a_rrset));
-    } else if (name == Name("noglue.example.com") && type == RRType::A()) {
+    } else if (name == Name("noglue.example.com") && (type == RRType::A() ||
+        type == RRType::ANY())) {
         return (FindResult(SUCCESS, noglue_a_rrset));
         return (FindResult(SUCCESS, noglue_a_rrset));
     } else if (name == Name("glue.ns.example.com") && type == RRType::AAAA() &&
     } else if (name == Name("glue.ns.example.com") && type == RRType::AAAA() &&
-        options == FIND_GLUE_OK) {
+        (options & FIND_GLUE_OK) != 0) {
         return (FindResult(SUCCESS, glue_aaaa_rrset));
         return (FindResult(SUCCESS, glue_aaaa_rrset));
     } else if (name == Name("example.com") && type == RRType::SOA() &&
     } else if (name == Name("example.com") && type == RRType::SOA() &&
         has_SOA_)
         has_SOA_)
     {
     {
         return (FindResult(SUCCESS, soa_rrset));
         return (FindResult(SUCCESS, soa_rrset));
-    } else if (name == Name("delegation.example.com")) {
+    } else if (name == Name("example.com") && type == RRType::NS() &&
+        has_apex_NS_)
+    {
+        return (FindResult(SUCCESS, auth_ns_rrset));
+    } else if (name == Name("mx.delegation.example.com") &&
+        type == RRType::A() && (options & FIND_GLUE_OK) != 0)
+    {
+        return (FindResult(SUCCESS, delegated_mx_a_rrset));
+    } else if (name == Name("delegation.example.com") ||
+        name.compare(Name("delegation.example.com")).getRelation() ==
+        NameComparisonResult::SUBDOMAIN)
+    {
         return (FindResult(DELEGATION, delegation_rrset));
         return (FindResult(DELEGATION, delegation_rrset));
     } else if (name == Name("ns.example.com")) {
     } else if (name == Name("ns.example.com")) {
         return (FindResult(DELEGATION, ns_rrset));
         return (FindResult(DELEGATION, ns_rrset));
@@ -132,6 +172,10 @@ MockZone::find(const Name& name, const RRType& type,
         return (FindResult(NXRRSET, RRsetPtr()));
         return (FindResult(NXRRSET, RRsetPtr()));
     } else if ((name == Name("cname.example.com"))) {
     } else if ((name == Name("cname.example.com"))) {
         return (FindResult(CNAME, cname_rrset));
         return (FindResult(CNAME, cname_rrset));
+    } else if (name == Name("cnamemailer.example.com")) {
+        return (FindResult(CNAME, mx_cname_rrset_));
+    } else if (name == Name("mx.example.com")) {
+        return (FindResult(SUCCESS, mx_rrset_));
     } else {
     } else {
         return (FindResult(DNAME, RRsetPtr()));
         return (FindResult(DNAME, RRsetPtr()));
     }
     }
@@ -157,24 +201,130 @@ protected:
 TEST_F(QueryTest, noZone) {
 TEST_F(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
     // REFUSED.
-    query.process();
+    EXPECT_NO_THROW(query.process());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 }
 
 
-TEST_F(QueryTest, matchZone) {
+TEST_F(QueryTest, exactMatch) {
     // add a matching zone.
     // add a matching zone.
     memory_datasrc.addZone(ZonePtr(new MockZone()));
     memory_datasrc.addZone(ZonePtr(new MockZone()));
-    query.process();
+    EXPECT_NO_THROW(query.process());
+    // find match rrset
     EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
     EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
     EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
                                   Name("www.example.com"), RRClass::IN(),
                                   Name("www.example.com"), RRClass::IN(),
                                   RRType::A()));
                                   RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+                                  Name("example.com"), RRClass::IN(),
+                                  RRType::NS()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("noglue.example.com"),
+                                  RRClass::IN(), RRType::A()));
+}
+
+TEST_F(QueryTest, exactAddrMatch) {
+    // find match rrset, omit additional data which has already been provided
+    // in the answer section from the additional.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    const Name noglue_name(Name("noglue.example.com"));
+    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
+    EXPECT_NO_THROW(noglue_query.process());
+    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
+                                  Name("noglue.example.com"), RRClass::IN(),
+                                  RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+                                  Name("example.com"), RRClass::IN(),
+                                  RRType::NS()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
+    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("noglue.example.com"),
+                                  RRClass::IN(), RRType::A()));
+}
+
+TEST_F(QueryTest, apexNSMatch) {
+    // find match rrset, omit authority data which has already been provided
+    // in the answer section from the authority section.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    const Name apex_name(Name("example.com"));
+    Query apex_ns_query(memory_datasrc, apex_name, RRType::NS(), response);
+    EXPECT_NO_THROW(apex_ns_query.process());
+    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
+                                  Name("example.com"), RRClass::IN(),
+                                  RRType::NS()));
+    EXPECT_FALSE(response.hasRRset(Message::SECTION_AUTHORITY,
+                                  Name("example.com"), RRClass::IN(),
+                                  RRType::NS()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("noglue.example.com"),
+                                  RRClass::IN(), RRType::A()));
+}
+
+TEST_F(QueryTest, exactAnyMatch) {
+    // find match rrset, omit additional data which has already been provided
+    // in the answer section from the additional.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    const Name noglue_name(Name("noglue.example.com"));
+    Query noglue_query(memory_datasrc, noglue_name, RRType::ANY(), response);
+    EXPECT_NO_THROW(noglue_query.process());
+    EXPECT_TRUE(response.getHeaderFlag(Message::HEADERFLAG_AA));
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
+                                  Name("noglue.example.com"), RRClass::IN(),
+                                  RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+                                  Name("example.com"), RRClass::IN(),
+                                  RRType::NS()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::A()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("glue.ns.example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
+    EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
+                                  Name("noglue.example.com"),
+                                  RRClass::IN(), RRType::A()));
+}
+
+// This tests that when we need to look up Zone's apex NS records for
+// authoritative answer, and there is no apex NS records. It should
+// throw in that case.
+TEST_F(QueryTest, noApexNS) {
+    // Add a zone without apex NS records
+    memory_datasrc.addZone(ZonePtr(new MockZone(true, false)));
+    const Name noglue_name(Name("noglue.example.com"));
+    Query noglue_query(memory_datasrc, noglue_name, qtype, response);
+    EXPECT_THROW(noglue_query.process(), Query::NoApexNS);
+    // We don't look into the response, as it throwed
+}
 
 
-    // Delegation
+TEST_F(QueryTest, delegation) {
+    // add a matching zone.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
     const Name delegation_name(Name("delegation.example.com"));
     const Name delegation_name(Name("delegation.example.com"));
     Query delegation_query(memory_datasrc, delegation_name, qtype, response);
     Query delegation_query(memory_datasrc, delegation_name, qtype, response);
-    delegation_query.process();
+    EXPECT_NO_THROW(delegation_query.process());
     EXPECT_FALSE(response.getHeaderFlag(Message::HEADERFLAG_AA));
     EXPECT_FALSE(response.getHeaderFlag(Message::HEADERFLAG_AA));
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
     EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
@@ -199,21 +349,27 @@ TEST_F(QueryTest, matchZone) {
     EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
     EXPECT_FALSE(response.hasRRset(Message::SECTION_ADDITIONAL,
                                   Name("example.org"),
                                   Name("example.org"),
                                   RRClass::IN(), RRType::A()));
                                   RRClass::IN(), RRType::A()));
+}
 
 
-    // NXDOMAIN
+TEST_F(QueryTest, nxdomain) {
+    // add a matching zone.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
     const Name nxdomain_name(Name("nxdomain.example.com"));
     const Name nxdomain_name(Name("nxdomain.example.com"));
     Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
     Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
-    nxdomain_query.process();
+    EXPECT_NO_THROW(nxdomain_query.process());
     EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
     EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
     EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
     EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
         Name("example.com"), RRClass::IN(), RRType::SOA()));
         Name("example.com"), RRClass::IN(), RRType::SOA()));
+}
 
 
-    // NXRRSET
+TEST_F(QueryTest, nxrrset) {
+    // add a matching zone.
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
     const Name nxrrset_name(Name("nxrrset.example.com"));
     const Name nxrrset_name(Name("nxrrset.example.com"));
     Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
     Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
-    nxrrset_query.process();
+    EXPECT_NO_THROW(nxrrset_query.process());
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
     EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
@@ -250,4 +406,83 @@ TEST_F(QueryTest, noMatchZone) {
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 }
 
 
+/*
+ * Test MX additional processing.
+ *
+ * The MX RRset has two RRs, one pointing to a known domain with
+ * A record, other to unknown out of zone one.
+ */
+TEST_F(QueryTest, MX) {
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    Name qname("mx.example.com");
+    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
+    EXPECT_NO_THROW(mx_query.process());
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ANSWER,
+        Name("mx.example.com"), RRClass::IN(), RRType::MX()));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_ADDITIONAL,
+        Name("www.example.com"), RRClass::IN(), RRType::A()));
+    // We want to skip the additional ones related to authoritative
+    RRsetPtr ns;
+    for (SectionIterator<RRsetPtr> ai(response.beginSection(
+        Message::SECTION_AUTHORITY)); ai != response.endSection(
+        Message::SECTION_AUTHORITY); ++ai)
+    {
+        if ((*ai)->getName() == Name("example.com") && (*ai)->getType() ==
+            RRType::NS())
+        {
+            ns = *ai;
+            break;
+        }
+    }
+    /*
+     * In fact, the MX RRset mentions three names, but we don't know anything
+     * about one of them and one is under a zone cut, so we should have just
+     * one RRset (A for www.example.com)
+     */
+    // We can't use getRRCount, as it counts RRs, not RRsets
+    unsigned additional_count(0);
+    for (SectionIterator<RRsetPtr> ai(response.beginSection(
+        Message::SECTION_ADDITIONAL)); ai != response.endSection(
+        Message::SECTION_ADDITIONAL); ++ai)
+    {
+        // Skip the ones for the NS record
+        if (ns) {
+            for (RdataIteratorPtr nsi(ns->getRdataIterator()); !nsi->isLast();
+                nsi->next())
+            {
+                if ((*ai)->getName() ==
+                    dynamic_cast<const isc::dns::rdata::generic::NS&>(
+                    nsi->getCurrent()).getNSName())
+                {
+                    goto NS_ADDITIONAL_DATA;
+                }
+            }
+        }
+        // It is not related to the NS, then it must be related to the MX
+        ++additional_count;
+        EXPECT_EQ(Name("www.example.com"), (*ai)->getName());
+        EXPECT_EQ(RRType::A(), (*ai)->getType());
+        NS_ADDITIONAL_DATA:;
+    }
+    EXPECT_EQ(1, additional_count);
+}
+
+/*
+ * Test when we ask for MX and encounter an alias (CNAME in this case).
+ *
+ * This should not trigger the additional processing.
+ */
+TEST_F(QueryTest, MXAlias) {
+    memory_datasrc.addZone(ZonePtr(new MockZone()));
+    Name qname("cnamemailer.example.com");
+    Query mx_query(memory_datasrc, qname, RRType::MX(), response);
+    EXPECT_NO_THROW(mx_query.process());
+    EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
+    // We should not have the IP address in additional section
+    // Currently, the section should be completely empty
+    EXPECT_TRUE(response.beginSection(Message::SECTION_ADDITIONAL) ==
+        response.endSection(Message::SECTION_ADDITIONAL));
+}
+
 }
 }

+ 1 - 10
src/bin/bind10/bind10.8

@@ -1,7 +1,7 @@
 '\" t
 '\" t
 .\"     Title: bind10
 .\"     Title: bind10
 .\"    Author: [see the "AUTHORS" section]
 .\"    Author: [see the "AUTHORS" section]
-.\" Generator: DocBook XSL Stylesheets v1.76.0 <http://docbook.sf.net/>
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
 .\"      Date: July 29, 2010
 .\"      Date: July 29, 2010
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
@@ -9,15 +9,6 @@
 .\"
 .\"
 .TH "BIND10" "8" "July 29, 2010" "BIND10" "BIND10"
 .TH "BIND10" "8" "July 29, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
-.\" * Define some portability stuff
-.\" -----------------------------------------------------------------
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.\" http://bugs.debian.org/507673
-.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.ie \n(.g .ds Aq \(aq
-.el       .ds Aq '
-.\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" disable hyphenation
 .\" disable hyphenation

+ 17 - 17
src/bin/bind10/bind10.py.in

@@ -208,13 +208,13 @@ class BoB:
         self.dns_port = dns_port
         self.dns_port = dns_port
         self.forward = forward
         self.forward = forward
         if forward:
         if forward:
-            self.recursive = True
+            self.resolver = True
         else:
         else:
-            self.recursive = False
+            self.resolver = False
         self.cc_session = None
         self.cc_session = None
         self.ccs = None
         self.ccs = None
         self.cfg_start_auth = True
         self.cfg_start_auth = True
-        self.cfg_start_recurse = False
+        self.cfg_start_resolver = False
         self.curproc = None
         self.curproc = None
         self.dead_processes = {}
         self.dead_processes = {}
         self.msgq_socket_file = msgq_socket_file
         self.msgq_socket_file = msgq_socket_file
@@ -278,13 +278,13 @@ class BoB:
 
 
         config_data = self.ccs.get_full_config()
         config_data = self.ccs.get_full_config()
         self.cfg_start_auth = config_data.get("start_auth")
         self.cfg_start_auth = config_data.get("start_auth")
-        self.cfg_start_recurse = config_data.get("start_recurse")
+        self.cfg_start_resolver = config_data.get("start_resolver")
 
 
         if self.verbose:
         if self.verbose:
             sys.stdout.write("[bind10] - start_auth: %s\n" %
             sys.stdout.write("[bind10] - start_auth: %s\n" %
                 str(self.cfg_start_auth))
                 str(self.cfg_start_auth))
-            sys.stdout.write("[bind10] - start_recurse: %s\n" %
-                str(self.cfg_start_recurse))
+            sys.stdout.write("[bind10] - start_resolver: %s\n" %
+                str(self.cfg_start_resolver))
 
 
     def log_starting(self, process, port = None, address = None):
     def log_starting(self, process, port = None, address = None):
         """
         """
@@ -423,13 +423,13 @@ class BoB:
             Start the Authoritative server
             Start the Authoritative server
         """
         """
         # XXX: this must be read from the configuration manager in the future
         # XXX: this must be read from the configuration manager in the future
-        if self.recursive:
-            dns_prog = 'b10-recurse'
+        if self.resolver:
+            dns_prog = 'b10-resolver'
         else:
         else:
             dns_prog = 'b10-auth'
             dns_prog = 'b10-auth'
         dnsargs = [dns_prog]
         dnsargs = [dns_prog]
-        if not self.recursive:
-            # The recursive uses configuration manager for these
+        if not self.resolver:
+            # The resolver uses configuration manager for these
             dnsargs += ['-p', str(self.dns_port)]
             dnsargs += ['-p', str(self.dns_port)]
             if self.address:
             if self.address:
                 dnsargs += ['-a', str(self.address)]
                 dnsargs += ['-a', str(self.address)]
@@ -444,22 +444,22 @@ class BoB:
         self.start_process("b10-auth", dnsargs, c_channel_env,
         self.start_process("b10-auth", dnsargs, c_channel_env,
             self.dns_port, self.address)
             self.dns_port, self.address)
 
 
-    def start_recurse(self, c_channel_env):
+    def start_resolver(self, c_channel_env):
         """
         """
             Start the Resolver.  At present, all these arguments and switches
             Start the Resolver.  At present, all these arguments and switches
             are pure speculation.  As with the auth daemon, they should be
             are pure speculation.  As with the auth daemon, they should be
             read from the configuration database.
             read from the configuration database.
         """
         """
-        self.curproc = "b10-recurse"
+        self.curproc = "b10-resolver"
         # XXX: this must be read from the configuration manager in the future
         # XXX: this must be read from the configuration manager in the future
-        resargs = ['b10-recurse']
+        resargs = ['b10-resolver']
         if self.uid:
         if self.uid:
             resargs += ['-u', str(self.uid)]
             resargs += ['-u', str(self.uid)]
         if self.verbose:
         if self.verbose:
             resargs += ['-v']
             resargs += ['-v']
 
 
         # ... and start
         # ... and start
-        self.start_process("b10-recurse", resargs, c_channel_env)
+        self.start_process("b10-resolver", resargs, c_channel_env)
 
 
     def start_xfrout(self, c_channel_env):
     def start_xfrout(self, c_channel_env):
         self.start_simple("b10-xfrout", c_channel_env)
         self.start_simple("b10-xfrout", c_channel_env)
@@ -496,8 +496,8 @@ class BoB:
             self.start_auth(c_channel_env)
             self.start_auth(c_channel_env)
 
 
         # ... and resolver (if selected):
         # ... and resolver (if selected):
-        if self.cfg_start_recurse:
-            self.start_recurse(c_channel_env)
+        if self.cfg_start_resolver:
+            self.start_resolver(c_channel_env)
 
 
         # Everything after the main components can run as non-root.
         # Everything after the main components can run as non-root.
         # TODO: this is only temporary - once the privileged socket creator is
         # TODO: this is only temporary - once the privileged socket creator is
@@ -557,7 +557,7 @@ class BoB:
         self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
         self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
         self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
         self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
         self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
         self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
-        self.cc_session.group_sendmsg(cmd, "Recurse", "Recurse")
+        self.cc_session.group_sendmsg(cmd, "Resolver", "Resolver")
         self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
         self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
         self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
         self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
         self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
         self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")

+ 1 - 1
src/bin/bind10/bob.spec

@@ -10,7 +10,7 @@
         "item_default": true
         "item_default": true
       },
       },
       {
       {
-        "item_name": "start_recurse",
+        "item_name": "start_resolver",
         "item_type": "boolean",
         "item_type": "boolean",
         "item_optional": false,
         "item_optional": false,
         "item_default": false
         "item_default": false

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

@@ -20,7 +20,7 @@ export PYTHON_EXEC
 
 
 BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 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/recurse:@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:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@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:$PATH
 export 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:@abs_top_builddir@/src/lib/log/.libs
 PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs

+ 20 - 20
src/bin/bind10/tests/bind10_test.py

@@ -89,7 +89,7 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_auth, True)
-        self.assertEqual(bob.cfg_start_recurse, False)
+        self.assertEqual(bob.cfg_start_resolver, False)
 
 
     def test_init_alternate_socket(self):
     def test_init_alternate_socket(self):
         bob = BoB("alt_socket_file")
         bob = BoB("alt_socket_file")
@@ -106,7 +106,7 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_auth, True)
-        self.assertEqual(bob.cfg_start_recurse, False)
+        self.assertEqual(bob.cfg_start_resolver, False)
 
 
     def test_init_alternate_dns_port(self):
     def test_init_alternate_dns_port(self):
         bob = BoB(None, 9999)
         bob = BoB(None, 9999)
@@ -123,7 +123,7 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_auth, True)
-        self.assertEqual(bob.cfg_start_recurse, False)
+        self.assertEqual(bob.cfg_start_resolver, False)
 
 
     def test_init_alternate_address(self):
     def test_init_alternate_address(self):
         bob = BoB(None, 1234, IPAddr('127.127.127.127'))
         bob = BoB(None, 1234, IPAddr('127.127.127.127'))
@@ -140,7 +140,7 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.username, None)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.nocache, False)
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_auth, True)
-        self.assertEqual(bob.cfg_start_recurse, False)
+        self.assertEqual(bob.cfg_start_resolver, False)
 
 
 # Class for testing the Bob.start_all_processes() method call.
 # Class for testing the Bob.start_all_processes() method call.
 #
 #
@@ -157,7 +157,7 @@ class StartAllProcessesBob(BoB):
         self.cfgmgr = False
         self.cfgmgr = False
         self.ccsession = False
         self.ccsession = False
         self.auth = False
         self.auth = False
-        self.recurse = False
+        self.resolver = False
         self.xfrout = False
         self.xfrout = False
         self.xfrin = False
         self.xfrin = False
         self.zonemgr = False
         self.zonemgr = False
@@ -180,8 +180,8 @@ class StartAllProcessesBob(BoB):
     def start_auth(self, c_channel_env):
     def start_auth(self, c_channel_env):
         self.auth = True
         self.auth = True
 
 
-    def start_recurse(self, c_channel_env):
-        self.recurse = True
+    def start_resolver(self, c_channel_env):
+        self.resolver = True
 
 
     def start_xfrout(self, c_channel_env):
     def start_xfrout(self, c_channel_env):
         self.xfrout = True
         self.xfrout = True
@@ -206,14 +206,14 @@ class TestStartAllProcessesBob(unittest.TestCase):
         self.assertEqual(bob.cfgmgr, False)
         self.assertEqual(bob.cfgmgr, False)
         self.assertEqual(bob.ccsession, False)
         self.assertEqual(bob.ccsession, False)
         self.assertEqual(bob.auth, False)
         self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.resolver, False)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.zonemgr, False)
         self.assertEqual(bob.zonemgr, False)
         self.assertEqual(bob.stats, False)
         self.assertEqual(bob.stats, False)
         self.assertEqual(bob.cmdctl, False)
         self.assertEqual(bob.cmdctl, False)
 
 
-    # Checks the processes started when starting neither auth nor recurse
+    # Checks the processes started when starting neither auth nor resolver
     # is specified.
     # is specified.
     def test_start_none(self):
     def test_start_none(self):
         # Created Bob and ensure initialization correct
         # Created Bob and ensure initialization correct
@@ -223,7 +223,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         # Start processes and check what was started
         # Start processes and check what was started
         c_channel_env = {}
         c_channel_env = {}
         bob.cfg_start_auth = False
         bob.cfg_start_auth = False
-        bob.cfg_start_recurse = False
+        bob.cfg_start_resolver = False
 
 
         bob.start_all_processes(c_channel_env)
         bob.start_all_processes(c_channel_env)
 
 
@@ -231,7 +231,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.auth, False)
         self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.resolver, False)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.zonemgr, False)
         self.assertEqual(bob.zonemgr, False)
@@ -247,7 +247,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         # Start processes and check what was started
         # Start processes and check what was started
         c_channel_env = {}
         c_channel_env = {}
         bob.cfg_start_auth = True
         bob.cfg_start_auth = True
-        bob.cfg_start_recurse = False
+        bob.cfg_start_resolver = False
 
 
         bob.start_all_processes(c_channel_env)
         bob.start_all_processes(c_channel_env)
 
 
@@ -255,15 +255,15 @@ class TestStartAllProcessesBob(unittest.TestCase):
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.auth, True)
         self.assertEqual(bob.auth, True)
-        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.resolver, False)
         self.assertEqual(bob.xfrout, True)
         self.assertEqual(bob.xfrout, True)
         self.assertEqual(bob.xfrin, True)
         self.assertEqual(bob.xfrin, True)
         self.assertEqual(bob.zonemgr, True)
         self.assertEqual(bob.zonemgr, True)
         self.assertEqual(bob.stats, True)
         self.assertEqual(bob.stats, True)
         self.assertEqual(bob.cmdctl, True)
         self.assertEqual(bob.cmdctl, True)
 
 
-    # Checks the processes started when starting only the recurse process
-    def test_start_recurse(self):
+    # Checks the processes started when starting only the resolver process
+    def test_start_resolver(self):
         # Created Bob and ensure initialization correct
         # Created Bob and ensure initialization correct
         bob = StartAllProcessesBob()
         bob = StartAllProcessesBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
@@ -271,7 +271,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         # Start processes and check what was started
         # Start processes and check what was started
         c_channel_env = {}
         c_channel_env = {}
         bob.cfg_start_auth = False
         bob.cfg_start_auth = False
-        bob.cfg_start_recurse = True
+        bob.cfg_start_resolver = True
 
 
         bob.start_all_processes(c_channel_env)
         bob.start_all_processes(c_channel_env)
 
 
@@ -279,14 +279,14 @@ class TestStartAllProcessesBob(unittest.TestCase):
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.auth, False)
         self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.resolver, True)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrout, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.xfrin, False)
         self.assertEqual(bob.zonemgr, False)
         self.assertEqual(bob.zonemgr, False)
         self.assertEqual(bob.stats, True)
         self.assertEqual(bob.stats, True)
         self.assertEqual(bob.cmdctl, True)
         self.assertEqual(bob.cmdctl, True)
 
 
-    # Checks the processes started when starting both auth and recurse process
+    # Checks the processes started when starting both auth and resolver process
     def test_start_both(self):
     def test_start_both(self):
         # Created Bob and ensure initialization correct
         # Created Bob and ensure initialization correct
         bob = StartAllProcessesBob()
         bob = StartAllProcessesBob()
@@ -295,7 +295,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         # Start processes and check what was started
         # Start processes and check what was started
         c_channel_env = {}
         c_channel_env = {}
         bob.cfg_start_auth = True
         bob.cfg_start_auth = True
-        bob.cfg_start_recurse = True
+        bob.cfg_start_resolver = True
 
 
         bob.start_all_processes(c_channel_env)
         bob.start_all_processes(c_channel_env)
 
 
@@ -303,7 +303,7 @@ class TestStartAllProcessesBob(unittest.TestCase):
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.cfgmgr, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.ccsession, True)
         self.assertEqual(bob.auth, True)
         self.assertEqual(bob.auth, True)
-        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.resolver, True)
         self.assertEqual(bob.xfrout, True)
         self.assertEqual(bob.xfrout, True)
         self.assertEqual(bob.xfrin, True)
         self.assertEqual(bob.xfrin, True)
         self.assertEqual(bob.zonemgr, True)
         self.assertEqual(bob.zonemgr, True)

+ 17 - 7
src/bin/bindctl/bindcmd.py

@@ -217,16 +217,24 @@ class BindCmdInterpreter(Cmd):
         for module_name in self.config_data.get_config_item_list():
         for module_name in self.config_data.get_config_item_list():
             self._prepare_module_commands(self.config_data.get_module_spec(module_name))
             self._prepare_module_commands(self.config_data.get_module_spec(module_name))
 
 
+    def _send_message(self, url, body):
+        headers = {"cookie" : self.session_id}
+        self.conn.request('GET', url, body, headers)
+        res = self.conn.getresponse()
+        return res.status, res.read()
+
     def send_GET(self, url, body = None):
     def send_GET(self, url, body = None):
         '''Send GET request to cmdctl, session id is send with the name
         '''Send GET request to cmdctl, session id is send with the name
         'cookie' in header.
         'cookie' in header.
         '''
         '''
-        headers = {"cookie" : self.session_id}
-        self.conn.request('GET', url, body, headers)
-        res = self.conn.getresponse()
-        reply_msg = res.read()
+        status, reply_msg = self._send_message(url, body)
+        if status == http.client.UNAUTHORIZED:
+            if self.login_to_cmdctl():
+                # successful, so try send again
+                status, reply_msg = self._send_message(url, body)
+            
         if reply_msg:
         if reply_msg:
-           return json.loads(reply_msg.decode())
+            return json.loads(reply_msg.decode())
         else:
         else:
             return {}
             return {}
        
        
@@ -668,9 +676,11 @@ class BindCmdInterpreter(Cmd):
         if (len(cmd.params) != 0):
         if (len(cmd.params) != 0):
             cmd_params = json.dumps(cmd.params)
             cmd_params = json.dumps(cmd.params)
 
 
-        print("send the command to cmd-ctrld")        
         reply = self.send_POST(url, cmd.params)
         reply = self.send_POST(url, cmd.params)
         data = reply.read().decode()
         data = reply.read().decode()
-        print("received reply:", data)
+        # The reply is a string containing JSON data,
+        # parse it, then prettyprint
+        if data != "" and data != "{}":
+            print(json.dumps(json.loads(data), sort_keys=True, indent=4))
 
 
 
 

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

@@ -61,8 +61,6 @@ def main():
     except ConfigManagerDataReadError as cmdre:
     except ConfigManagerDataReadError as cmdre:
         print("[b10-cfgmgr] " + str(cmdre))
         print("[b10-cfgmgr] " + str(cmdre))
         return 2
         return 2
-    if cm:
-        return cm.write_config()
     return 0
     return 0
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 2 - 1
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -58,7 +58,8 @@ class TestConfigManagerStartup(unittest.TestCase):
         self.assertTrue(b.cm.read_config_called)
         self.assertTrue(b.cm.read_config_called)
         self.assertTrue(b.cm.notify_boss_called)
         self.assertTrue(b.cm.notify_boss_called)
         self.assertTrue(b.cm.run_called)
         self.assertTrue(b.cm.run_called)
-        self.assertTrue(b.cm.write_config_called)
+        # if there are no changes, config is not written
+        self.assertFalse(b.cm.write_config_called)
 
 
         self.assertTrue(b.cm.running)
         self.assertTrue(b.cm.running)
         b.signal_handler(None, None)
         b.signal_handler(None, None)

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

@@ -1,57 +0,0 @@
-SUBDIRS = . tests
-
-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 += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
-AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-if USE_STATIC_LINK
-AM_LDFLAGS = -static
-endif
-
-pkglibexecdir = $(libexecdir)/@PACKAGE@
-
-CLEANFILES = *.gcno *.gcda recurse.spec spec_config.h
-
-man_MANS = b10-recurse.8
-EXTRA_DIST = $(man_MANS) b10-recurse.xml
-
-if ENABLE_MAN
-
-b10-recurse.8: b10-recurse.xml
-	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-recurse.xml
-
-endif
-
-recurse.spec: recurse.spec.pre
-	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" recurse.spec.pre >$@
-
-spec_config.h: spec_config.h.pre
-	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
-
-BUILT_SOURCES = spec_config.h 
-pkglibexec_PROGRAMS = b10-recurse
-b10_recurse_SOURCES = recursor.cc recursor.h
-b10_recurse_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
-b10_recurse_SOURCES += $(top_builddir)/src/bin/auth/common.h
-b10_recurse_SOURCES += main.cc
-b10_recurse_LDADD =  $(top_builddir)/src/lib/dns/libdns++.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/cc/libcc.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
-b10_recurse_LDADD += $(top_builddir)/src/lib/log/liblog.la
-b10_recurse_LDADD += $(top_builddir)/src/bin/auth/change_user.o
-b10_recurse_LDFLAGS = -pthread
-
-# TODO: config.h.in is wrong because doesn't honor pkgdatadir
-# and can't use @datadir@ because doesn't expand default ${prefix}
-b10_recursedir = $(DESTDIR)$(pkgdatadir)
-b10_recurse_DATA = recurse.spec
-

+ 57 - 0
src/bin/resolver/Makefile.am

@@ -0,0 +1,57 @@
+SUBDIRS = . tests
+
+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 += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda resolver.spec spec_config.h
+
+man_MANS = b10-resolver.8
+EXTRA_DIST = $(man_MANS) b10-resolver.xml
+
+if ENABLE_MAN
+
+b10-resolver.8: b10-resolver.xml
+	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-resolver.xml
+
+endif
+
+resolver.spec: resolver.spec.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" resolver.spec.pre >$@
+
+spec_config.h: spec_config.h.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
+
+BUILT_SOURCES = spec_config.h 
+pkglibexec_PROGRAMS = b10-resolver
+b10_resolver_SOURCES = resolver.cc resolver.h
+b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
+b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
+b10_resolver_SOURCES += main.cc
+b10_resolver_LDADD =  $(top_builddir)/src/lib/dns/libdns++.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/log/liblog.la
+b10_resolver_LDADD += $(top_builddir)/src/bin/auth/change_user.o
+b10_resolver_LDFLAGS = -pthread
+
+# TODO: config.h.in is wrong because doesn't honor pkgdatadir
+# and can't use @datadir@ because doesn't expand default ${prefix}
+b10_resolverdir = $(DESTDIR)$(pkgdatadir)
+b10_resolver_DATA = resolver.spec
+

+ 10 - 10
src/bin/recurse/b10-recurse.8

@@ -1,13 +1,13 @@
 '\" t
 '\" t
-.\"     Title: b10-recurse
+.\"     Title: b10-resolver
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: December 27, 2010
+.\"      Date: January 3, 2011
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "B10\-RECURSE" "8" "December 27, 2010" "BIND10" "BIND10"
+.TH "B10\-RESOLVER" "8" "January 3, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -19,14 +19,14 @@
 .\" * MAIN CONTENT STARTS HERE *
 .\" * MAIN CONTENT STARTS HERE *
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .SH "NAME"
 .SH "NAME"
-b10-recurse \- Recursive DNS server
+b10-resolver \- Recursive DNS server
 .SH "SYNOPSIS"
 .SH "SYNOPSIS"
-.HP \w'\fBb10\-recurse\fR\ 'u
-\fBb10\-recurse\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.HP \w'\fBb10\-resolver\fR\ 'u
+\fBb10\-resolver\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
 .SH "DESCRIPTION"
 .SH "DESCRIPTION"
 .PP
 .PP
 The
 The
-\fBb10\-recurse\fR
+\fBb10\-resolver\fR
 daemon provides the BIND 10 recursive DNS server\&. Normally it is started by the
 daemon provides the BIND 10 recursive DNS server\&. Normally it is started by the
 \fBbind10\fR(8)
 \fBbind10\fR(8)
 boss process\&.
 boss process\&.
@@ -34,7 +34,7 @@ boss process\&.
 This daemon communicates with other BIND 10 components over a
 This daemon communicates with other BIND 10 components over a
 \fBb10-msgq\fR(8)
 \fBb10-msgq\fR(8)
 C\-Channel connection\&. If this connection is not established,
 C\-Channel connection\&. If this connection is not established,
-\fBb10\-recurse\fR
+\fBb10\-resolver\fR
 will exit\&.
 will exit\&.
 .PP
 .PP
 It also receives its configurations from
 It also receives its configurations from
@@ -63,7 +63,7 @@ The arguments are as follows:
 \fB\-u \fR\fB\fIusername\fR\fR
 \fB\-u \fR\fB\fIusername\fR\fR
 .RS 4
 .RS 4
 The user name of the
 The user name of the
-\fBb10\-recurse\fR
+\fBb10\-resolver\fR
 daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
 daemon\&. If specified, the daemon changes the process owner to the specified user\&. The
 \fIusername\fR
 \fIusername\fR
 must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
 must be either a valid numeric user ID or a valid user name\&. By default the daemon runs as the user who invokes it\&.
@@ -87,7 +87,7 @@ BIND 10 Guide\&.
 .SH "HISTORY"
 .SH "HISTORY"
 .PP
 .PP
 The
 The
-\fBb10\-recurse\fR
+\fBb10\-resolver\fR
 daemon was first coded in September 2010\&.
 daemon was first coded in September 2010\&.
 .SH "COPYRIGHT"
 .SH "COPYRIGHT"
 .br
 .br

+ 8 - 8
src/bin/recurse/b10-recurse.xml

@@ -21,17 +21,17 @@
 <refentry>
 <refentry>
 
 
   <refentryinfo>
   <refentryinfo>
-    <date>December 27, 2010</date>
+    <date>January 3, 2011</date>
   </refentryinfo>
   </refentryinfo>
 
 
   <refmeta>
   <refmeta>
-    <refentrytitle>b10-recurse</refentrytitle>
+    <refentrytitle>b10-resolver</refentrytitle>
     <manvolnum>8</manvolnum>
     <manvolnum>8</manvolnum>
     <refmiscinfo>BIND10</refmiscinfo>
     <refmiscinfo>BIND10</refmiscinfo>
   </refmeta>
   </refmeta>
 
 
   <refnamediv>
   <refnamediv>
-    <refname>b10-recurse</refname>
+    <refname>b10-resolver</refname>
     <refpurpose>Recursive DNS server</refpurpose>
     <refpurpose>Recursive DNS server</refpurpose>
   </refnamediv>
   </refnamediv>
 
 
@@ -44,7 +44,7 @@
 
 
   <refsynopsisdiv>
   <refsynopsisdiv>
     <cmdsynopsis>
     <cmdsynopsis>
-      <command>b10-recurse</command>
+      <command>b10-resolver</command>
       <arg><option>-u <replaceable>username</replaceable></option></arg>
       <arg><option>-u <replaceable>username</replaceable></option></arg>
       <arg><option>-v</option></arg>
       <arg><option>-v</option></arg>
     </cmdsynopsis>
     </cmdsynopsis>
@@ -52,7 +52,7 @@
 
 
   <refsect1>
   <refsect1>
     <title>DESCRIPTION</title>
     <title>DESCRIPTION</title>
-    <para>The <command>b10-recurse</command> daemon provides the BIND 10
+    <para>The <command>b10-resolver</command> daemon provides the BIND 10
       recursive DNS server.  Normally it is started by the
       recursive DNS server.  Normally it is started by the
       <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       boss process.
       boss process.
@@ -62,7 +62,7 @@
       This daemon communicates with other BIND 10 components over a
       This daemon communicates with other BIND 10 components over a
       <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       C-Channel connection.  If this connection is not established,
       C-Channel connection.  If this connection is not established,
-      <command>b10-recurse</command> will exit.
+      <command>b10-resolver</command> will exit.
     </para>
     </para>
 
 
     <para>
     <para>
@@ -89,7 +89,7 @@
         <term><option>-u <replaceable>username</replaceable></option></term>
         <term><option>-u <replaceable>username</replaceable></option></term>
         <listitem>
         <listitem>
 	  <para>
 	  <para>
-	    The user name of the <command>b10-recurse</command> daemon.
+	    The user name of the <command>b10-resolver</command> daemon.
 	    If specified, the daemon changes the process owner to the
 	    If specified, the daemon changes the process owner to the
 	    specified user.
 	    specified user.
 	    The <replaceable>username</replaceable> must be either a
 	    The <replaceable>username</replaceable> must be either a
@@ -141,7 +141,7 @@
   <refsect1>
   <refsect1>
     <title>HISTORY</title>
     <title>HISTORY</title>
     <para>
     <para>
-      The <command>b10-recurse</command> daemon was first coded in
+      The <command>b10-resolver</command> daemon was first coded in
       September 2010.
       September 2010.
     </para>
     </para>
   </refsect1>
   </refsect1>

+ 17 - 17
src/bin/recurse/main.cc

@@ -44,8 +44,8 @@
 #include <auth/change_user.h>
 #include <auth/change_user.h>
 #include <auth/common.h>
 #include <auth/common.h>
 
 
-#include <recurse/spec_config.h>
-#include <recurse/recursor.h>
+#include <resolver/spec_config.h>
+#include <resolver/resolver.h>
 
 
 #include <log/dummylog.h>
 #include <log/dummylog.h>
 
 
@@ -59,14 +59,14 @@ using namespace asiolink;
 namespace {
 namespace {
 
 
 // Default port current 5300 for testing purposes
 // Default port current 5300 for testing purposes
-static const string PROGRAM = "Recurse";
+static const string PROGRAM = "Resolver";
 
 
 IOService io_service;
 IOService io_service;
-static Recursor *recursor;
+static Resolver *resolver;
 
 
 ConstElementPtr
 ConstElementPtr
 my_config_handler(ConstElementPtr new_config) {
 my_config_handler(ConstElementPtr new_config) {
-    return (recursor->updateConfig(new_config));
+    return (resolver->updateConfig(new_config));
 }
 }
 
 
 ConstElementPtr
 ConstElementPtr
@@ -86,7 +86,7 @@ my_command_handler(const string& command, ConstElementPtr args) {
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage:  b10-recurse [-u user] [-v]" << endl;
+    cerr << "Usage:  b10-resolver [-u user] [-v]" << endl;
     cerr << "\t-u: change process UID to the specified user" << endl;
     cerr << "\t-u: change process UID to the specified user" << endl;
     cerr << "\t-v: verbose output" << endl;
     cerr << "\t-v: verbose output" << endl;
     exit(1);
     exit(1);
@@ -95,7 +95,7 @@ usage() {
 
 
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
-    isc::log::dprefix = "b10-recurse";
+    isc::log::dprefix = "b10-resolver";
     int ch;
     int ch;
     const char* uid = NULL;
     const char* uid = NULL;
 
 
@@ -133,21 +133,21 @@ main(int argc, char* argv[]) {
         string specfile;
         string specfile;
         if (getenv("B10_FROM_BUILD")) {
         if (getenv("B10_FROM_BUILD")) {
             specfile = string(getenv("B10_FROM_BUILD")) +
             specfile = string(getenv("B10_FROM_BUILD")) +
-                "/src/bin/recurse/recurse.spec";
+                "/src/bin/resolver/resolver.spec";
         } else {
         } else {
-            specfile = string(RECURSE_SPECFILE_LOCATION);
+            specfile = string(RESOLVER_SPECFILE_LOCATION);
         }
         }
 
 
-        recursor = new Recursor();
+        resolver = new Resolver();
         dlog("Server created.");
         dlog("Server created.");
 
 
-        SimpleCallback* checkin = recursor->getCheckinProvider();
-        DNSLookup* lookup = recursor->getDNSLookupProvider();
-        DNSAnswer* answer = recursor->getDNSAnswerProvider();
+        SimpleCallback* checkin = resolver->getCheckinProvider();
+        DNSLookup* lookup = resolver->getDNSLookupProvider();
+        DNSAnswer* answer = resolver->getDNSAnswerProvider();
 
 
         DNSService dns_service(io_service, checkin, lookup, answer);
         DNSService dns_service(io_service, checkin, lookup, answer);
 
 
-        recursor->setDNSService(dns_service);
+        resolver->setDNSService(dns_service);
         dlog("IOService created.");
         dlog("IOService created.");
 
 
         cc_session = new Session(io_service.get_io_service());
         cc_session = new Session(io_service.get_io_service());
@@ -163,19 +163,19 @@ main(int argc, char* argv[]) {
             changeUser(uid);
             changeUser(uid);
         }
         }
 
 
-        recursor->setConfigSession(config_session);
+        resolver->setConfigSession(config_session);
         dlog("Config loaded");
         dlog("Config loaded");
 
 
         dlog("Server started.");
         dlog("Server started.");
         io_service.run();
         io_service.run();
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
-        dlog(string("Server failed: ") + ex.what());
+        dlog(string("Server failed: ") + ex.what(),true);
         ret = 1;
         ret = 1;
     }
     }
 
 
     delete config_session;
     delete config_session;
     delete cc_session;
     delete cc_session;
-    delete recursor;
+    delete resolver;
 
 
     return (ret);
     return (ret);
 }
 }

+ 56 - 46
src/bin/recurse/recursor.cc

@@ -45,7 +45,7 @@
 
 
 #include <log/dummylog.h>
 #include <log/dummylog.h>
 
 
-#include <recurse/recursor.h>
+#include <resolver/resolver.h>
 
 
 using namespace std;
 using namespace std;
 
 
@@ -58,49 +58,52 @@ using namespace asiolink;
 
 
 typedef pair<string, uint16_t> addr_t;
 typedef pair<string, uint16_t> addr_t;
 
 
-class RecursorImpl {
+class ResolverImpl {
 private:
 private:
     // prohibit copy
     // prohibit copy
-    RecursorImpl(const RecursorImpl& source);
-    RecursorImpl& operator=(const RecursorImpl& source);
+    ResolverImpl(const ResolverImpl& source);
+    ResolverImpl& operator=(const ResolverImpl& source);
 public:
 public:
-    RecursorImpl() :
+    ResolverImpl() :
         config_session_(NULL),
         config_session_(NULL),
         rec_query_(NULL)
         rec_query_(NULL)
     {}
     {}
 
 
-    ~RecursorImpl() {
+    ~ResolverImpl() {
         queryShutdown();
         queryShutdown();
     }
     }
 
 
     void querySetup(DNSService& dnss) {
     void querySetup(DNSService& dnss) {
         assert(!rec_query_); // queryShutdown must be called first
         assert(!rec_query_); // queryShutdown must be called first
         dlog("Query setup");
         dlog("Query setup");
-        rec_query_ = new RecursiveQuery(dnss, upstream_);
+        rec_query_ = new RecursiveQuery(dnss, upstream_, timeout_, retries_);
     }
     }
 
 
     void queryShutdown() {
     void queryShutdown() {
-        dlog("Query shutdown");
-        delete rec_query_;
-        rec_query_ = NULL;
+        // only shut down if we have actually called querySetup before
+        // (this is not a safety check, just to prevent logging of
+        // actions that are not performed
+        if (rec_query_) {
+            dlog("Query shutdown");
+            delete rec_query_;
+            rec_query_ = NULL;
+        }
     }
     }
 
 
     void setForwardAddresses(const vector<addr_t>& upstream,
     void setForwardAddresses(const vector<addr_t>& upstream,
         DNSService *dnss)
         DNSService *dnss)
     {
     {
-        queryShutdown();
         upstream_ = upstream;
         upstream_ = upstream;
         if (dnss) {
         if (dnss) {
             if (upstream_.empty()) {
             if (upstream_.empty()) {
                 dlog("Asked to do full recursive, but not implemented yet. "
                 dlog("Asked to do full recursive, but not implemented yet. "
-                    "I'll do nothing.");
+                    "I'll do nothing.",true);
             } else {
             } else {
                 dlog("Setting forward addresses:");
                 dlog("Setting forward addresses:");
                 BOOST_FOREACH(const addr_t& address, upstream) {
                 BOOST_FOREACH(const addr_t& address, upstream) {
                     dlog(" " + address.first + ":" +
                     dlog(" " + address.first + ":" +
                         boost::lexical_cast<string>(address.second));
                         boost::lexical_cast<string>(address.second));
                 }
                 }
-                querySetup(*dnss);
             }
             }
         }
         }
     }
     }
@@ -112,7 +115,7 @@ public:
     /// Currently non-configurable, but will be.
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
 
 
-    /// These members are public because Recursor accesses them directly.
+    /// These members are public because Resolver accesses them directly.
     ModuleCCSession* config_session_;
     ModuleCCSession* config_session_;
     /// Addresses of the forward nameserver
     /// Addresses of the forward nameserver
     vector<addr_t> upstream_;
     vector<addr_t> upstream_;
@@ -201,10 +204,10 @@ makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
 
 
 // This is a derived class of \c DNSLookup, to serve as a
 // This is a derived class of \c DNSLookup, to serve as a
 // callback in the asiolink module.  It calls
 // callback in the asiolink module.  It calls
-// Recursor::processMessage() on a single DNS message.
+// Resolver::processMessage() on a single DNS message.
 class MessageLookup : public DNSLookup {
 class MessageLookup : public DNSLookup {
 public:
 public:
-    MessageLookup(Recursor* srv) : server_(srv) {}
+    MessageLookup(Resolver* srv) : server_(srv) {}
 
 
     // \brief Handle the DNS Lookup
     // \brief Handle the DNS Lookup
     virtual void operator()(const IOMessage& io_message, MessagePtr message,
     virtual void operator()(const IOMessage& io_message, MessagePtr message,
@@ -213,7 +216,7 @@ public:
         server_->processMessage(io_message, message, buffer, server);
         server_->processMessage(io_message, message, buffer, server);
     }
     }
 private:
 private:
-    Recursor* server_;
+    Resolver* server_;
 };
 };
 
 
 // This is a derived class of \c DNSAnswer, to serve as a
 // This is a derived class of \c DNSAnswer, to serve as a
@@ -260,6 +263,7 @@ public:
                 Message incoming(Message::PARSE);
                 Message incoming(Message::PARSE);
                 InputBuffer ibuf(buffer->getData(), buffer->getLength());
                 InputBuffer ibuf(buffer->getData(), buffer->getLength());
                 incoming.fromWire(ibuf);
                 incoming.fromWire(ibuf);
+                message->setRcode(incoming.getRcode());
                 for_each(incoming.beginSection(Message::SECTION_ANSWER),
                 for_each(incoming.beginSection(Message::SECTION_ANSWER),
                          incoming.endSection(Message::SECTION_ANSWER),
                          incoming.endSection(Message::SECTION_ANSWER),
                          SectionInserter(message, Message::SECTION_ANSWER));
                          SectionInserter(message, Message::SECTION_ANSWER));
@@ -300,50 +304,48 @@ public:
 // configuration messages, and executes them if found.
 // configuration messages, and executes them if found.
 class ConfigCheck : public SimpleCallback {
 class ConfigCheck : public SimpleCallback {
 public:
 public:
-    ConfigCheck(Recursor* srv) : server_(srv) {}
+    ConfigCheck(Resolver* srv) : server_(srv) {}
     virtual void operator()(const IOMessage&) const {
     virtual void operator()(const IOMessage&) const {
         if (server_->getConfigSession()->hasQueuedMsgs()) {
         if (server_->getConfigSession()->hasQueuedMsgs()) {
             server_->getConfigSession()->checkCommand();
             server_->getConfigSession()->checkCommand();
         }
         }
     }
     }
 private:
 private:
-    Recursor* server_;
+    Resolver* server_;
 };
 };
 
 
-Recursor::Recursor() :
-    impl_(new RecursorImpl()),
+Resolver::Resolver() :
+    impl_(new ResolverImpl()),
     checkin_(new ConfigCheck(this)),
     checkin_(new ConfigCheck(this)),
     dns_lookup_(new MessageLookup(this)),
     dns_lookup_(new MessageLookup(this)),
     dns_answer_(new MessageAnswer)
     dns_answer_(new MessageAnswer)
 {}
 {}
 
 
-Recursor::~Recursor() {
+Resolver::~Resolver() {
     delete impl_;
     delete impl_;
     delete checkin_;
     delete checkin_;
     delete dns_lookup_;
     delete dns_lookup_;
     delete dns_answer_;
     delete dns_answer_;
-    dlog("Deleting the Recursor");
+    dlog("Deleting the Resolver",true);
 }
 }
 
 
 void
 void
-Recursor::setDNSService(asiolink::DNSService& dnss) {
-    impl_->queryShutdown();
-    impl_->querySetup(dnss);
+Resolver::setDNSService(asiolink::DNSService& dnss) {
     dnss_ = &dnss;
     dnss_ = &dnss;
 }
 }
 
 
 void
 void
-Recursor::setConfigSession(ModuleCCSession* config_session) {
+Resolver::setConfigSession(ModuleCCSession* config_session) {
     impl_->config_session_ = config_session;
     impl_->config_session_ = config_session;
 }
 }
 
 
 ModuleCCSession*
 ModuleCCSession*
-Recursor::getConfigSession() const {
+Resolver::getConfigSession() const {
     return (impl_->config_session_);
     return (impl_->config_session_);
 }
 }
 
 
 void
 void
-Recursor::processMessage(const IOMessage& io_message, MessagePtr message,
+Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
                         OutputBufferPtr buffer, DNSServer* server)
                         OutputBufferPtr buffer, DNSServer* server)
 {
 {
     dlog("Got a DNS message");
     dlog("Got a DNS message");
@@ -360,7 +362,7 @@ Recursor::processMessage(const IOMessage& io_message, MessagePtr message,
             return;
             return;
         }
         }
     } catch (const Exception& ex) {
     } catch (const Exception& ex) {
-        dlog(string("DNS packet exception: ") + ex.what());
+        dlog(string("DNS packet exception: ") + ex.what(),true);
         server->resume(false);
         server->resume(false);
         return;
         return;
     }
     }
@@ -422,7 +424,7 @@ Recursor::processMessage(const IOMessage& io_message, MessagePtr message,
 }
 }
 
 
 void
 void
-RecursorImpl::processNormalQuery(const Question& question, MessagePtr message,
+ResolverImpl::processNormalQuery(const Question& question, MessagePtr message,
                                  OutputBufferPtr buffer, DNSServer* server)
                                  OutputBufferPtr buffer, DNSServer* server)
 {
 {
     dlog("Processing normal query");
     dlog("Processing normal query");
@@ -435,7 +437,7 @@ RecursorImpl::processNormalQuery(const Question& question, MessagePtr message,
     if (edns) {
     if (edns) {
         EDNSPtr edns_response(new EDNS());
         EDNSPtr edns_response(new EDNS());
         edns_response->setDNSSECAwareness(dnssec_ok);
         edns_response->setDNSSECAwareness(dnssec_ok);
-        edns_response->setUDPSize(RecursorImpl::DEFAULT_LOCAL_UDPSIZE);
+        edns_response->setUDPSize(ResolverImpl::DEFAULT_LOCAL_UDPSIZE);
         message->setEDNS(edns_response);
         message->setEDNS(edns_response);
     }
     }
     rec_query_->sendQuery(question, buffer, server);
     rec_query_->sendQuery(question, buffer, server);
@@ -482,7 +484,7 @@ parseAddresses(ConstElementPtr addresses) {
 }
 }
 
 
 ConstElementPtr
 ConstElementPtr
-Recursor::updateConfig(ConstElementPtr config) {
+Resolver::updateConfig(ConstElementPtr config) {
     dlog("New config comes: " + config->toWire());
     dlog("New config comes: " + config->toWire());
 
 
     try {
     try {
@@ -514,35 +516,45 @@ Recursor::updateConfig(ConstElementPtr config) {
         }
         }
         // Everything OK, so commit the changes
         // Everything OK, so commit the changes
         // listenAddresses can fail to bind, so try them first
         // listenAddresses can fail to bind, so try them first
+        bool need_query_restart = false;
+        
         if (listenAddressesE) {
         if (listenAddressesE) {
             setListenAddresses(listenAddresses);
             setListenAddresses(listenAddresses);
+            need_query_restart = true;
         }
         }
         if (forwardAddressesE) {
         if (forwardAddressesE) {
             setForwardAddresses(forwardAddresses);
             setForwardAddresses(forwardAddresses);
+            need_query_restart = true;
         }
         }
         if (set_timeouts) {
         if (set_timeouts) {
             setTimeouts(timeout, retries);
             setTimeouts(timeout, retries);
+            need_query_restart = true;
+        }
+
+        if (need_query_restart) {
+            impl_->queryShutdown();
+            impl_->querySetup(*dnss_);
         }
         }
         return (isc::config::createAnswer());
         return (isc::config::createAnswer());
     } catch (const isc::Exception& error) {
     } catch (const isc::Exception& error) {
-        dlog(string("error in config: ") + error.what());
+        dlog(string("error in config: ") + error.what(),true);
         return (isc::config::createAnswer(1, error.what()));
         return (isc::config::createAnswer(1, error.what()));
     }
     }
 }
 }
 
 
 void
 void
-Recursor::setForwardAddresses(const vector<addr_t>& addresses)
+Resolver::setForwardAddresses(const vector<addr_t>& addresses)
 {
 {
     impl_->setForwardAddresses(addresses, dnss_);
     impl_->setForwardAddresses(addresses, dnss_);
 }
 }
 
 
 bool
 bool
-Recursor::isForwarding() const {
+Resolver::isForwarding() const {
     return (!impl_->upstream_.empty());
     return (!impl_->upstream_.empty());
 }
 }
 
 
 vector<addr_t>
 vector<addr_t>
-Recursor::getForwardAddresses() const {
+Resolver::getForwardAddresses() const {
     return (impl_->upstream_);
     return (impl_->upstream_);
 }
 }
 
 
@@ -559,7 +571,7 @@ setAddresses(DNSService *service, const vector<addr_t>& addresses) {
 }
 }
 
 
 void
 void
-Recursor::setListenAddresses(const vector<addr_t>& addresses) {
+Resolver::setListenAddresses(const vector<addr_t>& addresses) {
     try {
     try {
         dlog("Setting listen addresses:");
         dlog("Setting listen addresses:");
         BOOST_FOREACH(const addr_t& addr, addresses) {
         BOOST_FOREACH(const addr_t& addr, addresses) {
@@ -577,13 +589,13 @@ Recursor::setListenAddresses(const vector<addr_t>& addresses) {
          * If that fails, bad luck, but we are useless anyway, so just die
          * If that fails, bad luck, but we are useless anyway, so just die
          * and let boss start us again.
          * and let boss start us again.
          */
          */
-        dlog(string("Unable to set new address: ") + e.what());
+        dlog(string("Unable to set new address: ") + e.what(),true);
         try {
         try {
             setAddresses(dnss_, impl_->listen_);
             setAddresses(dnss_, impl_->listen_);
         }
         }
         catch (const exception& e2) {
         catch (const exception& e2) {
-            dlog(string("Unable to recover from error;"));
-            dlog(string("Rollback failed with: ") + e2.what());
+            dlog(string("Unable to recover from error;"),true);
+            dlog(string("Rollback failed with: ") + e2.what(),true);
             abort();
             abort();
         }
         }
         throw e; // Let it fly a little bit further
         throw e; // Let it fly a little bit further
@@ -591,20 +603,18 @@ Recursor::setListenAddresses(const vector<addr_t>& addresses) {
 }
 }
 
 
 void
 void
-Recursor::setTimeouts(int timeout, unsigned retries) {
+Resolver::setTimeouts(int timeout, unsigned retries) {
     dlog("Setting timeout to " + boost::lexical_cast<string>(timeout) +
     dlog("Setting timeout to " + boost::lexical_cast<string>(timeout) +
         " and retry count to " + boost::lexical_cast<string>(retries));
         " and retry count to " + boost::lexical_cast<string>(retries));
     impl_->timeout_ = timeout;
     impl_->timeout_ = timeout;
     impl_->retries_ = retries;
     impl_->retries_ = retries;
-    impl_->queryShutdown();
-    impl_->querySetup(*dnss_);
 }
 }
 pair<int, unsigned>
 pair<int, unsigned>
-Recursor::getTimeouts() const {
+Resolver::getTimeouts() const {
     return (pair<int, unsigned>(impl_->timeout_, impl_->retries_));
     return (pair<int, unsigned>(impl_->timeout_, impl_->retries_));
 }
 }
 
 
 vector<addr_t>
 vector<addr_t>
-Recursor::getListenAddresses() const {
+Resolver::getListenAddresses() const {
     return (impl_->listen_);
     return (impl_->listen_);
 }
 }

+ 11 - 11
src/bin/recurse/recursor.h

@@ -14,8 +14,8 @@
 
 
 // $Id$
 // $Id$
 
 
-#ifndef __RECURSOR_H
-#define __RECURSOR_H 1
+#ifndef __RESOLVER_H
+#define __RESOLVER_H 1
 
 
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
@@ -26,7 +26,7 @@
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 
 
-class RecursorImpl;
+class ResolverImpl;
 
 
 /**
 /**
  * \short The recursive nameserver.
  * \short The recursive nameserver.
@@ -37,7 +37,7 @@ class RecursorImpl;
  * answer. It doesn't really know about chasing referrals and similar, it
  * answer. It doesn't really know about chasing referrals and similar, it
  * simply plugs the parts that know into the network handling code.
  * simply plugs the parts that know into the network handling code.
  */
  */
-class Recursor {
+class Resolver {
     ///
     ///
     /// \name Constructors, Assignment Operator and Destructor.
     /// \name Constructors, Assignment Operator and Destructor.
     ///
     ///
@@ -45,12 +45,12 @@ class Recursor {
     /// intentionally defined as private.
     /// intentionally defined as private.
     //@{
     //@{
 private:
 private:
-    Recursor(const Recursor& source);
-    Recursor& operator=(const Recursor& source);
+    Resolver(const Resolver& source);
+    Resolver& operator=(const Resolver& source);
 public:
 public:
     /// The constructor.
     /// The constructor.
-    Recursor();
-    ~Recursor();
+    Resolver();
+    ~Resolver();
     //@}
     //@}
 
 
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
     /// \brief Process an incoming DNS message, then signal 'server' to resume 
@@ -76,7 +76,7 @@ public:
     /// \brief Handle commands from the config session
     /// \brief Handle commands from the config session
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
 
 
-    /// \brief Assign an ASIO IO Service queue to this Recursor object
+    /// \brief Assign an ASIO IO Service queue to this Resolver object
     void setDNSService(asiolink::DNSService& dnss);
     void setDNSService(asiolink::DNSService& dnss);
 
 
     /// \brief Return this object's ASIO IO Service queue
     /// \brief Return this object's ASIO IO Service queue
@@ -137,14 +137,14 @@ public:
     std::pair<int, unsigned> getTimeouts() const;
     std::pair<int, unsigned> getTimeouts() const;
 
 
 private:
 private:
-    RecursorImpl* impl_;
+    ResolverImpl* impl_;
     asiolink::DNSService* dnss_;
     asiolink::DNSService* dnss_;
     asiolink::SimpleCallback* checkin_;
     asiolink::SimpleCallback* checkin_;
     asiolink::DNSLookup* dns_lookup_;
     asiolink::DNSLookup* dns_lookup_;
     asiolink::DNSAnswer* dns_answer_;
     asiolink::DNSAnswer* dns_answer_;
 };
 };
 
 
-#endif // __RECURSOR_H
+#endif // __RESOLVER_H
 
 
 // Local Variables: 
 // Local Variables: 
 // mode: c++
 // mode: c++

+ 1 - 1
src/bin/recurse/recurse.spec.pre.in

@@ -1,6 +1,6 @@
 {
 {
   "module_spec": {
   "module_spec": {
-    "module_name": "Recurse",
+    "module_name": "Resolver",
     "module_description": "Recursive service",
     "module_description": "Recursive service",
     "config_data": [
     "config_data": [
       {
       {

+ 1 - 1
src/bin/recurse/spec_config.h.pre.in

@@ -12,4 +12,4 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#define RECURSE_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/recurse.spec"
+#define RESOLVER_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/resolver.spec"

+ 3 - 3
src/bin/recurse/tests/Makefile.am

@@ -19,9 +19,9 @@ if HAVE_GTEST
 TESTS += run_unittests
 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.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-run_unittests_SOURCES += ../recursor.h ../recursor.cc
-run_unittests_SOURCES += recursor_unittest.cc
-run_unittests_SOURCES += recursor_config_unittest.cc
+run_unittests_SOURCES += ../resolver.h ../resolver.cc
+run_unittests_SOURCES += resolver_unittest.cc
+run_unittests_SOURCES += resolver_config_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 14 - 14
src/bin/recurse/tests/recursor_config_unittest.cc

@@ -20,7 +20,7 @@
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 
 
-#include <recurse/recursor.h>
+#include <resolver/resolver.h>
 
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 #include <testutils/srv_test.h>
@@ -32,12 +32,12 @@ using namespace asiolink;
 using isc::UnitTestUtil;
 using isc::UnitTestUtil;
 
 
 namespace {
 namespace {
-class RecursorConfig : public ::testing::Test {
+class ResolverConfig : public ::testing::Test {
     public:
     public:
         IOService ios;
         IOService ios;
         DNSService dnss;
         DNSService dnss;
-        Recursor server;
-        RecursorConfig() :
+        Resolver server;
+        ResolverConfig() :
             dnss(ios, NULL, NULL, NULL)
             dnss(ios, NULL, NULL, NULL)
         {
         {
             server.setDNSService(dnss);
             server.setDNSService(dnss);
@@ -45,7 +45,7 @@ class RecursorConfig : public ::testing::Test {
         void invalidTest(const string &JOSN);
         void invalidTest(const string &JOSN);
 };
 };
 
 
-TEST_F(RecursorConfig, forwardAddresses) {
+TEST_F(ResolverConfig, forwardAddresses) {
     // Default value should be fully recursive
     // Default value should be fully recursive
     EXPECT_TRUE(server.getForwardAddresses().empty());
     EXPECT_TRUE(server.getForwardAddresses().empty());
     EXPECT_FALSE(server.isForwarding());
     EXPECT_FALSE(server.isForwarding());
@@ -69,7 +69,7 @@ TEST_F(RecursorConfig, forwardAddresses) {
     EXPECT_FALSE(server.isForwarding());
     EXPECT_FALSE(server.isForwarding());
 }
 }
 
 
-TEST_F(RecursorConfig, forwardAddressConfig) {
+TEST_F(ResolverConfig, forwardAddressConfig) {
     // Try putting there some address
     // Try putting there some address
     ElementPtr config(Element::fromJSON("{"
     ElementPtr config(Element::fromJSON("{"
         "\"forward_addresses\": ["
         "\"forward_addresses\": ["
@@ -97,13 +97,13 @@ TEST_F(RecursorConfig, forwardAddressConfig) {
 }
 }
 
 
 void
 void
-RecursorConfig::invalidTest(const string &JOSN) {
+ResolverConfig::invalidTest(const string &JOSN) {
     ElementPtr config(Element::fromJSON(JOSN));
     ElementPtr config(Element::fromJSON(JOSN));
     EXPECT_FALSE(server.updateConfig(config)->equals(
     EXPECT_FALSE(server.updateConfig(config)->equals(
         *isc::config::createAnswer())) << "Accepted config " << JOSN << endl;
         *isc::config::createAnswer())) << "Accepted config " << JOSN << endl;
 }
 }
 
 
-TEST_F(RecursorConfig, invalidForwardAddresses) {
+TEST_F(ResolverConfig, invalidForwardAddresses) {
     // Try torturing it with some invalid inputs
     // Try torturing it with some invalid inputs
     invalidTest("{"
     invalidTest("{"
         "\"forward_addresses\": \"error\""
         "\"forward_addresses\": \"error\""
@@ -128,7 +128,7 @@ TEST_F(RecursorConfig, invalidForwardAddresses) {
         "}]}");
         "}]}");
 }
 }
 
 
-TEST_F(RecursorConfig, listenAddresses) {
+TEST_F(ResolverConfig, listenAddresses) {
     // Default value should be fully recursive
     // Default value should be fully recursive
     EXPECT_TRUE(server.getListenAddresses().empty());
     EXPECT_TRUE(server.getListenAddresses().empty());
 
 
@@ -149,7 +149,7 @@ TEST_F(RecursorConfig, listenAddresses) {
     EXPECT_TRUE(server.getListenAddresses().empty());
     EXPECT_TRUE(server.getListenAddresses().empty());
 }
 }
 
 
-TEST_F(RecursorConfig, DISABLED_listenAddressConfig) {
+TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
     // Try putting there some address
     // Try putting there some address
     ElementPtr config(Element::fromJSON("{"
     ElementPtr config(Element::fromJSON("{"
         "\"listen_on\": ["
         "\"listen_on\": ["
@@ -185,7 +185,7 @@ TEST_F(RecursorConfig, DISABLED_listenAddressConfig) {
     EXPECT_EQ(5300, server.getListenAddresses()[0].second);
     EXPECT_EQ(5300, server.getListenAddresses()[0].second);
 }
 }
 
 
-TEST_F(RecursorConfig, invalidListenAddresses) {
+TEST_F(ResolverConfig, invalidListenAddresses) {
     // Try torturing it with some invalid inputs
     // Try torturing it with some invalid inputs
     invalidTest("{"
     invalidTest("{"
         "\"listen_on\": \"error\""
         "\"listen_on\": \"error\""
@@ -211,7 +211,7 @@ TEST_F(RecursorConfig, invalidListenAddresses) {
 }
 }
 
 
 // Just test it sets and gets the values correctly
 // Just test it sets and gets the values correctly
-TEST_F(RecursorConfig, timeouts) {
+TEST_F(ResolverConfig, timeouts) {
     server.setTimeouts(0, 1);
     server.setTimeouts(0, 1);
     EXPECT_EQ(0, server.getTimeouts().first);
     EXPECT_EQ(0, server.getTimeouts().first);
     EXPECT_EQ(1, server.getTimeouts().second);
     EXPECT_EQ(1, server.getTimeouts().second);
@@ -220,7 +220,7 @@ TEST_F(RecursorConfig, timeouts) {
     EXPECT_EQ(0, server.getTimeouts().second);
     EXPECT_EQ(0, server.getTimeouts().second);
 }
 }
 
 
-TEST_F(RecursorConfig, timeoutsConfig) {
+TEST_F(ResolverConfig, timeoutsConfig) {
     ElementPtr config = Element::fromJSON("{"
     ElementPtr config = Element::fromJSON("{"
             "\"timeout\": 1000,"
             "\"timeout\": 1000,"
             "\"retries\": 3"
             "\"retries\": 3"
@@ -231,7 +231,7 @@ TEST_F(RecursorConfig, timeoutsConfig) {
     EXPECT_EQ(3, server.getTimeouts().second);
     EXPECT_EQ(3, server.getTimeouts().second);
 }
 }
 
 
-TEST_F(RecursorConfig, invalidTimeoutsConfig) {
+TEST_F(ResolverConfig, invalidTimeoutsConfig) {
     invalidTest("{"
     invalidTest("{"
         "\"timeout\": \"error\""
         "\"timeout\": \"error\""
         "}");
         "}");

+ 16 - 16
src/bin/recurse/tests/recursor_unittest.cc

@@ -14,7 +14,7 @@
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 
 
-#include <recurse/recursor.h>
+#include <resolver/resolver.h>
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 #include <testutils/srv_test.h>
 
 
@@ -25,58 +25,58 @@ using isc::UnitTestUtil;
 namespace {
 namespace {
 const char* const TEST_PORT = "53535";
 const char* const TEST_PORT = "53535";
 
 
-class RecursorTest : public SrvTestBase{
+class ResolverTest : public SrvTestBase{
 protected:
 protected:
-    RecursorTest() : server(){}
+    ResolverTest() : server(){}
     virtual void processMessage() {
     virtual void processMessage() {
         server.processMessage(*io_message, parse_message, response_obuffer,
         server.processMessage(*io_message, parse_message, response_obuffer,
                               &dnsserv);
                               &dnsserv);
     }
     }
-    Recursor server;
+    Resolver server;
 };
 };
 
 
 // Unsupported requests.  Should result in NOTIMP.
 // Unsupported requests.  Should result in NOTIMP.
-TEST_F(RecursorTest, unsupportedRequest) {
+TEST_F(ResolverTest, unsupportedRequest) {
     unsupportedRequest();
     unsupportedRequest();
 }
 }
 
 
 // Multiple questions.  Should result in FORMERR.
 // Multiple questions.  Should result in FORMERR.
-TEST_F(RecursorTest, multiQuestion) {
-    multiQuestion();
+TEST_F(ResolverTest, multiQuestion) {
+    multiQuestion(); 
 }
 }
 
 
 // Incoming data doesn't even contain the complete header.  Must be silently
 // Incoming data doesn't even contain the complete header.  Must be silently
 // dropped.
 // dropped.
-TEST_F(RecursorTest, shortMessage) {
+TEST_F(ResolverTest, shortMessage) {
     shortMessage();
     shortMessage();
 }
 }
 
 
 // Response messages.  Must be silently dropped, whether it's a valid response
 // Response messages.  Must be silently dropped, whether it's a valid response
 // or malformed or could otherwise cause a protocol error.
 // or malformed or could otherwise cause a protocol error.
-TEST_F(RecursorTest, response) {
-    response();
+TEST_F(ResolverTest, response) {
+     response();
 }
 }
 
 
 // Query with a broken question
 // Query with a broken question
-TEST_F(RecursorTest, shortQuestion) {
+TEST_F(ResolverTest, shortQuestion) {
     shortQuestion();
     shortQuestion();
 }
 }
 
 
 // Query with a broken answer section
 // Query with a broken answer section
-TEST_F(RecursorTest, shortAnswer) {
+TEST_F(ResolverTest, shortAnswer) {
     shortAnswer();
     shortAnswer();
 }
 }
 
 
 // Query with unsupported version of EDNS.
 // Query with unsupported version of EDNS.
-TEST_F(RecursorTest, ednsBadVers) {
+TEST_F(ResolverTest, ednsBadVers) {
     ednsBadVers();
     ednsBadVers();
 }
 }
 
 
-TEST_F(RecursorTest, AXFROverUDP) {
+TEST_F(ResolverTest, AXFROverUDP) {
     axfrOverUDP();
     axfrOverUDP();
 }
 }
 
 
-TEST_F(RecursorTest, AXFRFail) {
+TEST_F(ResolverTest, AXFRFail) {
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                                        Name("example.com"), RRClass::IN(),
                                        Name("example.com"), RRClass::IN(),
                                        RRType::AXFR());
                                        RRType::AXFR());
@@ -88,7 +88,7 @@ TEST_F(RecursorTest, AXFRFail) {
                 QR_FLAG, 1, 0, 0, 0);
                 QR_FLAG, 1, 0, 0, 0);
 }
 }
 
 
-TEST_F(RecursorTest, notifyFail) {
+TEST_F(ResolverTest, notifyFail) {
     // Notify should always return NOTAUTH
     // Notify should always return NOTAUTH
     request_message.clear(Message::RENDER);
     request_message.clear(Message::RENDER);
     request_message.setOpcode(Opcode::NOTIFY());
     request_message.setOpcode(Opcode::NOTIFY());

src/bin/recurse/tests/run_unittests.cc → src/bin/resolver/tests/run_unittests.cc


+ 12 - 10
src/bin/xfrout/tests/xfrout_test.py

@@ -215,36 +215,38 @@ class TestXfroutSession(unittest.TestCase):
         rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
         rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
         self.assertEqual(82, get_rrset_len(rrset_soa))
         self.assertEqual(82, get_rrset_len(rrset_soa))
 
 
-    def test_zone_is_empty(self):
+    def test_zone_has_soa(self):
         global sqlite3_ds
         global sqlite3_ds
         def mydb1(zone, file):
         def mydb1(zone, file):
             return True
             return True
         sqlite3_ds.get_zone_soa = mydb1
         sqlite3_ds.get_zone_soa = mydb1
-        self.assertEqual(self.xfrsess._zone_is_empty(""), False)
+        self.assertTrue(self.xfrsess._zone_has_soa(""))
         def mydb2(zone, file):
         def mydb2(zone, file):
             return False
             return False
         sqlite3_ds.get_zone_soa = mydb2
         sqlite3_ds.get_zone_soa = mydb2
-        self.assertEqual(self.xfrsess._zone_is_empty(""), True)
+        self.assertFalse(self.xfrsess._zone_has_soa(""))
 
 
     def test_zone_exist(self):
     def test_zone_exist(self):
         global sqlite3_ds
         global sqlite3_ds
-        def zone_soa(zone, file):
+        def zone_exist(zone, file):
             return zone
             return zone
-        sqlite3_ds.get_zone_soa = zone_soa
-        self.assertEqual(self.xfrsess._zone_exist(True), True)
-        self.assertEqual(self.xfrsess._zone_exist(False), False)
+        sqlite3_ds.zone_exist = zone_exist
+        self.assertTrue(self.xfrsess._zone_exist(True))
+        self.assertFalse(self.xfrsess._zone_exist(False))
 
 
     def test_check_xfrout_available(self):
     def test_check_xfrout_available(self):
         def zone_exist(zone):
         def zone_exist(zone):
             return zone
             return zone
+        def zone_has_soa(zone):
+            return (not zone)
         self.xfrsess._zone_exist = zone_exist
         self.xfrsess._zone_exist = zone_exist
-        self.xfrsess._zone_is_empty = zone_exist
+        self.xfrsess._zone_has_soa = zone_has_soa
         self.assertEqual(self.xfrsess._check_xfrout_available(False).to_text(), "NOTAUTH")
         self.assertEqual(self.xfrsess._check_xfrout_available(False).to_text(), "NOTAUTH")
         self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "SERVFAIL")
         self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "SERVFAIL")
 
 
         def zone_empty(zone):
         def zone_empty(zone):
-            return not zone
-        self.xfrsess._zone_is_empty = zone_empty
+            return zone
+        self.xfrsess._zone_has_soa = zone_empty
         def false_func():
         def false_func():
             return False
             return False
         self.xfrsess.server.increase_transfers_counter = false_func
         self.xfrsess.server.increase_transfers_counter = false_func

+ 22 - 11
src/bin/xfrout/xfrout.py.in

@@ -193,30 +193,41 @@ class XfroutSession(BaseRequestHandler):
         self._send_message(sock_fd, msg)
         self._send_message(sock_fd, msg)
 
 
 
 
-    def _zone_is_empty(self, zone):
+    def _zone_has_soa(self, zone):
+        '''Judge if the zone has an SOA record.'''
+        # In some sense, the SOA defines a zone.
+        # If the current name server has authority for the
+        # specific zone, we need to judge if the zone has an SOA record;
+        # if not, we consider the zone has incomplete data, so xfrout can't
+        # serve for it.
         if sqlite3_ds.get_zone_soa(zone, self.server.get_db_file()):
         if sqlite3_ds.get_zone_soa(zone, self.server.get_db_file()):
-            return False
-
-        return True
-
-    def _zone_exist(self, zonename):
-        # 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 True
+
         return False
         return False
 
 
+    def _zone_exist(self, zonename):
+        '''Judge if the zone is configured by config manager.'''
+        # Currently, if we find the zone in datasource successfully, we
+        # consider the zone is configured, and the current name server has
+        # authority for the specific zone.
+        # TODO: should get zone's configuration from cfgmgr or other place
+        # in future.
+        return sqlite3_ds.zone_exist(zonename, self.server.get_db_file())
 
 
     def _check_xfrout_available(self, zone_name):
     def _check_xfrout_available(self, zone_name):
         '''Check if xfr request can be responsed.
         '''Check if xfr request can be responsed.
            TODO, Get zone's configuration from cfgmgr or some other place
            TODO, Get zone's configuration from cfgmgr or some other place
            eg. check allow_transfer setting,
            eg. check allow_transfer setting,
         '''
         '''
+        # If the current name server does not have authority for the
+        # zone, xfrout can't serve for it, return rcode NOTAUTH.
         if not self._zone_exist(zone_name):
         if not self._zone_exist(zone_name):
             return Rcode.NOTAUTH()
             return Rcode.NOTAUTH()
 
 
-        if self._zone_is_empty(zone_name):
+        # If we are an authoritative name server for the zone, but fail
+        # to find the zone's SOA record in datasource, xfrout can't
+        # provide zone transfer for it.
+        if not self._zone_has_soa(zone_name):
             return Rcode.SERVFAIL()
             return Rcode.SERVFAIL()
 
 
         #TODO, check allow_transfer
         #TODO, check allow_transfer

+ 1 - 1
src/bin/xfrout/xfrout.spec.pre.in

@@ -48,4 +48,4 @@
       ]
       ]
   }
   }
 }
 }
-     
+

+ 1 - 1
src/lib/asiolink/README

@@ -17,7 +17,7 @@ including:
     leaving it in place elsewhere.
     leaving it in place elsewhere.
 
 
 Currently, the asiolink library only supports DNS servers (i.e., b10-auth
 Currently, the asiolink library only supports DNS servers (i.e., b10-auth
-and b10-recurse).  The plan is to make it more generic and allow it to
+and b10-resolver).  The plan is to make it more generic and allow it to
 support other modules as well.
 support other modules as well.
 
 
 Some of the classes defined here--for example, IOSocket, IOEndpoint,
 Some of the classes defined here--for example, IOSocket, IOEndpoint,

+ 21 - 2
src/lib/asiolink/asiolink.cc

@@ -260,7 +260,7 @@ convertAddr(const string& address) {
         isc_throw(IOError, "Invalid IP address '" << &address << "': "
         isc_throw(IOError, "Invalid IP address '" << &address << "': "
             << err.message());
             << err.message());
     }
     }
-    return addr;
+    return (addr);
 }
 }
 
 
 }
 }
@@ -386,6 +386,11 @@ public:
     void setupTimer(const IntervalTimer::Callback& cbfunc,
     void setupTimer(const IntervalTimer::Callback& cbfunc,
                     const uint32_t interval);
                     const uint32_t interval);
     void callback(const asio::error_code& error);
     void callback(const asio::error_code& error);
+    void cancel() {
+        timer_.cancel();
+        interval_ = 0;
+    }
+    uint32_t getInterval() const { return (interval_); }
 private:
 private:
     // a function to update timer_ when it expires
     // a function to update timer_ when it expires
     void updateTimer();
     void updateTimer();
@@ -398,7 +403,7 @@ private:
 };
 };
 
 
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
-    timer_(io_service.get_io_service())
+    interval_(0), timer_(io_service.get_io_service())
 {}
 {}
 
 
 IntervalTimerImpl::~IntervalTimerImpl()
 IntervalTimerImpl::~IntervalTimerImpl()
@@ -427,6 +432,10 @@ IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
 
 
 void
 void
 IntervalTimerImpl::updateTimer() {
 IntervalTimerImpl::updateTimer() {
+    if (interval_ == 0) {
+        // timer has been canceled.  Do nothing.
+        return;
+    }
     try {
     try {
         // Update expire time to (current time + interval_).
         // Update expire time to (current time + interval_).
         timer_.expires_from_now(boost::posix_time::seconds(interval_));
         timer_.expires_from_now(boost::posix_time::seconds(interval_));
@@ -461,4 +470,14 @@ IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
     return (impl_->setupTimer(cbfunc, interval));
     return (impl_->setupTimer(cbfunc, interval));
 }
 }
 
 
+void
+IntervalTimer::cancel() {
+    impl_->cancel();
+}
+
+uint32_t
+IntervalTimer::getInterval() const {
+    return (impl_->getInterval());
+}
+
 }
 }

+ 24 - 1
src/lib/asiolink/asiolink.h

@@ -77,7 +77,7 @@ class io_service;
 ///
 ///
 /// Notes to developers:
 /// Notes to developers:
 /// Currently the wrapper interface is fairly specific to use by a
 /// Currently the wrapper interface is fairly specific to use by a
-/// DNS server, i.e., b10-auth or b10-recurse.  But the plan is to
+/// DNS server, i.e., b10-auth or b10-resolver.  But the plan is to
 /// generalize it and have other modules use it as well.
 /// generalize it and have other modules use it as well.
 ///
 ///
 /// One obvious drawback of this approach is performance overhead
 /// One obvious drawback of this approach is performance overhead
@@ -657,6 +657,29 @@ public:
     /// \throw isc::Unexpected ASIO library error
     /// \throw isc::Unexpected ASIO library error
     ///
     ///
     void setupTimer(const Callback& cbfunc, const uint32_t interval);
     void setupTimer(const Callback& cbfunc, const uint32_t interval);
+
+    /// Cancel the timer.
+    ///
+    /// If the timer has been set up, this method cancels any asynchronous
+    /// events waiting on the timer and stops the timer itself.
+    /// If the timer has already been canceled, this method effectively does
+    /// nothing.
+    ///
+    /// This method never throws an exception.
+    void cancel();
+
+    /// Return the timer interval.
+    ///
+    /// This method returns the timer interval in seconds if it's running;
+    /// if the timer has been canceled it returns 0.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// Note: We may want to change the granularity of the timer to
+    /// milliseconds or even finer.  If and when this happens the semantics
+    /// of the return value of this method will be changed accordingly.
+    uint32_t getInterval() const;
+
 private:
 private:
     IntervalTimerImpl* impl_;
     IntervalTimerImpl* impl_;
 };
 };

+ 32 - 1
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -751,7 +751,7 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
 // or not.
 // or not.
 class IntervalTimerTest : public ::testing::Test {
 class IntervalTimerTest : public ::testing::Test {
 protected:
 protected:
-    IntervalTimerTest() : io_service_() {};
+    IntervalTimerTest() : io_service_() {}
     ~IntervalTimerTest() {}
     ~IntervalTimerTest() {}
     class TimerCallBack : public std::unary_function<void, void> {
     class TimerCallBack : public std::unary_function<void, void> {
     public:
     public:
@@ -811,6 +811,19 @@ protected:
         int count_;
         int count_;
         int prev_counter_;
         int prev_counter_;
     };
     };
+    class TimerCallBackCanceller {
+    public:
+        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+            counter_(counter), itimer_(itimer)
+        {}
+        void operator()() {
+            ++counter_;
+            itimer_.cancel();
+        }
+    private:
+        unsigned int& counter_;
+        IntervalTimer& itimer_;
+    };
     class TimerCallBackOverwriter : public std::unary_function<void, void> {
     class TimerCallBackOverwriter : public std::unary_function<void, void> {
     public:
     public:
         TimerCallBackOverwriter(IntervalTimerTest* test_obj,
         TimerCallBackOverwriter(IntervalTimerTest* test_obj,
@@ -864,6 +877,7 @@ TEST_F(IntervalTimerTest, startIntervalTimer) {
     start = boost::posix_time::microsec_clock::universal_time();
     start = boost::posix_time::microsec_clock::universal_time();
     // setup timer
     // setup timer
     itimer.setupTimer(TimerCallBack(this), 1);
     itimer.setupTimer(TimerCallBack(this), 1);
+    EXPECT_EQ(1, itimer.getInterval());
     io_service_.run();
     io_service_.run();
     // reaches here after timer expired
     // reaches here after timer expired
     // delta: difference between elapsed time and 1 second
     // delta: difference between elapsed time and 1 second
@@ -926,6 +940,23 @@ TEST_F(IntervalTimerTest, destructIntervalTimer) {
     EXPECT_TRUE(timer_cancel_success_);
     EXPECT_TRUE(timer_cancel_success_);
 }
 }
 
 
+TEST_F(IntervalTimerTest, cancel) {
+    // Similar to destructIntervalTimer test, but the first timer explicitly
+    // cancels itself on first callback.
+    IntervalTimer itimer_counter(io_service_);
+    IntervalTimer itimer_watcher(io_service_);
+    unsigned int counter = 0;
+    itimer_counter.setupTimer(TimerCallBackCanceller(counter, itimer_counter),
+                              1);
+    itimer_watcher.setupTimer(TimerCallBack(this), 3);
+    io_service_.run();
+    EXPECT_EQ(1, counter);
+    EXPECT_EQ(0, itimer_counter.getInterval());
+
+    // canceling an already canceled timer shouldn't cause any surprise.
+    EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
 TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
 TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     // Note: This test currently takes 4 seconds. The timer should have
     // Note: This test currently takes 4 seconds. The timer should have
     // finer granularity and timer periods in this test should be shorter
     // finer granularity and timer periods in this test should be shorter

+ 1 - 1
src/lib/asiolink/udpdns.cc

@@ -69,7 +69,7 @@ UDPServer::UDPServer(io_service& io_service,
     if (addr.is_v6()) {
     if (addr.is_v6()) {
         socket_->set_option(asio::ip::v6_only(true));
         socket_->set_option(asio::ip::v6_only(true));
     }
     }
-    socket_->bind(udp::endpoint(proto, port));
+    socket_->bind(udp::endpoint(addr, port));
 }
 }
 
 
 /// The function operator is implemented with the "stackless coroutine"
 /// The function operator is implemented with the "stackless coroutine"

+ 49 - 20
src/lib/config/ccsession.cc

@@ -135,8 +135,6 @@ createCommand(const std::string& command, ConstElementPtr arg) {
     return (cmd);
     return (cmd);
 }
 }
 
 
-/// Returns "" and empty ElementPtr() if this does not
-/// look like a command
 std::string
 std::string
 parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
 parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
     if (command &&
     if (command &&
@@ -149,7 +147,7 @@ parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
             if (cmd->size() > 1) {
             if (cmd->size() > 1) {
                 arg = cmd->get(1);
                 arg = cmd->get(1);
             } else {
             } else {
-                arg = ElementPtr();
+                arg = Element::createMap();
             }
             }
             return (cmd->get(0)->stringValue());
             return (cmd->get(0)->stringValue());
         } else {
         } else {
@@ -257,7 +255,7 @@ ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
     ElementPtr errors = Element::createList();
     ElementPtr errors = Element::createList();
     if (!config_handler_) {
     if (!config_handler_) {
         answer = createAnswer(1, module_name_ + " does not have a config handler");
         answer = createAnswer(1, module_name_ + " does not have a config handler");
-    } else if (!module_specification_.validate_config(new_config, false,
+    } else if (!module_specification_.validateConfig(new_config, false,
                                                       errors)) {
                                                       errors)) {
         std::stringstream ss;
         std::stringstream ss;
         ss << "Error in config validation: ";
         ss << "Error in config validation: ";
@@ -286,6 +284,51 @@ ModuleCCSession::hasQueuedMsgs() const {
     return (session_.hasQueuedMsgs());
     return (session_.hasQueuedMsgs());
 }
 }
 
 
+ConstElementPtr
+ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
+                                          ConstElementPtr arg)
+{
+    if (target_module == module_name_) {
+        return (handleConfigUpdate(arg));
+    } else {
+        // ok this update is not for us, if we have this module
+        // in our remote config list, update that
+        updateRemoteConfig(target_module, arg);
+        // we're not supposed to answer to this, so return
+        return (ElementPtr());
+    }
+}
+
+ConstElementPtr
+ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
+                                    const std::string& target_module,
+                                    ConstElementPtr arg) const
+{
+    if (target_module == module_name_) {
+        if (command_handler_) {
+            ElementPtr errors = Element::createList();
+            if (module_specification_.validateCommand(cmd_str,
+                                                       arg,
+                                                       errors)) {
+                return (command_handler_(cmd_str, arg));
+            } else {
+                std::stringstream ss;
+                ss << "Error in command validation: ";
+                BOOST_FOREACH(ConstElementPtr error,
+                              errors->listValue()) {
+                    ss << error->stringValue();
+                }
+                return (createAnswer(3, ss.str()));
+            }
+        } else {
+            return (createAnswer(1,
+                                 "Command given but no "
+                                 "command handler for module"));
+        }
+    }
+    return (ElementPtr());
+}
+
 int
 int
 ModuleCCSession::checkCommand() {
 ModuleCCSession::checkCommand() {
     ConstElementPtr cmd, routing, data;
     ConstElementPtr cmd, routing, data;
@@ -302,23 +345,9 @@ ModuleCCSession::checkCommand() {
             std::string cmd_str = parseCommand(arg, data);
             std::string cmd_str = parseCommand(arg, data);
             std::string target_module = routing->get("group")->stringValue();
             std::string target_module = routing->get("group")->stringValue();
             if (cmd_str == "config_update") {
             if (cmd_str == "config_update") {
-                if (target_module == module_name_) {
-                    answer = handleConfigUpdate(arg);
-                } else {
-                    // ok this update is not for us, if we have this module
-                    // in our remote config list, update that
-                    updateRemoteConfig(target_module, arg);
-                    // we're not supposed to answer to this, so return
-                    return (0);
-                }
+                answer = checkConfigUpdateCommand(target_module, arg);
             } else {
             } else {
-                if (target_module == module_name_) {
-                    if (command_handler_) {
-                        answer = command_handler_(cmd_str, arg);
-                    } else {
-                        answer = createAnswer(1, "Command given but no command handler for module");
-                    }
-                }
+                answer = checkModuleCommand(cmd_str, target_module, arg);
             }
             }
         } catch (const CCSessionError& re) {
         } catch (const CCSessionError& re) {
             // TODO: Once we have logging and timeouts, we should not
             // TODO: Once we have logging and timeouts, we should not

+ 36 - 2
src/lib/config/ccsession.h

@@ -91,11 +91,36 @@ isc::data::ConstElementPtr createCommand(const std::string& command,
 /// \brief Parses the given command into a string containing the actual
 /// \brief Parses the given command into a string containing the actual
 ///        command and an ElementPtr containing the optional argument.
 ///        command and an ElementPtr containing the optional argument.
 ///
 ///
+/// Raises a CCSessionError if this is not a well-formed command
+///
+/// Example code: (command_message is a ConstElementPtr that is
+/// passed here)
+/// \code
+/// ElementPtr command_message = Element::fromJSON("{ \"command\": [\"foo\", { \"bar\": 123 } ] }");
+/// try {
+///     ConstElementPtr args;
+///     std::string command_str = parseCommand(args, command_message);
+///     if (command_str == "foo") {
+///         std::cout << "The command 'foo' was given" << std::endl;
+///         if (args->contains("bar")) {
+///             std::cout << "It had argument name 'bar' set, which has"
+///                       << "value " 
+///                       << args->get("bar")->intValue();
+///         }
+///     } else {
+///         std::cout << "Unknown command '" << command_str << std::endl;
+///     }
+/// } catch (CCSessionError cse) {
+///     std::cerr << "Bad command in CC Session: "
+///     << cse.what() << std::endl;
+/// }
+/// \endcode
+/// 
 /// \param arg This value will be set to the ElementPtr pointing to
 /// \param arg This value will be set to the ElementPtr pointing to
-///        the argument, or to an empty ElementPtr if there was none.
+///        the argument, or to an empty Map (ElementPtr) if there was none.
 /// \param command The command message containing the command (as made
 /// \param command The command message containing the command (as made
 ///        by createCommand()
 ///        by createCommand()
-/// \return The command string
+/// \return The command name
 std::string parseCommand(isc::data::ConstElementPtr& arg,
 std::string parseCommand(isc::data::ConstElementPtr& arg,
                          isc::data::ConstElementPtr command);
                          isc::data::ConstElementPtr command);
 
 
@@ -244,6 +269,15 @@ private:
     isc::data::ConstElementPtr handleConfigUpdate(
     isc::data::ConstElementPtr handleConfigUpdate(
         isc::data::ConstElementPtr new_config);
         isc::data::ConstElementPtr new_config);
 
 
+    isc::data::ConstElementPtr checkConfigUpdateCommand(
+        const std::string& target_module,
+        isc::data::ConstElementPtr arg);
+
+    isc::data::ConstElementPtr checkModuleCommand(
+        const std::string& cmd_str,
+        const std::string& target_module,
+        isc::data::ConstElementPtr arg) const;
+
     isc::data::ConstElementPtr(*config_handler_)(
     isc::data::ConstElementPtr(*config_handler_)(
         isc::data::ConstElementPtr new_config);
         isc::data::ConstElementPtr new_config);
     isc::data::ConstElementPtr(*command_handler_)(
     isc::data::ConstElementPtr(*command_handler_)(

+ 41 - 11
src/lib/config/module_spec.cc

@@ -177,17 +177,47 @@ ModuleSpec::getModuleDescription() const {
 }
 }
 
 
 bool
 bool
-ModuleSpec::validate_config(ConstElementPtr data, const bool full) const {
+ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
     ConstElementPtr spec = module_specification->find("config_data");
     ConstElementPtr spec = module_specification->find("config_data");
-    return (validate_spec_list(spec, data, full, ElementPtr()));
+    return (validateSpecList(spec, data, full, ElementPtr()));
 }
 }
 
 
 bool
 bool
-ModuleSpec::validate_config(ConstElementPtr data, const bool full,
+ModuleSpec::validateCommand(const std::string& command,
+                             ConstElementPtr args,
+                             ElementPtr errors) const
+{
+    if (args->getType() != Element::map) {
+        errors->add(Element::create("args for command " +
+                                    command + " is not a map"));
+        return (false);
+    }
+
+    ConstElementPtr commands_spec = module_specification->find("commands");
+    if (!commands_spec) {
+        // there are no commands according to the spec.
+        errors->add(Element::create("The given module has no commands"));
+        return (false);
+    }
+
+    BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
+        if (cur_command->get("command_name")->stringValue() == command) {
+            return (validateSpecList(cur_command->get("command_args"),
+                                       args, true, errors));
+        }
+    }
+
+    // this command is unknown
+    errors->add(Element::create("Unknown command " + command));
+    return (false);
+}
+
+bool
+ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
                             ElementPtr errors) const
                             ElementPtr errors) const
 {
 {
     ConstElementPtr spec = module_specification->find("config_data");
     ConstElementPtr spec = module_specification->find("config_data");
-    return (validate_spec_list(spec, data, full, errors));
+    return (validateSpecList(spec, data, full, errors));
 }
 }
 
 
 ModuleSpec
 ModuleSpec
@@ -264,7 +294,7 @@ check_type(ConstElementPtr spec, ConstElementPtr element) {
 }
 }
 
 
 bool
 bool
-ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
                           const bool full, ElementPtr errors) const
                           const bool full, ElementPtr errors) const
 {
 {
     if (!check_type(spec, data)) {
     if (!check_type(spec, data)) {
@@ -286,14 +316,14 @@ ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
                 return (false);
                 return (false);
             }
             }
             if (list_spec->get("item_type")->stringValue() == "map") {
             if (list_spec->get("item_type")->stringValue() == "map") {
-                if (!validate_item(list_spec, list_el, full, errors)) {
+                if (!validateItem(list_spec, list_el, full, errors)) {
                     return (false);
                     return (false);
                 }
                 }
             }
             }
         }
         }
     }
     }
     if (data->getType() == Element::map) {
     if (data->getType() == Element::map) {
-        if (!validate_spec_list(spec->get("map_item_spec"), data, full, errors)) {
+        if (!validateSpecList(spec->get("map_item_spec"), data, full, errors)) {
             return (false);
             return (false);
         }
         }
     }
     }
@@ -302,7 +332,7 @@ ModuleSpec::validate_item(ConstElementPtr spec, ConstElementPtr data,
 
 
 // spec is a map with item_name etc, data is a map
 // spec is a map with item_name etc, data is a map
 bool
 bool
-ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
                           const bool full, ElementPtr errors) const
                           const bool full, ElementPtr errors) const
 {
 {
     std::string item_name = spec->get("item_name")->stringValue();
     std::string item_name = spec->get("item_name")->stringValue();
@@ -311,7 +341,7 @@ ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
     data_el = data->get(item_name);
     data_el = data->get(item_name);
     
     
     if (data_el) {
     if (data_el) {
-        if (!validate_item(spec, data_el, full, errors)) {
+        if (!validateItem(spec, data_el, full, errors)) {
             return (false);
             return (false);
         }
         }
     } else {
     } else {
@@ -327,13 +357,13 @@ ModuleSpec::validate_spec(ConstElementPtr spec, ConstElementPtr data,
 
 
 // spec is a list of maps, data is a map
 // spec is a list of maps, data is a map
 bool
 bool
-ModuleSpec::validate_spec_list(ConstElementPtr spec, ConstElementPtr data,
+ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
                                const bool full, ElementPtr errors) const
                                const bool full, ElementPtr errors) const
 {
 {
     bool validated = true;
     bool validated = true;
     std::string cur_item_name;
     std::string cur_item_name;
     BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
     BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
-        if (!validate_spec(cur_spec_el, data, full, errors)) {
+        if (!validateSpec(cur_spec_el, data, full, errors)) {
             validated = false;
             validated = false;
         }
         }
     }
     }

+ 51 - 8
src/lib/config/module_spec.h

@@ -88,23 +88,66 @@ namespace isc { namespace config {
         /// \param data The base \c Element of the data to check
         /// \param data The base \c Element of the data to check
         /// \return true if the data conforms to the specification,
         /// \return true if the data conforms to the specification,
         /// false otherwise.
         /// false otherwise.
-        bool validate_config(isc::data::ConstElementPtr data,
+        bool validateConfig(isc::data::ConstElementPtr data,
                              const bool full = false) const;
                              const bool full = false) const;
 
 
+        /// Validates the arguments for the given command
+        ///
+        /// This checks the command and argument against the
+        /// specification in the module's .spec file.
+        ///
+        /// A command is considered valid if:
+        /// - it is known (the 'command' string must have an entry in
+        ///                the specification)
+        /// - the args is a MapElement
+        /// - args contains all mandatory arguments
+        /// - args does not contain unknown arguments
+        /// - all arguments in args match their specification
+        /// If all of these are true, this function returns \c true
+        /// If not, this method returns \c false
+        ///
+        /// Example usage:
+        /// \code
+        ///     ElementPtr errors = Element::createList();
+        ///     if (module_specification_.validateCommand(cmd_str,
+        ///                                               arg,
+        ///                                               errors)) {
+        ///         std::cout << "Command is valid" << std::endl;
+        ///     } else {
+        ///         std::cout << "Command is invalid: " << std::endl;
+        ///         BOOST_FOREACH(ConstElementPtr error,
+        ///                       errors->listValue()) {
+        ///             std::cout << error->stringValue() << std::endl;
+        ///         }
+        ///     }
+        /// \endcode
+        ///
+        /// \param command The command to validate the arguments for
+        /// \param args A dict containing the command parameters
+        /// \param errors An ElementPtr pointing to a ListElement. Any
+        ///               errors that are found are added as
+        ///               StringElements to this list
+        /// \return true if the command is known and the parameters are correct
+        ///         false otherwise
+        bool validateCommand(const std::string& command,
+                              isc::data::ConstElementPtr args,
+                              isc::data::ElementPtr errors) const;
+
+
         /// errors must be of type ListElement
         /// errors must be of type ListElement
-        bool validate_config(isc::data::ConstElementPtr data, const bool full,
+        bool validateConfig(isc::data::ConstElementPtr data, const bool full,
                              isc::data::ElementPtr errors) const;
                              isc::data::ElementPtr errors) const;
 
 
     private:
     private:
-        bool validate_item(isc::data::ConstElementPtr spec,
-                           isc::data::ConstElementPtr data,
-                           const bool full,
-                           isc::data::ElementPtr errors) const;
-        bool validate_spec(isc::data::ConstElementPtr spec,
+        bool validateItem(isc::data::ConstElementPtr spec,
+                          isc::data::ConstElementPtr data,
+                          const bool full,
+                          isc::data::ElementPtr errors) const;
+        bool validateSpec(isc::data::ConstElementPtr spec,
                            isc::data::ConstElementPtr data,
                            isc::data::ConstElementPtr data,
                            const bool full,
                            const bool full,
                            isc::data::ElementPtr errors) const;
                            isc::data::ElementPtr errors) const;
-        bool validate_spec_list(isc::data::ConstElementPtr spec,
+        bool validateSpecList(isc::data::ConstElementPtr spec,
                                 isc::data::ConstElementPtr data,
                                 isc::data::ConstElementPtr data,
                                 const bool full,
                                 const bool full,
                                 isc::data::ElementPtr errors) const;
                                 isc::data::ElementPtr errors) const;

+ 41 - 25
src/lib/config/tests/ccsession_unittests.cc

@@ -137,7 +137,7 @@ TEST_F(CCSessionTest, parseCommand) {
 
 
     cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\" ] }"));
     cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\" ] }"));
     EXPECT_EQ("my_command", cmd);
     EXPECT_EQ("my_command", cmd);
-    EXPECT_TRUE(isNull(arg));
+    EXPECT_EQ(*arg, *Element::createMap());
 
 
     cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\", 1 ] }"));
     cmd = parseCommand(arg, el("{ \"command\": [ \"my_command\", 1 ] }"));
     EXPECT_EQ("my_command", cmd);
     EXPECT_EQ("my_command", cmd);
@@ -193,8 +193,8 @@ ConstElementPtr my_command_handler(const std::string& command,
     if (command == "good_command") {
     if (command == "good_command") {
         return (createAnswer());
         return (createAnswer());
     } else if (command == "command_with_arg") {
     } else if (command == "command_with_arg") {
-        if (arg) {
-            if (arg->getType() == Element::integer) {
+        if (arg->contains("number")) {
+            if (arg->get("number")->getType() == Element::integer) {
                 return (createAnswer(0, el("2")));
                 return (createAnswer(0, el("2")));
             } else {
             } else {
                 return (createAnswer(1, "arg bad type"));
                 return (createAnswer(1, "arg bad type"));
@@ -235,10 +235,10 @@ TEST_F(CCSessionTest, checkCommand) {
     // client will ask for config
     // client will ask for config
     session.getMessages()->add(createAnswer(0, el("{}")));
     session.getMessages()->add(createAnswer(0, el("{}")));
 
 
-    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
-    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, my_config_handler,
+    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
                          my_command_handler);
                          my_command_handler);
-    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
 
 
     EXPECT_EQ(2, session.getMsgQueue()->size());
     EXPECT_EQ(2, session.getMsgQueue()->size());
     ConstElementPtr msg;
     ConstElementPtr msg;
@@ -252,19 +252,19 @@ TEST_F(CCSessionTest, checkCommand) {
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
     // not a command, should be ignored
     // not a command, should be ignored
-    session.addMessage(el("1"), "Spec2", "*");
+    session.addMessage(el("1"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
-
-    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec2",
+    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29",
                        "*");
                        "*");
+
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
-    session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
@@ -272,53 +272,69 @@ TEST_F(CCSessionTest, checkCommand) {
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
     session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
     session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
-                       "Spec2", "*");
+                       "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 1, \"bad command\" ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 1, \"bad command\" ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
-    session.addMessage(el("{ \"command\": [ \"command_with_arg\", 1 ] }"),
-                       "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"command_with_arg\", {\"number\": 1} ] }"),
+                       "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 0, 2 ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 0, 2 ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
-    session.addMessage(el("{ \"command\": [ \"command_with_arg\" ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"command_with_arg\" ] }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 1, \"arg missing\" ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 1, \"arg missing\" ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
-    session.addMessage(el("{ \"command\": [ \"command_with_arg\", \"asdf\" ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"command_with_arg\", \"asdf\" ] }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
-    EXPECT_EQ("{ \"result\": [ 1, \"arg bad type\" ] }", msg->str());
+    EXPECT_EQ("{ \"result\": [ 3, \"Error in command validation: args for command command_with_arg is not a map\" ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
 
 
     mccs.setCommandHandler(NULL);
     mccs.setCommandHandler(NULL);
-    session.addMessage(el("{ \"command\": [ \"whatever\" ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"whatever\" ] }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 1, \"Command given but no command handler for module\" ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 1, \"Command given but no command handler for module\" ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
+}
+
+// A heuristic workaround for clang++: It doesn't seem to compile the whole
+// test, probably due to its length.  Dividing the tests into two separate
+// test cases seems to work.
+TEST_F(CCSessionTest, checkCommand2) {
+    session.getMessages()->add(createAnswer(0, el("{}")));
+    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler,
+                         my_command_handler);
+    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
+    ConstElementPtr msg;
+    std::string group, to;
+    // checked above, drop em
+    msg = session.getFirstMessage(group, to);
+    msg = session.getFirstMessage(group, to);
 
 
     EXPECT_EQ(1, mccs.getValue("item1")->intValue());
     EXPECT_EQ(1, mccs.getValue("item1")->intValue());
-    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 2 } ] }"), "Spec2", "*");
-    result = mccs.checkCommand();
+    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 2 } ] }"), "Spec29", "*");
+    int result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
     EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
     EXPECT_EQ("{ \"result\": [ 0 ] }", msg->str());
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
     EXPECT_EQ(2, mccs.getValue("item1")->intValue());
     EXPECT_EQ(2, mccs.getValue("item1")->intValue());
 
 
-    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": \"asdf\" } ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": \"asdf\" } ] }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
@@ -326,7 +342,7 @@ TEST_F(CCSessionTest, checkCommand) {
     EXPECT_EQ(0, result);
     EXPECT_EQ(0, result);
     EXPECT_EQ(2, mccs.getValue("item1")->intValue());
     EXPECT_EQ(2, mccs.getValue("item1")->intValue());
 
 
-    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 5 } ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"config_update\", { \"item1\": 5 } ] }"), "Spec29", "*");
     result = mccs.checkCommand();
     result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
@@ -387,9 +403,9 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     // client will ask for config
     // client will ask for config
     session.getMessages()->add(createAnswer(0, el("{  }")));
     session.getMessages()->add(createAnswer(0, el("{  }")));
 
 
-    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
-    ModuleCCSession mccs(ccspecfile("spec2.spec"), session, my_config_handler, my_command_handler);
-    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+    ModuleCCSession mccs(ccspecfile("spec29.spec"), session, my_config_handler, my_command_handler);
+    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
 
 
     EXPECT_EQ(2, session.getMsgQueue()->size());
     EXPECT_EQ(2, session.getMsgQueue()->size());
     ConstElementPtr msg;
     ConstElementPtr msg;
@@ -404,7 +420,7 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);
 
 
     // Check if commands for the module are handled
     // Check if commands for the module are handled
-    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec2", "*");
+    session.addMessage(el("{ \"command\": [ \"good_command\" ] }"), "Spec29", "*");
     int result = mccs.checkCommand();
     int result = mccs.checkCommand();
     EXPECT_EQ(1, session.getMsgQueue()->size());
     EXPECT_EQ(1, session.getMsgQueue()->size());
     msg = session.getFirstMessage(group, to);
     msg = session.getFirstMessage(group, to);

+ 73 - 40
src/lib/config/tests/module_spec_unittests.cc

@@ -30,7 +30,7 @@ std::string specfile(const std::string name) {
 }
 }
 
 
 void
 void
-module_spec_error(const std::string& file,
+moduleSpecError(const std::string& file,
                const std::string& error1,
                const std::string& error1,
                const std::string& error2 = "",
                const std::string& error2 = "",
                const std::string& error3 = "")
                const std::string& error3 = "")
@@ -52,7 +52,7 @@ TEST(ModuleSpec, ReadingSpecfiles) {
                               ->stringValue(), "Spec1");
                               ->stringValue(), "Spec1");
     dd = moduleSpecFromFile(specfile("spec2.spec"));
     dd = moduleSpecFromFile(specfile("spec2.spec"));
     EXPECT_EQ(dd.getFullSpec()->get("config_data")->size(), 6);
     EXPECT_EQ(dd.getFullSpec()->get("config_data")->size(), 6);
-    module_spec_error("doesnotexist",
+    moduleSpecError("doesnotexist",
                    "Error opening ",
                    "Error opening ",
                    specfile("doesnotexist"),
                    specfile("doesnotexist"),
                    ": No such file or directory");
                    ": No such file or directory");
@@ -81,69 +81,65 @@ TEST(ModuleSpec, ReadingSpecfiles) {
 }
 }
 
 
 TEST(ModuleSpec, SpecfileItems) {
 TEST(ModuleSpec, SpecfileItems) {
-    module_spec_error("spec3.spec",
+    moduleSpecError("spec3.spec",
                    "item_name missing in { \"item_default\": 1, \"item_optional\": false, \"item_type\": \"integer\" }");
                    "item_name missing in { \"item_default\": 1, \"item_optional\": false, \"item_type\": \"integer\" }");
-    module_spec_error("spec4.spec",
+    moduleSpecError("spec4.spec",
                    "item_type missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": false }");
                    "item_type missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_optional\": false }");
-    module_spec_error("spec5.spec",
+    moduleSpecError("spec5.spec",
                    "item_optional missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_type\": \"integer\" }");
                    "item_optional missing in { \"item_default\": 1, \"item_name\": \"item1\", \"item_type\": \"integer\" }");
-    module_spec_error("spec6.spec",
+    moduleSpecError("spec6.spec",
                    "item_default missing in { \"item_name\": \"item1\", \"item_optional\": false, \"item_type\": \"integer\" }");
                    "item_default missing in { \"item_name\": \"item1\", \"item_optional\": false, \"item_type\": \"integer\" }");
-    module_spec_error("spec9.spec",
+    moduleSpecError("spec9.spec",
                    "item_default not of type integer");
                    "item_default not of type integer");
-    module_spec_error("spec10.spec",
+    moduleSpecError("spec10.spec",
                    "item_default not of type real");
                    "item_default not of type real");
-    module_spec_error("spec11.spec",
+    moduleSpecError("spec11.spec",
                    "item_default not of type boolean");
                    "item_default not of type boolean");
-    module_spec_error("spec12.spec",
+    moduleSpecError("spec12.spec",
                    "item_default not of type string");
                    "item_default not of type string");
-    module_spec_error("spec13.spec",
+    moduleSpecError("spec13.spec",
                    "item_default not of type list");
                    "item_default not of type list");
-    module_spec_error("spec14.spec",
+    moduleSpecError("spec14.spec",
                    "item_default not of type map");
                    "item_default not of type map");
-    module_spec_error("spec15.spec",
+    moduleSpecError("spec15.spec",
                    "badname is not a valid type name");
                    "badname is not a valid type name");
 }
 }
 
 
 TEST(ModuleSpec, SpecfileConfigData) {
 TEST(ModuleSpec, SpecfileConfigData) {
-    module_spec_error("spec7.spec",
+    moduleSpecError("spec7.spec",
                    "module_name missing in {  }");
                    "module_name missing in {  }");
-    module_spec_error("spec8.spec",
+    moduleSpecError("spec8.spec",
                    "No module_spec in specification");
                    "No module_spec in specification");
-    module_spec_error("spec16.spec",
+    moduleSpecError("spec16.spec",
                    "config_data is not a list of elements");
                    "config_data is not a list of elements");
-    module_spec_error("spec21.spec",
+    moduleSpecError("spec21.spec",
                    "commands is not a list of elements");
                    "commands is not a list of elements");
 }
 }
 
 
 TEST(ModuleSpec, SpecfileCommands) {
 TEST(ModuleSpec, SpecfileCommands) {
-    module_spec_error("spec17.spec",
+    moduleSpecError("spec17.spec",
                    "command_name missing in { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\" }");
                    "command_name missing in { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\" }");
-    module_spec_error("spec18.spec",
+    moduleSpecError("spec18.spec",
                    "command_args missing in { \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }");
                    "command_args missing in { \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }");
-    module_spec_error("spec19.spec",
+    moduleSpecError("spec19.spec",
                    "command_args not of type list");
                    "command_args not of type list");
-    module_spec_error("spec20.spec",
+    moduleSpecError("spec20.spec",
                    "somethingbad is not a valid type name");
                    "somethingbad is not a valid type name");
-/*
-    module_spec_error("spec22.spec",
-                   "");
-*/
 }
 }
 
 
 bool
 bool
-data_test(const ModuleSpec& dd, const std::string& data_file_name) {
+dataTest(const ModuleSpec& dd, const std::string& data_file_name) {
     std::ifstream data_file;
     std::ifstream data_file;
 
 
     data_file.open(specfile(data_file_name).c_str());
     data_file.open(specfile(data_file_name).c_str());
     ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
     ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
     data_file.close();
     data_file.close();
 
 
-    return (dd.validate_config(data));
+    return (dd.validateConfig(data));
 }
 }
 
 
 bool
 bool
-data_test_with_errors(const ModuleSpec& dd, const std::string& data_file_name,
+dataTestWithErrors(const ModuleSpec& dd, const std::string& data_file_name,
                       ElementPtr errors)
                       ElementPtr errors)
 {
 {
     std::ifstream data_file;
     std::ifstream data_file;
@@ -152,27 +148,64 @@ data_test_with_errors(const ModuleSpec& dd, const std::string& data_file_name,
     ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
     ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
     data_file.close();
     data_file.close();
 
 
-    return (dd.validate_config(data, true, errors));
+    return (dd.validateConfig(data, true, errors));
 }
 }
 
 
 TEST(ModuleSpec, DataValidation) {
 TEST(ModuleSpec, DataValidation) {
     ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec"));
     ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec"));
 
 
-    EXPECT_TRUE(data_test(dd, "data22_1.data"));
-    EXPECT_FALSE(data_test(dd, "data22_2.data"));
-    EXPECT_FALSE(data_test(dd, "data22_3.data"));
-    EXPECT_FALSE(data_test(dd, "data22_4.data"));
-    EXPECT_FALSE(data_test(dd, "data22_5.data"));
-    EXPECT_TRUE(data_test(dd, "data22_6.data"));
-    EXPECT_TRUE(data_test(dd, "data22_7.data"));
-    EXPECT_FALSE(data_test(dd, "data22_8.data"));
-    EXPECT_FALSE(data_test(dd, "data22_9.data"));
+    EXPECT_TRUE(dataTest(dd, "data22_1.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_2.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_3.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_4.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_5.data"));
+    EXPECT_TRUE(dataTest(dd, "data22_6.data"));
+    EXPECT_TRUE(dataTest(dd, "data22_7.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_8.data"));
+    EXPECT_FALSE(dataTest(dd, "data22_9.data"));
 
 
     ElementPtr errors = Element::createList();
     ElementPtr errors = Element::createList();
-    EXPECT_FALSE(data_test_with_errors(dd, "data22_8.data", errors));
+    EXPECT_FALSE(dataTestWithErrors(dd, "data22_8.data", errors));
     EXPECT_EQ("[ \"Type mismatch\" ]", errors->str());
     EXPECT_EQ("[ \"Type mismatch\" ]", errors->str());
 
 
     errors = Element::createList();
     errors = Element::createList();
-    EXPECT_FALSE(data_test_with_errors(dd, "data22_9.data", errors));
+    EXPECT_FALSE(dataTestWithErrors(dd, "data22_9.data", errors));
     EXPECT_EQ("[ \"Unknown item value_does_not_exist\" ]", errors->str());
     EXPECT_EQ("[ \"Unknown item value_does_not_exist\" ]", errors->str());
 }
 }
+
+TEST(ModuleSpec, CommandValidation) {
+    ModuleSpec dd = moduleSpecFromFile(specfile("spec2.spec"));
+    ConstElementPtr arg = Element::fromJSON("{}");
+    ElementPtr errors = Element::createList();
+
+    EXPECT_TRUE(dd.validateCommand("shutdown", arg, errors));
+    EXPECT_EQ(errors->size(), 0);
+
+    errors = Element::createList();
+    EXPECT_FALSE(dd.validateCommand("unknowncommand", arg, errors));
+    EXPECT_EQ(errors->size(), 1);
+    EXPECT_EQ(errors->get(0)->stringValue(), "Unknown command unknowncommand");
+
+    errors = Element::createList();
+    EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+    EXPECT_EQ(errors->size(), 1);
+    EXPECT_EQ(errors->get(0)->stringValue(), "Non-optional value missing");
+
+    errors = Element::createList();
+    arg = Element::fromJSON("{ \"message\": \"Hello\" }");
+    EXPECT_TRUE(dd.validateCommand("print_message", arg, errors));
+    EXPECT_EQ(errors->size(), 0);
+
+    errors = Element::createList();
+    arg = Element::fromJSON("{ \"message\": \"Hello\", \"unknown_second_arg\": 1 }");
+    EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+    EXPECT_EQ(errors->size(), 1);
+    EXPECT_EQ(errors->get(0)->stringValue(), "Unknown item unknown_second_arg");
+
+    errors = Element::createList();
+    arg = Element::fromJSON("{ \"message\": 1 }");
+    EXPECT_FALSE(dd.validateCommand("print_message", arg, errors));
+    EXPECT_EQ(errors->size(), 1);
+    EXPECT_EQ(errors->get(0)->stringValue(), "Type mismatch");
+
+}

+ 1 - 0
src/lib/config/tests/testdata/Makefile.am

@@ -49,3 +49,4 @@ EXTRA_DIST += spec25.spec
 EXTRA_DIST += spec26.spec
 EXTRA_DIST += spec26.spec
 EXTRA_DIST += spec27.spec
 EXTRA_DIST += spec27.spec
 EXTRA_DIST += spec28.spec
 EXTRA_DIST += spec28.spec
+EXTRA_DIST += spec29.spec

+ 35 - 0
src/lib/config/tests/testdata/spec29.spec

@@ -0,0 +1,35 @@
+{
+  "module_spec": {
+    "module_name": "Spec29",
+    "config_data": [
+      { "item_name": "item1",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 1
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "good_command",
+        "command_description": "A good command",
+        "command_args": []
+      },
+      {
+        "command_name": "bad_command",
+        "command_description": "A bad command",
+        "command_args": []
+      },
+      {
+        "command_name": "command_with_arg",
+        "command_description": "A command with an (integer) argument",
+        "command_args": [ {
+          "item_name": "number",
+          "item_type": "integer",
+          "item_optional": true,
+          "item_default": 1
+        } ]
+      }
+    ]
+  }
+}
+

+ 14 - 2
src/lib/datasrc/memory_datasrc.cc

@@ -40,6 +40,7 @@ struct MemoryZone::MemoryZoneImpl {
     // Information about the zone
     // Information about the zone
     RRClass zone_class_;
     RRClass zone_class_;
     Name origin_;
     Name origin_;
+    string file_name_;
 
 
     // Some type aliases
     // Some type aliases
     /*
     /*
@@ -84,8 +85,8 @@ struct MemoryZone::MemoryZoneImpl {
         DomainNode* node;
         DomainNode* node;
         switch (domains->insert(name, &node)) {
         switch (domains->insert(name, &node)) {
             // Just check it returns reasonable results
             // Just check it returns reasonable results
-            case DomainTree::SUCCEED:
-            case DomainTree::ALREADYEXIST:
+            case DomainTree::SUCCESS:
+            case DomainTree::ALREADYEXISTS:
                 break;
                 break;
             // Something odd got out
             // Something odd got out
             default:
             default:
@@ -275,10 +276,21 @@ MemoryZone::load(const string& filename) {
     masterLoad(filename.c_str(), getOrigin(), getClass(),
     masterLoad(filename.c_str(), getOrigin(), getClass(),
         boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
         boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
     // If it went well, put it inside
     // If it went well, put it inside
+    impl_->file_name_ = filename;
     tmp.swap(impl_->domains_);
     tmp.swap(impl_->domains_);
     // And let the old data die with tmp
     // And let the old data die with tmp
 }
 }
 
 
+void
+MemoryZone::swap(MemoryZone& zone) {
+    std::swap(impl_, zone.impl_);
+}
+
+const string
+MemoryZone::getFileName() const {
+    return (impl_->file_name_);
+}
+
 /// Implementation details for \c MemoryDataSrc hidden from the public
 /// Implementation details for \c MemoryDataSrc hidden from the public
 /// interface.
 /// interface.
 ///
 ///

+ 27 - 6
src/lib/datasrc/memory_datasrc.h

@@ -15,6 +15,8 @@
 #ifndef __MEMORY_DATA_SOURCE_H
 #ifndef __MEMORY_DATA_SOURCE_H
 #define __MEMORY_DATA_SOURCE_H 1
 #define __MEMORY_DATA_SOURCE_H 1
 
 
+#include <string>
+
 #include <datasrc/zonetable.h>
 #include <datasrc/zonetable.h>
 
 
 namespace isc {
 namespace isc {
@@ -98,6 +100,20 @@ public:
         { }
         { }
     };
     };
 
 
+    /// Return the master file name of the zone
+    ///
+    /// This method returns the name of the zone's master file to be loaded.
+    /// The returned string will be an empty unless the zone has successfully
+    /// loaded a zone.
+    ///
+    /// This method should normally not throw an exception.  But the creation
+    /// of the return string may involve a resource allocation, and if it
+    /// fails, the corresponding standard exception will be thrown.
+    ///
+    /// \return The name of the zone file loaded in the zone, or an empty
+    /// string if the zone hasn't loaded any file.
+    const std::string getFileName() const;
+
     /// \brief Load zone from masterfile.
     /// \brief Load zone from masterfile.
     ///
     ///
     /// This loads data from masterfile specified by filename. It replaces
     /// This loads data from masterfile specified by filename. It replaces
@@ -122,6 +138,15 @@ public:
     ///     This will probably be needed when a better implementation of
     ///     This will probably be needed when a better implementation of
     ///     configuration reloading is written.
     ///     configuration reloading is written.
     void load(const std::string& filename);
     void load(const std::string& filename);
+
+    /// Exchanges the content of \c this zone with that of the given \c zone.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param zone Another \c MemoryZone object which is to be swapped with
+    /// \c this zone.
+    void swap(MemoryZone& zone);
+
 private:
 private:
     /// \name Hidden private data
     /// \name Hidden private data
     //@{
     //@{
@@ -158,10 +183,6 @@ private:
 /// The findZone() method takes a domain name and returns the best matching \c
 /// The findZone() method takes a domain name and returns the best matching \c
 /// MemoryZone in the form of (Boost) shared pointer, so that it can provide
 /// MemoryZone in the form of (Boost) shared pointer, so that it can provide
 /// the general interface for all data sources.
 /// the general interface for all data sources.
-///
-/// Currently, \c FindResult::zone is immutable for safety.
-/// In future versions we may want to make it changeable.  For example,
-/// we may want to allow configuration update on an existing zone.
 class MemoryDataSrc {
 class MemoryDataSrc {
 public:
 public:
     /// \brief A helper structure to represent the search result of
     /// \brief A helper structure to represent the search result of
@@ -180,11 +201,11 @@ public:
     /// See the description of \c find() for the semantics of the member
     /// See the description of \c find() for the semantics of the member
     /// variables.
     /// variables.
     struct FindResult {
     struct FindResult {
-        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
+        FindResult(result::Result param_code, const ZonePtr param_zone) :
             code(param_code), zone(param_zone)
             code(param_code), zone(param_zone)
         {}
         {}
         const result::Result code;
         const result::Result code;
-        const ConstZonePtr zone;
+        const ZonePtr zone;
     };
     };
 
 
     ///
     ///

+ 338 - 252
src/lib/datasrc/rbtree.h

@@ -17,11 +17,11 @@
 
 
 //! \file datasrc/rbtree.h
 //! \file datasrc/rbtree.h
 ///
 ///
-/// \note The purpose of the RBTree is to provide a generic map with 
-/// domain names as the key that can be used by various BIND 10 modules or
-/// even by other applications.  However, because of some unresolved design
-/// issue, the design and interface are not fixed, and RBTree isn't ready to
-/// be used as a base data structure by other modules.
+/// \note The purpose of the RBTree is to provide a generic map with
+///     domain names as the key that can be used by various BIND 10 modules or
+///     even by other applications.  However, because of some unresolved design
+///     issue, the design and interface are not fixed, and RBTree isn't ready
+///     to be used as a base data structure by other modules.
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 #include <boost/utility.hpp>
 #include <boost/utility.hpp>
@@ -35,16 +35,18 @@ namespace datasrc {
 
 
 namespace helper {
 namespace helper {
 
 
-/// Helper function to remove the base domain from super domain
+/// \brief Helper function to remove the base domain from super domain.
 ///
 ///
-/// the precondition of this function is the super_name contains the
+/// The precondition of this function is the super_name contains the
 /// sub_name so
 /// sub_name so
 /// \code Name a("a.b.c");
 /// \code Name a("a.b.c");
 /// Name b("b.c");
 /// Name b("b.c");
 /// Name c = a - b;
 /// Name c = a - b;
 /// \endcode
 /// \endcode
+/// c will contain "a".
 ///
 ///
-/// \note function in this namespace is not intended to be used outside.
+/// \note Functions in this namespace is not intended to be used outside of
+///     RBTree implementation.
 inline isc::dns::Name
 inline isc::dns::Name
 operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
 operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
     return (super_name.split(0, super_name.getLabelCount() -
     return (super_name.split(0, super_name.getLabelCount() -
@@ -52,66 +54,99 @@ operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
 }
 }
 }
 }
 
 
-template <typename T>
+template <typename T, bool returnEmptyNode>
 class RBTree;
 class RBTree;
 
 
-/// \brief \c RBNode use by RBTree to store any data related to one domain name
+/// \brief \c RBNode is used by RBTree to store any data related to one domain
+///     name.
+///
+/// This is meant to be used only from RBTree. It is meaningless to inherit it
+/// or create instances of it from elsewhere. For that reason, the constructor
+/// is private.
+///
+/// It serves three roles. One is to keep structure of the \c RBTree as a
+/// red-black tree. For that purpose, it has left, right and parent pointers
+/// and color member. These are private and accessed only from within the tree.
+///
+/// The second one is to store data for one domain name. The data related
+/// functions can be used to access and set the data.
 ///
 ///
-/// It has two roles, the first one is as one node in the \c RBTree,
-/// the second one is to store the data related to one domain name and maintain
-/// the domain name hierarchy struct in one domain name space.
-/// As for the first role, it has left, right, parent and color members
-/// which is used to keep the balance of the \c RBTree.
-/// As for the second role, \c RBNode use down pointer to refer to all its sub
-/// domains, so the name of current node always relative to the up node. since
-/// we only has down pointer without up pointer, so we can only walk down from
-/// top domain to sub domain.
-/// One special kind of node is non-terminal node
-/// which has subdomains with RRset but itself doesn't have any RRsets.
+/// The third role is to keep the hierarchy of domains. The down pointer points
+/// to a subtree of subdomains. Note that we can traverse the hierarchy down,
+/// but not up.
 ///
 ///
-/// \note \c RBNode basically used internally by RBTree, it is meaningless to
-/// inherited from it or create it without \c RBTree.
+/// One special kind of node is non-terminal node. It has subdomains with
+/// RRsets, but doesn't have any RRsets itself.
 template <typename T>
 template <typename T>
 class RBNode : public boost::noncopyable {
 class RBNode : public boost::noncopyable {
+private:
+    /// The RBNode is meant for use from within RBTree, so it has access to
+    /// it.
+    template <typename U, bool returnEmptyNode>
+    friend class RBTree;
+
+    /// \name Constructors
+    ///
+    /// \note The existence of a RBNode without a RBTree is meaningless.
+    ///     Therefore the constructors are private.
+    //@{
+
+    /// \brief Default constructor.
+    ///
+    /// This constructor is provided specifically for generating a special
+    /// "null" node.
+    RBNode();
+
+    /// \brief Constructor from the node name.
+    ///
+    /// \param name The *relative* domain name (if this will live inside
+    ///     a.b.c and is called d.e.a.b.c, then you pass d.e).
+    RBNode(const isc::dns::Name& name);
+    //@}
+
 public:
 public:
-    /// only \c RBTree can create and destroy \c RBNode
-    friend class RBTree<T>;
+    /// \brief Alias for shared pointer to the data.
     typedef boost::shared_ptr<T> NodeDataPtr;
     typedef boost::shared_ptr<T> NodeDataPtr;
 
 
-    /// \name Destructor
-    /// \note it's seems a little strange that constructor is private
-    /// but deconstructor left public, the reason is for some smart pointer
-    /// like std::auto_ptr, they needs to delete RBNode in sometimes, but
-    /// \code delete *pointer_to_node \endcode shouldn't be called directly
-    //@{
+    /// \brief Destructor
+    ///
+    /// It might seem strange that constructors are private and destructor
+    /// public, but this is needed because of shared pointers need access
+    /// to the destructor.
+    ///
+    /// You should never call anything like:
+    /// \code delete pointer_to_node; \endcode
+    /// The RBTree handles both creation and destructoion of nodes.
     ~RBNode();
     ~RBNode();
-    //@}
 
 
-    /// \name Test functions
+    /// \name Getter functions.
     //@{
     //@{
-    /// \brief return the name of current node, it's relative to its top node
+    /// \brief Return the name of current node.
+    ///
+    /// It's relative to its containing node.
     ///
     ///
     /// To get the absolute name of one node, the node path from the top node
     /// To get the absolute name of one node, the node path from the top node
-    /// to current node has to be recorded
+    /// to current node has to be recorded.
     const isc::dns::Name& getName() const { return (name_); }
     const isc::dns::Name& getName() const { return (name_); }
 
 
-    /// \brief return the data store in this node
-    /// \note, since the data is managed by RBNode, developer should not
-    /// free the pointer
+    /// \brief Return the data stored in this node.
+    ///
+    /// You should not delete the data, it is handled by shared pointers.
     NodeDataPtr& getData() { return (data_); }
     NodeDataPtr& getData() { return (data_); }
-    /// \brief return the data stored in this node, read-only version
+    /// \brief Return the data stored in this node.
     const NodeDataPtr& getData() const { return (data_); }
     const NodeDataPtr& getData() const { return (data_); }
 
 
-    /// \brief return whether the node has related data
-    /// \note it's meaningless has empty \c RBNode in one RBTree, the only
-    /// exception is for non-terminal node which has sub domain nodes who
-    /// has data(rrset)
+    /// \brief return whether the node has related data.
+    ///
+    /// There can be empty nodes inside the RBTree. They are usually the
+    /// non-terminal domains, but it is possible (yet probably meaningless)
+    /// empty nodes anywhere.
     bool isEmpty() const { return (data_.get() == NULL); }
     bool isEmpty() const { return (data_.get() == NULL); }
     //@}
     //@}
 
 
-    /// \name Modify functions
+    /// \name Setter functions.
     //@{
     //@{
-    /// \breif set the data stored in the node
+    /// \brief Set the data stored in the node.
     void setData(const NodeDataPtr& data) { data_ = data; }
     void setData(const NodeDataPtr& data) { data_ = data; }
     //@}
     //@}
 
 
@@ -122,8 +157,6 @@ public:
     /// These methods never throw an exception.
     /// These methods never throw an exception.
     //@{
     //@{
     /// Return if callback is enabled at the node.
     /// Return if callback is enabled at the node.
-    ///
-    /// This method never throws an exception.
     bool isCallbackEnabled() const { return (callback_required_); }
     bool isCallbackEnabled() const { return (callback_required_); }
 
 
     /// Enable callback at the node.
     /// Enable callback at the node.
@@ -137,55 +170,45 @@ public:
 private:
 private:
     /// \brief Define rbnode color
     /// \brief Define rbnode color
     enum RBNodeColor {BLACK, RED};
     enum RBNodeColor {BLACK, RED};
-
-    /// \name Constructors
-    /// \note \c Single RBNode is meaningless without living inside one \c RBTree
-    /// the creation and destroy of one \c RBNode is handle by host \c RBTree, so
-    /// the constructors and destructor of \c RBNode is left private
-    //@{
-    /// \brief Default constructor.
-    ///
-    /// This constructor is provided specifically for generating a special
-    /// "null" node, and is intended be used only internally.
-    RBNode();
-
-    /// \brief Constructor from the node name.
-    ///
-    /// \param name The domain name corresponding to the node.
-    RBNode(const isc::dns::Name& name);
-    //@}
-
     /// This is a factory class method of a special singleton null node.
     /// This is a factory class method of a special singleton null node.
     static RBNode<T>* NULL_NODE() {
     static RBNode<T>* NULL_NODE() {
         static RBNode<T> null_node;
         static RBNode<T> null_node;
         return (&null_node);
         return (&null_node);
     }
     }
 
 
-    /// data to maintain the rbtree balance
+    /// \name Data to maintain the rbtree structure.
+    //@{
     RBNode<T>*  parent_;
     RBNode<T>*  parent_;
     RBNode<T>*  left_;
     RBNode<T>*  left_;
     RBNode<T>*  right_;
     RBNode<T>*  right_;
     RBNodeColor color_;
     RBNodeColor color_;
+    //@}
 
 
+    /// \brief Relative name of the node.
     isc::dns::Name     name_;
     isc::dns::Name     name_;
+    /// \brief Data stored here.
     NodeDataPtr       data_;
     NodeDataPtr       data_;
 
 
-    /// the down pointer points to the root node of sub domains of current
-    /// domain
-    /// \par Adding down pointer to \c RBNode is for two purpose:
-    /// \li Accelerate the search process, with sub domain tree, it split the
-    /// big flat tree into several hierarchy trees
-    /// \li It save memory useage, so same label won't be saved several times
+    /// \brief The subdomain tree.
+    ///
+    /// This points to the root node of trees of subdomains of this domain.
+    ///
+    /// \par Adding down pointer to \c RBNode has two purposes:
+    /// \li Accelerate the search process, with sub domain tree, it splits the
+    ///     big flat tree into several hierarchy trees.
+    /// \li It saves memory useage as it allows storing only relative names,
+    ///     avoiding storage of the same domain labels multiple times.
     RBNode<T>*  down_;
     RBNode<T>*  down_;
 
 
-    // If true, callback should be called at this node in search.
-    // (This may have to become part of more general "attribute flags")
+    /// \brief If callback should be called when traversing this node in
+    /// RBTree::find().
+    ///
+    /// \todo It might be needed to put it into more general attributes field.
     bool callback_required_;
     bool callback_required_;
 };
 };
 
 
 
 
-// typically each node should has a name associate with it
-// this construction is only used to create \c NULLNODE
+// This is only to support NULL nodes.
 template <typename T>
 template <typename T>
 RBNode<T>::RBNode() :
 RBNode<T>::RBNode() :
     parent_(this),
     parent_(this),
@@ -216,30 +239,46 @@ template <typename T>
 RBNode<T>::~RBNode() {
 RBNode<T>::~RBNode() {
 }
 }
 
 
-// note: the following class description is documented using C-style comments
+
+// note: the following class description is documented using multiline comments
 // because the verbatim diagram contain a backslash, which could be interpreted
 // because the verbatim diagram contain a backslash, which could be interpreted
-// as part of a multi-line comment with C++ style comments.
+// as escape of newline in singleline comment.
 /**
 /**
- *  \brief \c RBTree class represents all the domains with the same suffix,
- *  so it can be used to store the domains in one zone.
- * 
- *  \c RBTree is a generic red black tree, and contains all the nodes with
- *  the same suffix, since each name may have sub domain names
- *  so \c RBTree is a recursive data structure namely tree in tree.
- *  So for one zone, several RBTrees may be involved. But from outside, the sub
- *  tree is opaque for end users.
- * 
- *  \c RBTree split the domain space into hierarchy red black trees, nodes in one
- *  tree has the same base name. The benefit of this struct is that:
- *  - enhance the query performace compared with one big flat red black tree
- *  - decrase the memory footprint to save common labels only once.
- * 
+ *  \brief \c RBTree class represents all the domains with the same suffix.
+ *      It can be used to store the domains in one zone, for example.
+ *
+ *  RBTree is a generic map from domain names to any kind of data. Internally,
+ *  it uses a red-black tree. However, it isn't one tree containing everything.
+ *  Subdomains are trees, so this structure is recursive - trees inside trees.
+ *  But, from the interface point of view, it is opaque data structure.
+ *
+ *  \c RBTree splits the domain space into hierarchy red black trees; nodes
+ * in one tree has the same base name. The benefit of this struct is that:
+ *  - Enhances the query performace compared with one big flat red black tree.
+ *  - Decreases the memory footprint, as it doesn't store the suffix labels
+ *      multiple times.
+ *
+ *  Depending on different usage, rbtree will support different search policy.
+ *  Whether return empty node to end user is one policy among them. Search
+ *  policy is as the last template parameter, the default policy will NOT
+ *  return empty node to end user, pass ture will get empty node during find
+ *  is needed
+ *
+ * \anchor diagram
+ *
+ * with the following names:
+ *  - a
+ *  - b
+ *  - c
+ *  - x.d.e.f
+ *  - z.d.e.f
+ *  - g.h
+ *  - o.w.y.d.e.f
+ *  - p.w.y.d.e.f
+ *  - q.w.y.d.e.f
+ *
+ * the tree will looks like:
  *  \verbatim
  *  \verbatim
-  with the following names:
-      a       x.d.e.f     o.w.y.d.e.f
-      b       z.d.e.f     p.w.y.d.e.f
-      c       g.h         q.w.y.d.e.f
-      the tree will looks like:
                                 b
                                 b
                               /   \
                               /   \
                              a    d.e.f
                              a    d.e.f
@@ -253,33 +292,34 @@ RBNode<T>::~RBNode() {
                                      p
                                      p
                                     / \
                                     / \
                                    o   q
                                    o   q
- *  \endverbatim
- *  \note open problems:
- *  - current find funciton only return non-empty nodes, so there is no difference
- *    between find one not exist name with empty non-terminal nodes, but in DNS query
- *    logic, they are different
+   \endverbatim
  *  \todo
  *  \todo
  *  - add remove interface
  *  - add remove interface
- *  - add iterator to iterate the whole rbtree while may needed by axfr
- *  - since \c RBNode only has down pointer without up pointer, the node path during finding
- *    should be recorded for later use
+ *  - add iterator to iterate over the whole \c RBTree.  This may be necessary,
+ *    for example, to support AXFR.
+ *  - since \c RBNode only has down pointer without up pointer, the node path
+ *    during finding should be recorded for later use
  */
  */
-template <typename T>
+template <typename T, bool returnEmptyNode = false>
 class RBTree : public boost::noncopyable {
 class RBTree : public boost::noncopyable {
     friend class RBNode<T>;
     friend class RBNode<T>;
 public:
 public:
-    /// \brief The return value for the \c find() insert() and erase() method
+    /// \brief The return value for the \c find() and insert() methods
     enum Result {
     enum Result {
-        SUCCEED, //insert or erase succeed
-        EXACTMATCH, //find the target name
-        PARTIALMATCH, //find part of target name
-        NOTFOUND,  // for find function means no related name found
-                   // for erase function means erase not exist name
-        ALREADYEXIST, //for insert operation, the name to insert already exist
+        SUCCESS,    ///< Insert was successful
+        /// \brief The node returned from find mathes exactly the name given
+        EXACTMATCH,
+        PARTIALMATCH, ///< A superdomain node was found
+        NOTFOUND,   ///< Not even any superdomain was found
+        /// \brief Returned by insert() if a node of the name already exists
+        ALREADYEXISTS,
     };
     };
 
 
     /// \name Constructor and Destructor
     /// \name Constructor and Destructor
     //@{
     //@{
+    /// The constructor.
+    ///
+    /// It never throws an exception.
     explicit RBTree();
     explicit RBTree();
 
 
     /// \b Note: RBTree is not intended to be inherited so the destructor
     /// \b Note: RBTree is not intended to be inherited so the destructor
@@ -287,130 +327,157 @@ public:
     ~RBTree();
     ~RBTree();
     //@}
     //@}
 
 
-    /// \name Inquery methods
-    //@{
-    /// \brief Find the node that gives a longest match against the given name
-    ///
-    /// This method searches the \c RBTree for a node whose name is a longest
-    /// match against \c name.  The found node, if any, is returned via the
-    /// \c node pointer.
-    /// By default, nodes that don't have data will be ignored, and the result
-    /// can be \c NOTFOUND even if there is a node whose name matches the
-    /// given \c name.
-    /// We'll soon introduce a "no data OK" mode in this method.  It would
-    /// match any node of the tree regardless of whether the node has data
-    /// or not.
-    /// Since the tree is "compressed", i.e., a node can contain multiple
-    /// name labels, there are counter intuitive cases in the "no data OK"
-    /// mode.  For example, see the diagram of the class description.
-    /// Name "y.d.e.f" is logically contained in the tree as part of the
-    /// "compressed" node of "w.y".  But the search logic of this method
-    /// cannot find the logical match, and would return a \c PARTIALMATCH
-    /// result pointing to node "d.e.f".  To correctly identify the real
-    /// longest match, "y.d.e.f" with empty data, the caller needs to
-    /// perform additional steps.
-    ///
-    /// This version of \c find() method is templated to allow the caller
-    /// to specify a "hook" at nodes that give a partial match.
-    /// When the search encounters a node with data that partially matches
-    /// \c name (i.e. node's name is a superdomain of \c name) and has
-    /// enabled callback (via the \c RBNode::enableCallback() method), if
-    /// \c callback is non \c NULL then the callback function is called
-    /// with the argument of a reference to the node and the given
-    /// callback argument (\c callback_arg).  The template parameter specifies
-    /// the type of the callback argument.
-    /// The callback function returns either \c true or \c false, meaning
-    /// the search should stop or continue, respectively.
-    /// If the return value is \c true the search stops immediately at the
-    /// node even if there could be a longer matching name below it.
-    /// In reality, this convoluted callback rule is specifically intended
-    /// to be used to handle a zone cut (delegation) at a name search inside
-    /// a zone, and won't be used in any other cases.
-    /// Other applications of the tree won't need callbacks, and they should
-    /// use the non templated version of the \c find() method.
-    ///
-    /// Since the expected usage of callback is very limited, we do not
-    /// generalize the interface so that it can be an arbitrary functions or
-    /// functor objects in favor of simplicity and efficiency.
-    ///
-    /// This method involves operations on names that can throw an exception.
+    /// \name Find methods
+    ///
+    /// \brief Find the node that gives a longest match against the given name.
+    ///
+    /// \anchor find
+    ///
+    /// These methods search the RBTree for a node whose name is a longest
+    /// against name. The found node, if any, is returned via the node pointer.
+    ///
+    /// By default, nodes that don't have data (see RBNode::isEmpty) are
+    /// ignored and the result can be NOTFOUND even if there's a node whose
+    /// name mathes. The plan is to introduce a "no data OK" mode for this
+    /// method, that would match any node of the tree regardless of wheather
+    /// the node has any data or not.
+    ///
+    /// The case with "no data OK" mode is not as easy as it seems. For example
+    /// in the diagram shown in the class description, the name y.d.e.f is
+    /// logically contained in the tree as part of the node w.y.  It cannot be
+    /// identified simply by checking whether existing nodes (such as
+    /// d.e.f or w.y) has data.
+    ///
+    /// These methods involves operations on names that can throw an exception.
     /// If that happens the exception will be propagated to the caller.
     /// If that happens the exception will be propagated to the caller.
     /// The callback function should generally not throw an exception, but
     /// The callback function should generally not throw an exception, but
     /// if it throws, the exception will be propagated to the caller.
     /// if it throws, the exception will be propagated to the caller.
     ///
     ///
+    /// The \c name parameter says what should be found. The node parameter
+    /// is output only and in case of EXACTMATCH and PARTIALMATCH, it is set
+    /// to a pointer to the found node.
+    ///
+    /// They return:
+    ///  - EXACTMATCH when a node with the same name as requested exists.
+    ///  - PARTIALMATCH when a node with the same name does not exist (or is
+    ///    empty), but there's a (nonempty) superdomain of the requested one.
+    ///    The superdomain with longest name is returned through the node
+    ///    parameter. Beware that if you store a zone in the tree, you may get
+    ///    PARTIALMATCH with zone apex when the given domain name is not there.
+    ///    You should not try to delegate into another zone in that case.
+    ///  - NOTFOUND if there's no node with the same name nor any superdomain
+    ///    of it. In that case, node parameter is left intact.
+    //@{
+
+    /// \brief Find with callback.
+    ///
+    /// \anchor callback
+    ///
+    /// This version of find calls the callback whenever traversing (on the
+    /// way from root down the tree) a marked node on the way down through the
+    /// domain namespace (see RBNode::enableCallback and related functions).
+    ///
+    /// If you return true from the callback, the search is stopped and a
+    /// PARTIALMATCH is returned with the given node. Note that this node
+    /// doesn't really need to be the one with longest possible match.
+    ///
+    /// This callback mechanism was designed with zone cut (delegation)
+    /// processing in mind. The marked nodes would be the ones at delegation
+    /// points. It is not expected that any other applications would need
+    /// callbacks; they should use the versions of find without callbacks.
+    /// The callbacks are not general functors for the same reason - we don't
+    /// expect it to be needed.
+    ///
     /// \param name Target to be found
     /// \param name Target to be found
     /// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
     /// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
-    /// it will store a pointer to the matching node
+    ///     it will store a pointer to the matching node
     /// \param callback If non \c NULL, a call back function to be called
     /// \param callback If non \c NULL, a call back function to be called
-    /// at "delegation" nodes (see above).
+    ///     at marked nodes (see above).
     /// \param callback_arg A caller supplied argument to be passed to
     /// \param callback_arg A caller supplied argument to be passed to
-    /// \c callback.
-    ///
-    /// \return \c EXACTMATCH A node that whose name is equal to \c name is
-    /// found.  \c *node will be set to point to that node.
-    /// \return \c PARTIALMATCH There is a no exact match, but a superdomain
-    /// of \c name exists.  \c node will be set to point to the node whose
-    /// name is the longest among such superdomains.
-    /// \return \c NOTFOUND There is no exact or partial match against \c name
-    /// \c *node will be intact in this case.
+    ///     \c callback.
+    ///
+    /// \return As described above, but in case of callback returning true,
+    ///     it returns immediately with the current node.
     template <typename CBARG>
     template <typename CBARG>
     Result find(const isc::dns::Name& name, RBNode<T>** node,
     Result find(const isc::dns::Name& name, RBNode<T>** node,
                 bool (*callback)(const RBNode<T>&, CBARG),
                 bool (*callback)(const RBNode<T>&, CBARG),
                 CBARG callback_arg) const;
                 CBARG callback_arg) const;
 
 
-    /// Same as the other version, but the returned \c node will be immutable.
+    /// \brief Find with callback returning immutable node.
+    ///
+    /// It has the same behaviour as the find with \ref callback version.
     template <typename CBARG>
     template <typename CBARG>
     Result find(const isc::dns::Name& name, const RBNode<T>** node,
     Result find(const isc::dns::Name& name, const RBNode<T>** node,
                 bool (*callback)(const RBNode<T>&, CBARG),
                 bool (*callback)(const RBNode<T>&, CBARG),
                 CBARG callback_arg) const;
                 CBARG callback_arg) const;
 
 
-    /// Same as the templated version, but does not use callback.
+    /// \brief Simple find.
     ///
     ///
-    /// Applications except the zone implementation should generally use the
-    /// non templated version.
+    /// Acts as described in the \ref find section.
     Result find(const isc::dns::Name& name, RBNode<T>** node) const {
     Result find(const isc::dns::Name& name, RBNode<T>** node) const {
         return (find<void*>(name, node, NULL, NULL));
         return (find<void*>(name, node, NULL, NULL));
     }
     }
 
 
-    /// Same as the templated version, but does not use callback, and the
-    /// returned \c node will be immutable.
+    /// \brieg Simple find returning immutable node.
     ///
     ///
-    /// In general, this version should be preferred over the other non
-    /// templated version, unless the caller knows it should modify the
-    /// returned node.
+    /// Acts as described in the \ref find section, but returns immutable node
+    /// pointer.
     Result find(const isc::dns::Name& name, const RBNode<T>** node) const {
     Result find(const isc::dns::Name& name, const RBNode<T>** node) const {
         return (find<void*>(name, node, NULL, NULL));
         return (find<void*>(name, node, NULL, NULL));
     }
     }
-
-    /// \brief Get the total node count in the tree
-    /// the node count including the node created common suffix node,
-    /// this function will only be used for debuging
-    int getNodeCount() const { return (node_count_);}
     //@}
     //@}
 
 
+    /// \brief Get the total number of nodes in the tree
+    ///
+    /// It includes nodes internally created as a result of adding a domain
+    /// name that is a subdomain of an existing node of the tree.
+    /// This function is mainly intended to be used for debugging.
+    int getNodeCount() const { return (node_count_); }
+
     /// \name Debug function
     /// \name Debug function
     //@{
     //@{
-    /// \brief print the nodes in the trees
+    /// \brief Print the nodes in the trees.
+    ///
+    /// \param os A \c std::ostream object to which the tree is printed.
+    /// \param depth A factor of the initial indentation.  Each line
+    /// will begin with space character repeating <code>5 * depth</code>
+    /// times.
     void dumpTree(std::ostream& os, unsigned int depth = 0) const;
     void dumpTree(std::ostream& os, unsigned int depth = 0) const;
     //@}
     //@}
 
 
-    /// \name Modify function
+    /// \name Modify functions
     //@{
     //@{
-    /// \brief Insert the domain name into the tree
-    /// \param name The name to be inserted into the tree
-    /// \param inserted_node If no node with the name in the tree,
-    /// new \c RBNode will be created, otherwise nothing will be done.
-    /// Anyway the pointer point to the node with the name will be assigned to
-    /// inserted_node
+    /// \brief Insert the domain name into the tree.
+    ///
+    /// It either finds an already existing node of the given name or inserts
+    /// a new one, if none exists yet. In any case, the inserted_node parameter
+    /// is set to point to that node. You can fill data into it or modify it.
+    /// So, if you don't know if a node exists or not and you need to modify
+    /// it, just call insert and act by the result.
+    ///
+    /// Please note that the tree can add some empty nodes by itself, so don't
+    /// assume that if you didn't insert a node of that name it doesn't exist.
+    ///
+    /// This method normally involves resource allocation.  If it fails
+    /// the corresponding standard exception will be thrown.
+    ///
+    /// This method does not provide the strong exception guarantee in its
+    /// strict sense; if an exception is thrown in the middle of this
+    /// method, the internal structure may change.  However, it should
+    /// still retain the same property as a mapping container before this
+    /// method is called.  For example, the result of \c find() should be
+    /// the same.  This method provides the weak exception guarantee in its
+    /// normal sense.
+    ///
+    /// \param name The name to be inserted into the tree.
+    /// \param inserted_node This is an output parameter and is set to the
+    ///     node.
+    ///
     /// \return
     /// \return
-    //  - SUCCEED means no node exists in the tree with the name before insert
-    /// - ALREADYEXIST means already has the node with the given name
-    //
-    /// \node To modify the data related with one name but not sure the name has
-    /// inserted or not, it is better to call \c insert,instead of
-    /// \c find(), in case the name isn't exist and needs to insert again
+    ///  - SUCCESS The node was added.
+    ///  - ALREADYEXISTS There was already a node of that name, so it was not
+    ///     added.
     Result insert(const isc::dns::Name& name, RBNode<T>** inserted_node);
     Result insert(const isc::dns::Name& name, RBNode<T>** inserted_node);
-    //@}
 
 
     /// \brief Swaps two tree's contents.
     /// \brief Swaps two tree's contents.
     ///
     ///
@@ -421,6 +488,7 @@ public:
         std::swap(NULLNODE, other.NULLNODE);
         std::swap(NULLNODE, other.NULLNODE);
         std::swap(node_count_, other.node_count_);
         std::swap(node_count_, other.node_count_);
     }
     }
+    //@}
 
 
 private:
 private:
     /// \name RBTree balance functions
     /// \name RBTree balance functions
@@ -435,14 +503,15 @@ private:
     /// \brief delete tree whose root is equal to node
     /// \brief delete tree whose root is equal to node
     void deleteHelper(RBNode<T> *node);
     void deleteHelper(RBNode<T> *node);
     /// \brief find the node with name
     /// \brief find the node with name
-    /// \param name is the target, up will points to the base domain of
-    /// the tree which name resides, node will point to the target node
-    /// if we has exact same name or partical name in current tree.
-    /// so for example, in zone a, we has
-    /// b.a, c.b.a and d.b.a search c.b.a, up will points to b.a.
-    /// and node will points to c.b.a
-    /// \note parameter up now is not used by any funciton, but we are gonna
-    /// need it soon to implement function like remove
+    ///
+    /// Internal searching function.
+    ///
+    /// \param name What should be found.
+    /// \param up It will point to the node whose down pointer points
+    ///     to the tree containing node. If we looked for o.w.y.d.e.f in the
+    ///     \ref diagram, the up would point to the w.y node.
+    ///     This parameter is not used currently, but it will be soon.
+    /// \param node The found node.
     template <typename CBARG>
     template <typename CBARG>
     Result findHelper(const isc::dns::Name& name, const RBNode<T>** up,
     Result findHelper(const isc::dns::Name& name, const RBNode<T>** up,
                       RBNode<T>** node,
                       RBNode<T>** node,
@@ -450,9 +519,7 @@ private:
                       CBARG callback_arg) const;
                       CBARG callback_arg) const;
     void dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
     void dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
                         unsigned int depth) const;
                         unsigned int depth) const;
-    /// for indent purpose, add certian mount empty charachter to output stream
-    /// according to the depth. This is a helper function which is only used when
-    /// dump tree
+    /// \brief Indentation helper function for dumpTree
     static void indent(std::ostream& os, unsigned int depth);
     static void indent(std::ostream& os, unsigned int depth);
 
 
     /// Split one node into two nodes, keep the old node and create one new
     /// Split one node into two nodes, keep the old node and create one new
@@ -468,21 +535,22 @@ private:
     unsigned int node_count_;
     unsigned int node_count_;
 };
 };
 
 
-template <typename T>
-RBTree<T>::RBTree() {
+template <typename T, bool S>
+RBTree<T,S>::RBTree() {
     NULLNODE = RBNode<T>::NULL_NODE();
     NULLNODE = RBNode<T>::NULL_NODE();
     root_ = NULLNODE;
     root_ = NULLNODE;
     node_count_ = 0;
     node_count_ = 0;
 }
 }
 
 
-template <typename T>
-RBTree<T>::~RBTree() {
+template <typename T, bool S>
+RBTree<T,S>::~RBTree() {
     deleteHelper(root_);
     deleteHelper(root_);
     assert(node_count_ == 0);
     assert(node_count_ == 0);
 }
 }
 
 
-template <typename T>
-void RBTree<T> ::deleteHelper(RBNode<T> *root) {
+template <typename T, bool S>
+void 
+RBTree<T,S>::deleteHelper(RBNode<T> *root) {
     if (root == NULLNODE) {
     if (root == NULLNODE) {
         return;
         return;
     }
     }
@@ -511,9 +579,10 @@ void RBTree<T> ::deleteHelper(RBNode<T> *root) {
     --node_count_;
     --node_count_;
 }
 }
 
 
-template <typename T> template <typename CBARG>
-typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node,
+template <typename T, bool S>
+template <typename CBARG>
+typename RBTree<T,S>::Result
+RBTree<T,S>::find(const isc::dns::Name& name, RBNode<T>** node,
                 bool (*callback)(const RBNode<T>&, CBARG),
                 bool (*callback)(const RBNode<T>&, CBARG),
                 CBARG callback_arg) const
                 CBARG callback_arg) const
 {
 {
@@ -521,9 +590,10 @@ RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node,
     return (findHelper(name, &up_node, node, callback, callback_arg));
     return (findHelper(name, &up_node, node, callback, callback_arg));
 }
 }
 
 
-template <typename T> template <typename CBARG>
-typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node,
+template <typename T, bool S>
+template <typename CBARG>
+typename RBTree<T,S>::Result
+RBTree<T,S>::find(const isc::dns::Name& name, const RBNode<T>** node,
                 bool (*callback)(const RBNode<T>&, CBARG),
                 bool (*callback)(const RBNode<T>&, CBARG),
                 CBARG callback_arg) const
                 CBARG callback_arg) const
 {
 {
@@ -537,13 +607,14 @@ RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node,
     return (ret);
     return (ret);
 }
 }
 
 
-template <typename T> template <typename CBARG>
-typename RBTree<T>::Result
-RBTree<T>::findHelper(const isc::dns::Name& target_name,
-                      const RBNode<T>** up_node,
-                      RBNode<T>** target,
-                      bool (*callback)(const RBNode<T>&, CBARG),
-                      CBARG callback_arg) const
+template <typename T, bool returnEmptyNode>
+template <typename CBARG>
+typename RBTree<T,returnEmptyNode>::Result
+RBTree<T,returnEmptyNode>::findHelper(const isc::dns::Name& target_name,
+                                      const RBNode<T>** up_node,
+                                      RBNode<T>** target,
+                                      bool (*callback)(const RBNode<T>&, CBARG),
+                                      CBARG callback_arg) const
 {
 {
     using namespace helper;
     using namespace helper;
 
 
@@ -558,7 +629,7 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name,
         const isc::dns::NameComparisonResult::NameRelation relation =
         const isc::dns::NameComparisonResult::NameRelation relation =
             compare_result.getRelation();
             compare_result.getRelation();
         if (relation == isc::dns::NameComparisonResult::EQUAL) {
         if (relation == isc::dns::NameComparisonResult::EQUAL) {
-            if (!node->isEmpty()) {
+            if (returnEmptyNode || !node->isEmpty()) {
                 *target = node;
                 *target = node;
                 ret = EXACTMATCH;
                 ret = EXACTMATCH;
             }
             }
@@ -571,7 +642,7 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name,
                 node = (compare_result.getOrder() < 0) ?
                 node = (compare_result.getOrder() < 0) ?
                     node->left_ : node->right_;
                     node->left_ : node->right_;
             } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
             } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
-                if (!node->isEmpty()) {
+                if (returnEmptyNode || !node->isEmpty()) {
                     ret = RBTree<T>::PARTIALMATCH;
                     ret = RBTree<T>::PARTIALMATCH;
                     *target = node;
                     *target = node;
                     if (callback != NULL && node->callback_required_) {
                     if (callback != NULL && node->callback_required_) {
@@ -592,9 +663,11 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name,
     return (ret);
     return (ret);
 }
 }
 
 
-template <typename T>
-typename RBTree<T>::Result
-RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
+
+template <typename T, bool returnEmptyNode>
+typename RBTree<T,returnEmptyNode>::Result
+RBTree<T,returnEmptyNode>::insert(const isc::dns::Name& target_name,
+                                  RBNode<T>** new_node) {
     using namespace helper;
     using namespace helper;
     RBNode<T>* parent = NULLNODE;
     RBNode<T>* parent = NULLNODE;
     RBNode<T>* current = root_;
     RBNode<T>* current = root_;
@@ -611,7 +684,12 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
             if (new_node != NULL) {
             if (new_node != NULL) {
                 *new_node = current;
                 *new_node = current;
             }
             }
-            return (ALREADYEXIST);
+
+            if (current->isEmpty() && !returnEmptyNode) {
+                return (SUCCESS);
+            } else {
+                return (ALREADYEXISTS);
+            }
         } else {
         } else {
             const int common_label_count = compare_result.getCommonLabels();
             const int common_label_count = compare_result.getCommonLabels();
             if (common_label_count == 1) {
             if (common_label_count == 1) {
@@ -664,21 +742,25 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
 
 
     ++node_count_;
     ++node_count_;
     node.release();
     node.release();
-    return (SUCCEED);
+    return (SUCCESS);
 }
 }
 
 
-template <typename T>
+
+template <typename T, bool S>
 void
 void
-RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
+RBTree<T,S>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
     using namespace helper;
     using namespace helper;
     const isc::dns::Name sub_name = node.name_ - base_name;
     const isc::dns::Name sub_name = node.name_ - base_name;
     // using auto_ptr here is to avoid memory leak in case of exception raised
     // using auto_ptr here is to avoid memory leak in case of exception raised
     // after the RBNode creation
     // after the RBNode creation
     std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
     std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
+    node.name_ = base_name;
+    // the rest of this function should be exception free so that it keeps
+    // consistent behavior (i.e., a weak form of strong exception guarantee)
+    // even if code after the call to this function throws an exception.
     std::swap(node.data_, down_node->data_);
     std::swap(node.data_, down_node->data_);
     std::swap(node.callback_required_, down_node->callback_required_);
     std::swap(node.callback_required_, down_node->callback_required_);
     down_node->down_ = node.down_;
     down_node->down_ = node.down_;
-    node.name_ = base_name;
     node.down_ = down_node.get();
     node.down_ = down_node.get();
     // root node of sub tree, the initial color is BLACK
     // root node of sub tree, the initial color is BLACK
     down_node->color_ = RBNode<T>::BLACK;
     down_node->color_ = RBNode<T>::BLACK;
@@ -686,9 +768,10 @@ RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
     down_node.release();
     down_node.release();
 }
 }
 
 
-template <typename T>
+
+template <typename T, bool S>
 void
 void
-RBTree<T>::insertRebalance(RBNode<T>** root, RBNode<T>* node) {
+RBTree<T,S>::insertRebalance(RBNode<T>** root, RBNode<T>* node) {
 
 
     RBNode<T>* uncle;
     RBNode<T>* uncle;
     while (node != *root && node->parent_->color_ == RBNode<T>::RED) {
     while (node != *root && node->parent_->color_ == RBNode<T>::RED) {
@@ -732,9 +815,9 @@ RBTree<T>::insertRebalance(RBNode<T>** root, RBNode<T>* node) {
 }
 }
 
 
 
 
-template <typename T>
+template <typename T, bool S>
 RBNode<T>*
 RBNode<T>*
-RBTree<T>::leftRotate(RBNode<T>** root, RBNode<T>* node) {
+RBTree<T,S>::leftRotate(RBNode<T>** root, RBNode<T>* node) {
     RBNode<T>* right = node->right_;
     RBNode<T>* right = node->right_;
     node->right_ = right->left_;
     node->right_ = right->left_;
     if (right->left_ != NULLNODE)
     if (right->left_ != NULLNODE)
@@ -757,9 +840,9 @@ RBTree<T>::leftRotate(RBNode<T>** root, RBNode<T>* node) {
     return (node);
     return (node);
 }
 }
 
 
-template <typename T>
+template <typename T, bool S>
 RBNode<T>*
 RBNode<T>*
-RBTree<T>::rightRotate(RBNode<T>** root, RBNode<T>* node) {
+RBTree<T,S>::rightRotate(RBNode<T>** root, RBNode<T>* node) {
     RBNode<T>* left = node->left_;
     RBNode<T>* left = node->left_;
     node->left_ = left->right_;
     node->left_ = left->right_;
     if (left->right_ != NULLNODE)
     if (left->right_ != NULLNODE)
@@ -781,17 +864,18 @@ RBTree<T>::rightRotate(RBNode<T>** root, RBNode<T>* node) {
     return (node);
     return (node);
 }
 }
 
 
-template <typename T>
+
+template <typename T, bool S>
 void
 void
-RBTree<T>::dumpTree(std::ostream& os, unsigned int depth) const {
+RBTree<T,S>::dumpTree(std::ostream& os, unsigned int depth) const {
     indent(os, depth);
     indent(os, depth);
     os << "tree has " << node_count_ << " node(s)\n";
     os << "tree has " << node_count_ << " node(s)\n";
     dumpTreeHelper(os, root_, depth);
     dumpTreeHelper(os, root_, depth);
 }
 }
 
 
-template <typename T>
+template <typename T, bool S>
 void
 void
-RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
+RBTree<T,S>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
                           unsigned int depth) const
                           unsigned int depth) const
 {
 {
     if (node == NULLNODE) {
     if (node == NULLNODE) {
@@ -816,13 +900,15 @@ RBTree<T>::dumpTreeHelper(std::ostream& os, const RBNode<T>* node,
     dumpTreeHelper(os, node->right_, depth + 1);
     dumpTreeHelper(os, node->right_, depth + 1);
 }
 }
 
 
-template <typename T>
+template <typename T, bool S>
 void
 void
-RBTree<T>::indent(std::ostream& os, unsigned int depth) {
+RBTree<T,S>::indent(std::ostream& os, unsigned int depth) {
     static const unsigned int INDENT_FOR_EACH_DEPTH = 5;
     static const unsigned int INDENT_FOR_EACH_DEPTH = 5;
     os << std::string(depth * INDENT_FOR_EACH_DEPTH, ' ');
     os << std::string(depth * INDENT_FOR_EACH_DEPTH, ' ');
 }
 }
 
 
+
+
 }
 }
 }
 }
 
 

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

@@ -405,4 +405,58 @@ TEST_F(MemoryZoneTest, load) {
         MasterLoadError);
         MasterLoadError);
 }
 }
 
 
+TEST_F(MemoryZoneTest, swap) {
+    // build one zone with some data
+    MemoryZone zone1(class_, origin_);
+    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_));
+    EXPECT_EQ(result::SUCCESS, zone1.add(rr_ns_aaaa_));
+
+    // build another zone of a different RR class with some other data
+    const Name other_origin("version.bind");
+    ASSERT_NE(origin_, other_origin); // make sure these two are different
+    MemoryZone zone2(RRClass::CH(), other_origin);
+    EXPECT_EQ(result::SUCCESS,
+              zone2.add(RRsetPtr(new RRset(Name("version.bind"),
+                                           RRClass::CH(), RRType::TXT(),
+                                           RRTTL(0)))));
+
+    zone1.swap(zone2);
+    EXPECT_EQ(other_origin, zone1.getOrigin());
+    EXPECT_EQ(origin_, zone2.getOrigin());
+    EXPECT_EQ(RRClass::CH(), zone1.getClass());
+    EXPECT_EQ(RRClass::IN(), zone2.getClass());
+    // make sure the zone data is swapped, too
+    findTest(origin_, RRType::NS(), Zone::NXDOMAIN, false, ConstRRsetPtr(),
+             &zone1);
+    findTest(other_origin, RRType::TXT(), Zone::SUCCESS, false,
+             ConstRRsetPtr(), &zone1);
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+             &zone2);
+    findTest(other_origin, RRType::TXT(), Zone::NXDOMAIN, false,
+             ConstRRsetPtr(), &zone2);
+}
+
+TEST_F(MemoryZoneTest, getFileName) {
+    // for an empty zone the file name should also be empty.
+    EXPECT_TRUE(zone_.getFileName().empty());
+
+    // if loading a zone fails the file name shouldn't be set.
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+    EXPECT_TRUE(zone_.getFileName().empty());
+
+    // after a successful load, the specified file name should be set
+    MemoryZone rootzone(class_, Name("."));
+    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+    // overriding load, which will fail
+    EXPECT_THROW(rootzone.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+                 MasterLoadError);
+    // the file name should be intact.
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", rootzone.getFileName());
+
+    // After swap, file names should also be swapped.
+    zone_.swap(rootzone);
+    EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_.getFileName());
+    EXPECT_TRUE(rootzone.getFileName().empty());
+}
 }
 }

+ 102 - 49
src/lib/datasrc/tests/rbtree_unittest.cc

@@ -51,30 +51,21 @@ namespace {
 class RBTreeTest : public::testing::Test {
 class RBTreeTest : public::testing::Test {
 protected:
 protected:
     RBTreeTest() : rbtree() {
     RBTreeTest() : rbtree() {
-        rbtree.insert(Name("c"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
-        rbtree.insert(Name("b"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
-        rbtree.insert(Name("a"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(3)));
-        rbtree.insert(Name("x.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(4)));
-        rbtree.insert(Name("z.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(5)));
-        rbtree.insert(Name("g.h"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(6)));
-        rbtree.insert(Name("i.g.h"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(7)));
-        rbtree.insert(Name("o.w.y.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(8)));
-        rbtree.insert(Name("j.z.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(9)));
-        rbtree.insert(Name("p.w.y.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(10)));
-        rbtree.insert(Name("q.w.y.d.e.f"), &rbtnode);
-        rbtnode->setData(RBNode<int>::NodeDataPtr(new int(11)));
+        const char * domain_names[] = {"c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", 
+            "o.w.y.d.e.f", "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
+        int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
+        for (int i = 0; i < name_count; ++i) {
+            rbtree.insert(Name(domain_names[i]), &rbtnode);
+            rbtnode->setData(RBNode<int>::NodeDataPtr(new int(i + 1)));
+
+            rbtree_expose_empty_node.insert(Name(domain_names[i]), &rbtnode);
+            rbtnode->setData(RBNode<int>::NodeDataPtr(new int(i + 1)));
+
+        }
     }
     }
+
     RBTree<int> rbtree;
     RBTree<int> rbtree;
+    RBTree<int, true> rbtree_expose_empty_node;
     RBNode<int>* rbtnode;
     RBNode<int>* rbtnode;
     const RBNode<int>* crbtnode;
     const RBNode<int>* crbtnode;
 };
 };
@@ -90,64 +81,126 @@ TEST_F(RBTreeTest, setGetData) {
 }
 }
 
 
 TEST_F(RBTreeTest, insertNames) {
 TEST_F(RBTreeTest, insertNames) {
-    EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("d.e.f"), &rbtnode));
+    //if don't expose empty node, even the node already exsit which is caused by node fission
+    //we will return succeed
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("d.e.f"), &rbtnode));
     EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
     EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
     EXPECT_EQ(13, rbtree.getNodeCount());
     EXPECT_EQ(13, rbtree.getNodeCount());
 
 
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("."), &rbtnode));
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, 
+            rbtree_expose_empty_node.insert(Name("d.e.f"), &rbtnode));
+    EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
+    EXPECT_EQ(13, rbtree_expose_empty_node.getNodeCount());
+
+
+    //insert not exist node
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("."), &rbtnode));
     EXPECT_EQ(Name("."), rbtnode->getName());
     EXPECT_EQ(Name("."), rbtnode->getName());
     EXPECT_EQ(14, rbtree.getNodeCount());
     EXPECT_EQ(14, rbtree.getNodeCount());
 
 
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("example.com"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("."), &rbtnode));
+    EXPECT_EQ(Name("."), rbtnode->getName());
+    EXPECT_EQ(14, rbtree_expose_empty_node.getNodeCount());
+    
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("example.com"), &rbtnode));
     EXPECT_EQ(15, rbtree.getNodeCount());
     EXPECT_EQ(15, rbtree.getNodeCount());
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
 
 
-    // return ALREADYEXIST, since node "example.com" already has been explicitly inserted
-    EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("example.com"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("example.com"), &rbtnode));
+    EXPECT_EQ(15, rbtree_expose_empty_node.getNodeCount());
+    rbtnode->setData(RBNode<int>::NodeDataPtr(new int(12)));
+
+
+    // return ALREADYEXISTS, since node "example.com" already has been explicitly inserted
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree.insert(Name("example.com"), &rbtnode));
     EXPECT_EQ(15, rbtree.getNodeCount());
     EXPECT_EQ(15, rbtree.getNodeCount());
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree_expose_empty_node.insert(Name("example.com"), &rbtnode));
+    EXPECT_EQ(15, rbtree_expose_empty_node.getNodeCount());
+
 
 
     // split the node "d.e.f"
     // split the node "d.e.f"
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("k.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("k.e.f"), &rbtnode));
     EXPECT_EQ(Name("k"), rbtnode->getName());
     EXPECT_EQ(Name("k"), rbtnode->getName());
     EXPECT_EQ(17, rbtree.getNodeCount());
     EXPECT_EQ(17, rbtree.getNodeCount());
 
 
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("k.e.f"), &rbtnode));
+    EXPECT_EQ(Name("k"), rbtnode->getName());
+    EXPECT_EQ(17, rbtree_expose_empty_node.getNodeCount());
+
+
     // split the node "g.h"
     // split the node "g.h"
-    EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("h"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("h"), &rbtnode));
     EXPECT_EQ(Name("h"), rbtnode->getName());
     EXPECT_EQ(Name("h"), rbtnode->getName());
     EXPECT_EQ(18, rbtree.getNodeCount());
     EXPECT_EQ(18, rbtree.getNodeCount());
 
 
+    //node fission will create node "h"
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree_expose_empty_node.insert(Name("h"), &rbtnode));
+    EXPECT_EQ(Name("h"), rbtnode->getName());
+    EXPECT_EQ(18, rbtree_expose_empty_node.getNodeCount());
+
+
     // add child domain
     // add child domain
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
     EXPECT_EQ(Name("m"), rbtnode->getName());
     EXPECT_EQ(Name("m"), rbtnode->getName());
     EXPECT_EQ(19, rbtree.getNodeCount());
     EXPECT_EQ(19, rbtree.getNodeCount());
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("n.p.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("n.p.w.y.d.e.f"), &rbtnode));
     EXPECT_EQ(Name("n"), rbtnode->getName());
     EXPECT_EQ(Name("n"), rbtnode->getName());
     EXPECT_EQ(20, rbtree.getNodeCount());
     EXPECT_EQ(20, rbtree.getNodeCount());
 
 
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("l.a"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("m.p.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(Name("m"), rbtnode->getName());
+    EXPECT_EQ(19, rbtree_expose_empty_node.getNodeCount());
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("n.p.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(Name("n"), rbtnode->getName());
+    EXPECT_EQ(20, rbtree_expose_empty_node.getNodeCount());
+
+
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("l.a"), &rbtnode));
     EXPECT_EQ(Name("l"), rbtnode->getName());
     EXPECT_EQ(Name("l"), rbtnode->getName());
     EXPECT_EQ(21, rbtree.getNodeCount());
     EXPECT_EQ(21, rbtree.getNodeCount());
 
 
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("r.d.e.f"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("s.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("l.a"), &rbtnode));
+    EXPECT_EQ(Name("l"), rbtnode->getName());
+    EXPECT_EQ(21, rbtree_expose_empty_node.getNodeCount());
+
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("r.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("s.d.e.f"), &rbtnode));
     EXPECT_EQ(23, rbtree.getNodeCount());
     EXPECT_EQ(23, rbtree.getNodeCount());
 
 
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("h.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("r.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("s.d.e.f"), &rbtnode));
+    EXPECT_EQ(23, rbtree_expose_empty_node.getNodeCount());
+
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("h.w.y.d.e.f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("h.w.y.d.e.f"), &rbtnode));
 
 
     // add more nodes one by one to cover leftRotate and rightRotate
     // add more nodes one by one to cover leftRotate and rightRotate
-    EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("f"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("m"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("nm"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("om"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("k"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("l"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("fe"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("ge"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("i"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("ae"), &rbtnode));
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("n"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("m"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("nm"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("om"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("k"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("l"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("fe"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("ge"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("i"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("ae"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("n"), &rbtnode));
+    
+    EXPECT_EQ(RBTree<int>::ALREADYEXISTS, rbtree_expose_empty_node.insert(Name("f"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("m"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("nm"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("om"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("k"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("l"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("fe"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("ge"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("i"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("ae"), &rbtnode));
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree_expose_empty_node.insert(Name("n"), &rbtnode));
 }
 }
 
 
+   
 TEST_F(RBTreeTest, findName) {
 TEST_F(RBTreeTest, findName) {
     // find const rbtnode
     // find const rbtnode
     // exact match
     // exact match
@@ -177,7 +230,7 @@ testCallback(const RBNode<int>&, bool* callack_checker) {
 
 
 TEST_F(RBTreeTest, callback) {
 TEST_F(RBTreeTest, callback) {
     // by default callback isn't enabled
     // by default callback isn't enabled
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("callback.example"),
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("callback.example"),
                                                   &rbtnode));
                                                   &rbtnode));
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
     rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
     EXPECT_FALSE(rbtnode->isCallbackEnabled());
     EXPECT_FALSE(rbtnode->isCallbackEnabled());
@@ -192,11 +245,11 @@ TEST_F(RBTreeTest, callback) {
     rbtnode->enableCallback();
     rbtnode->enableCallback();
     // add more levels below and above the callback node for partial match.
     // add more levels below and above the callback node for partial match.
     RBNode<int>* subrbtnode;
     RBNode<int>* subrbtnode;
-    EXPECT_EQ(RBTree<int>::SUCCEED, rbtree.insert(Name("sub.callback.example"),
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("sub.callback.example"),
                                                   &subrbtnode));
                                                   &subrbtnode));
     subrbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
     subrbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
     RBNode<int>* parentrbtnode;
     RBNode<int>* parentrbtnode;
-    EXPECT_EQ(RBTree<int>::ALREADYEXIST, rbtree.insert(Name("example"),
+    EXPECT_EQ(RBTree<int>::SUCCESS, rbtree.insert(Name("example"),
                                                        &parentrbtnode));
                                                        &parentrbtnode));
     //  the chilld/parent nodes shouldn't "inherit" the callback flag.
     //  the chilld/parent nodes shouldn't "inherit" the callback flag.
     // "rbtnode" may be invalid due to the insertion, so we need to re-find
     // "rbtnode" may be invalid due to the insertion, so we need to re-find

+ 4 - 4
src/lib/datasrc/zonetable.cc

@@ -51,8 +51,8 @@ struct ZoneTable::ZoneTableImpl {
         ZoneNode* node(NULL);
         ZoneNode* node(NULL);
         switch (zones_.insert(zone->getOrigin(), &node)) {
         switch (zones_.insert(zone->getOrigin(), &node)) {
             // This is OK
             // This is OK
-            case ZoneTree::SUCCEED:
-            case ZoneTree::ALREADYEXIST:
+            case ZoneTree::SUCCESS:
+            case ZoneTree::ALREADYEXISTS:
                 break;
                 break;
             // Can Not Happen
             // Can Not Happen
             default:
             default:
@@ -85,12 +85,12 @@ struct ZoneTable::ZoneTableImpl {
                 break;
                 break;
             // We have no data there, so translate the pointer to NULL as well
             // We have no data there, so translate the pointer to NULL as well
             case ZoneTree::NOTFOUND:
             case ZoneTree::NOTFOUND:
-                return (FindResult(result::NOTFOUND, ConstZonePtr()));
+                return (FindResult(result::NOTFOUND, ZonePtr()));
             // Can Not Happen
             // Can Not Happen
             default:
             default:
                 assert(0);
                 assert(0);
                 // Because of warning
                 // Because of warning
-                return (FindResult(result::NOTFOUND, ConstZonePtr()));
+                return (FindResult(result::NOTFOUND, ZonePtr()));
         }
         }
 
 
         // Can Not Happen (remember, NOTFOUND is handled)
         // Can Not Happen (remember, NOTFOUND is handled)

+ 2 - 2
src/lib/datasrc/zonetable.h

@@ -41,11 +41,11 @@ namespace datasrc {
 class ZoneTable {
 class ZoneTable {
 public:
 public:
     struct FindResult {
     struct FindResult {
-        FindResult(result::Result param_code, const ConstZonePtr param_zone) :
+        FindResult(result::Result param_code, const ZonePtr param_zone) :
             code(param_code), zone(param_zone)
             code(param_code), zone(param_zone)
         {}
         {}
         const result::Result code;
         const result::Result code;
-        const ConstZonePtr zone;
+        const ZonePtr zone;
     };
     };
     ///
     ///
     /// \name Constructors and Destructor.
     /// \name Constructors and Destructor.

+ 2 - 2
src/lib/log/dummylog.cc

@@ -24,8 +24,8 @@ namespace log {
 bool denabled = false;
 bool denabled = false;
 string dprefix;
 string dprefix;
 
 
-void dlog(const string& message) {
-    if (denabled) {
+void dlog(const string& message,bool error_flag) {
+    if (denabled || error_flag) {
         if (!dprefix.empty()) {
         if (!dprefix.empty()) {
             cerr << "[" << dprefix << "] ";
             cerr << "[" << dprefix << "] ";
         }
         }

+ 1 - 1
src/lib/log/dummylog.h

@@ -52,7 +52,7 @@ extern std::string dprefix;
  * @param message The message to log. The real interface will probably have
  * @param message The message to log. The real interface will probably have
  *     more parameters.
  *     more parameters.
  */
  */
-void dlog(const std::string& message);
+void dlog(const std::string& message, bool error_flag=false);
 
 
 }
 }
 }
 }

+ 1 - 1
src/lib/nsas/README

@@ -2,6 +2,6 @@ For an overview of the Nameserver Address Store, see the requirements and design
 documents at http://bind10.isc.org/wiki/Resolver.
 documents at http://bind10.isc.org/wiki/Resolver.
 
 
 At the time of writing (19 October 2010), the file asiolink.h is present in this
 At the time of writing (19 October 2010), the file asiolink.h is present in this
-directory only for the purposes of development.  When the recursor's
+directory only for the purposes of development.  When the resolver's
 asynchronous I/O code has been finished, this will be removed and the NSAS will
 asynchronous I/O code has been finished, this will be removed and the NSAS will
 use the "real" code.
 use the "real" code.

+ 1 - 1
src/lib/nsas/asiolink.h

@@ -24,7 +24,7 @@ namespace asiolink {
 
 
 /// \brief IO Address Dummy Class
 /// \brief IO Address Dummy Class
 ///
 ///
-/// As part of ther recursor, Evan has written the asiolink.h file, which
+/// As part of ther resolver, Evan has written the asiolink.h file, which
 /// encapsulates some of the boost::asio classes.  Until these are checked
 /// encapsulates some of the boost::asio classes.  Until these are checked
 /// into trunk and merged with this branch, these dummy classes should fulfill
 /// into trunk and merged with this branch, these dummy classes should fulfill
 /// their function.
 /// their function.

+ 1 - 1
src/lib/python/isc/config/ccsession.py

@@ -285,7 +285,7 @@ class ModuleCCSession(ConfigData):
         if answer:
         if answer:
             rcode, value = parse_answer(answer)
             rcode, value = parse_answer(answer)
             if rcode == 0:
             if rcode == 0:
-                if value != None and self.get_module_spec().validate_config(False, value):
+                if value != None and module_spec.validate_config(False, value):
                     module_cfg.set_local_config(value);
                     module_cfg.set_local_config(value);
 
 
         # all done, add it
         # all done, add it

+ 25 - 27
src/lib/python/isc/config/cfgmgr.py

@@ -26,6 +26,7 @@ import os
 import copy
 import copy
 import tempfile
 import tempfile
 import json
 import json
+import errno
 from isc.cc import data
 from isc.cc import data
 from isc.config import ccsession, config_data
 from isc.config import ccsession, config_data
 
 
@@ -87,7 +88,12 @@ class ConfigManagerData:
             else:
             else:
                 raise ConfigManagerDataReadError("No version information in configuration file " + config.db_filename)
                 raise ConfigManagerDataReadError("No version information in configuration file " + config.db_filename)
         except IOError as ioe:
         except IOError as ioe:
-            raise ConfigManagerDataEmpty("No configuration file found")
+            # if IOError is 'no such file or directory', then continue
+            # (raise empty), otherwise fail (raise error)
+            if ioe.errno == errno.ENOENT:
+                raise ConfigManagerDataEmpty("No configuration file found")
+            else:
+                raise ConfigManagerDataReadError("Can't read configuration file: " + str(ioe))
         except ValueError:
         except ValueError:
             raise ConfigManagerDataReadError("Configuration file out of date or corrupt, please update or remove " + config.db_filename)
             raise ConfigManagerDataReadError("Configuration file out of date or corrupt, please update or remove " + config.db_filename)
         finally:
         finally:
@@ -270,16 +276,15 @@ class ConfigManager:
         else:
         else:
             return ccsession.create_answer(0, self.config.data)
             return ccsession.create_answer(0, self.config.data)
 
 
-    def _handle_set_config_module(self, cmd):
+    def _handle_set_config_module(self, module_name, cmd):
         # the answer comes (or does not come) from the relevant module
         # the answer comes (or does not come) from the relevant module
         # so we need a variable to see if we got it
         # so we need a variable to see if we got it
         answer = None
         answer = None
         # todo: use api (and check the data against the definition?)
         # todo: use api (and check the data against the definition?)
         old_data = copy.deepcopy(self.config.data)
         old_data = copy.deepcopy(self.config.data)
-        module_name = cmd[0]
         conf_part = data.find_no_exc(self.config.data, module_name)
         conf_part = data.find_no_exc(self.config.data, module_name)
         if conf_part:
         if conf_part:
-            data.merge(conf_part, cmd[1])
+            data.merge(conf_part, cmd)
             update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
             update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
                                                   conf_part)
                                                   conf_part)
             seq = self.cc.group_sendmsg(update_cmd, module_name)
             seq = self.cc.group_sendmsg(update_cmd, module_name)
@@ -289,7 +294,7 @@ class ConfigManager:
                 answer = ccsession.create_answer(1, "Timeout waiting for answer from " + module_name)
                 answer = ccsession.create_answer(1, "Timeout waiting for answer from " + module_name)
         else:
         else:
             conf_part = data.set(self.config.data, module_name, {})
             conf_part = data.set(self.config.data, module_name, {})
-            data.merge(conf_part[module_name], cmd[1])
+            data.merge(conf_part[module_name], cmd)
             # send out changed info
             # send out changed info
             update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
             update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
                                                   conf_part[module_name])
                                                   conf_part[module_name])
@@ -309,29 +314,21 @@ class ConfigManager:
 
 
     def _handle_set_config_all(self, cmd):
     def _handle_set_config_all(self, cmd):
         old_data = copy.deepcopy(self.config.data)
         old_data = copy.deepcopy(self.config.data)
-        data.merge(self.config.data, cmd[0])
-        # send out changed info
         got_error = False
         got_error = False
         err_list = []
         err_list = []
-        for module in self.config.data:
-            if module != "version" and \
-               (module not in old_data or self.config.data[module] != old_data[module]):
-                update_cmd = ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
-                                                      self.config.data[module])
-                seq = self.cc.group_sendmsg(update_cmd, module)
-                try:
-                    answer, env = self.cc.group_recvmsg(False, seq)
-                    if answer == None:
-                        got_error = True
-                        err_list.append("No answer message from " + module)
-                    else:
-                        rcode, val = ccsession.parse_answer(answer)
-                        if rcode != 0:
-                            got_error = True
-                            err_list.append(val)
-                except isc.cc.SessionTimeout:
+        # The format of the command is a dict with module->newconfig
+        # sets, so we simply call set_config_module for each of those
+        for module in cmd:
+            if module != "version":
+                answer = self._handle_set_config_module(module, cmd[module])
+                if answer == None:
                     got_error = True
                     got_error = True
-                    err_list.append("CC Timeout waiting on answer message from " + module)
+                    err_list.append("No answer message from " + module)
+                else:
+                    rcode, val = ccsession.parse_answer(answer)
+                    if rcode != 0:
+                        got_error = True
+                        err_list.append(val)
         if not got_error:
         if not got_error:
             self.write_config()
             self.write_config()
             return ccsession.create_answer(0)
             return ccsession.create_answer(0)
@@ -343,12 +340,13 @@ class ConfigManager:
     def _handle_set_config(self, cmd):
     def _handle_set_config(self, cmd):
         """Private function that handles the 'set_config' command"""
         """Private function that handles the 'set_config' command"""
         answer = None
         answer = None
+
         if cmd == None:
         if cmd == None:
             return ccsession.create_answer(1, "Wrong number of arguments")
             return ccsession.create_answer(1, "Wrong number of arguments")
         if len(cmd) == 2:
         if len(cmd) == 2:
-            answer = self._handle_set_config_module(cmd)
+            answer = self._handle_set_config_module(cmd[0], cmd[1])
         elif len(cmd) == 1:
         elif len(cmd) == 1:
-            answer = self._handle_set_config_all(cmd)
+            answer = self._handle_set_config_all(cmd[0])
         else:
         else:
             answer = ccsession.create_answer(1, "Wrong number of arguments")
             answer = ccsession.create_answer(1, "Wrong number of arguments")
         if not answer:
         if not answer:

+ 13 - 1
src/lib/python/isc/config/tests/ccsession_test.py

@@ -530,7 +530,19 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertTrue("Spec2" in fake_session.subscriptions)
         self.assertTrue("Spec2" in fake_session.subscriptions)
         mccs = None
         mccs = None
         self.assertFalse("Spec2" in fake_session.subscriptions)
         self.assertFalse("Spec2" in fake_session.subscriptions)
-        
+
+    def test_remote_module_with_custom_config(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        # override the default config value for "item1".  add_remote_config()
+        # should incorporate the overridden value, and we should be abel to
+        # get it via get_remote_config_value().
+        fake_session.group_sendmsg({'result': [0, {"item1": 10}]}, 'Spec2')
+        rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+        value, default = mccs.get_remote_config_value(rmodname, "item1")
+        self.assertEqual(10, value)
+        self.assertEqual(False, default)
+
     def test_ignore_command_remote_module(self):
     def test_ignore_command_remote_module(self):
         # Create a Spec1 module and subscribe to remote config for Spec2
         # Create a Spec1 module and subscribe to remote config for Spec2
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()

+ 60 - 0
src/lib/python/isc/config/tests/cfgmgr_test.py

@@ -288,6 +288,66 @@ class TestConfigManager(unittest.TestCase):
                                 },
                                 },
                                 {'result': [0]})
                                 {'result': [0]})
 
 
+    def test_set_config_all(self):
+        my_ok_answer = { 'result': [ 0 ] }
+
+        self.assertEqual({"version": 2}, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value1": 123 }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value1": 123 }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value1": 124 }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value1": 124 }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value2": True }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value1": 124,
+                                    "value2": True
+                                  }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value3": [ 1, 2, 3 ] }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value1": 124,
+                                    "value2": True,
+                                    "value3": [ 1, 2, 3 ]
+                                  }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value2": False }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value1": 124,
+                                    "value2": False,
+                                    "value3": [ 1, 2, 3 ]
+                                  }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value1": None }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value2": False,
+                                    "value3": [ 1, 2, 3 ]
+                                  }
+                         }, self.cm.config.data)
+
+        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
+        self.cm._handle_set_config_all({"test": { "value3": [ 1 ] }})
+        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
+                          "test": { "value2": False,
+                                    "value3": [ 1 ]
+                                  }
+                         }, self.cm.config.data)
+
+
     def test_run(self):
     def test_run(self):
         self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")

+ 102 - 68
src/lib/python/isc/datasrc/sqlite3_ds.py

@@ -25,25 +25,24 @@ RR_NAME_INDEX = 2
 RR_TTL_INDEX = 4
 RR_TTL_INDEX = 4
 RR_RDATA_INDEX = 7
 RR_RDATA_INDEX = 7
 
 
-#########################################################################
-# define exceptions
-#########################################################################
 class Sqlite3DSError(Exception):
 class Sqlite3DSError(Exception):
+    """ Define exceptions."""
     pass
     pass
 
 
-#########################################################################
-# create: set up schema for a newly created zones/records database
-#########################################################################
 def create(cur):
 def create(cur):
-    """Create new zone database"""
+    """ Set up schema for a newly created zones/records database.
+
+    Arguments:
+        cur - sqlite3 cursor.
+    """
     cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
     cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
     cur.execute("INSERT INTO schema_version VALUES (1)")
     cur.execute("INSERT INTO schema_version VALUES (1)")
-    cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY, 
+    cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
                    name STRING NOT NULL COLLATE NOCASE,
                    name STRING NOT NULL COLLATE NOCASE,
-                   rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN', 
+                   rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
                    dnssec BOOLEAN NOT NULL DEFAULT 0)""")
                    dnssec BOOLEAN NOT NULL DEFAULT 0)""")
     cur.execute("CREATE INDEX zones_byname ON zones (name)")
     cur.execute("CREATE INDEX zones_byname ON zones (name)")
-    cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY, 
+    cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
                    zone_id INTEGER NOT NULL,
                    zone_id INTEGER NOT NULL,
                    name STRING NOT NULL COLLATE NOCASE,
                    name STRING NOT NULL COLLATE NOCASE,
                    rname STRING NOT NULL COLLATE NOCASE,
                    rname STRING NOT NULL COLLATE NOCASE,
@@ -53,7 +52,7 @@ def create(cur):
                    rdata STRING NOT NULL)""")
                    rdata STRING NOT NULL)""")
     cur.execute("CREATE INDEX records_byname ON records (name)")
     cur.execute("CREATE INDEX records_byname ON records (name)")
     cur.execute("CREATE INDEX records_byrname ON records (rname)")
     cur.execute("CREATE INDEX records_byrname ON records (rname)")
-    cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY, 
+    cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
                    zone_id INTEGER NOT NULL,
                    zone_id INTEGER NOT NULL,
                    hash STRING NOT NULL COLLATE NOCASE,
                    hash STRING NOT NULL COLLATE NOCASE,
                    owner STRING NOT NULL COLLATE NOCASE,
                    owner STRING NOT NULL COLLATE NOCASE,
@@ -62,17 +61,17 @@ def create(cur):
                    rdata STRING NOT NULL)""")
                    rdata STRING NOT NULL)""")
     cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
     cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
 
 
-#########################################################################
-# open: open a database.  if the database is not yet set up, 
-# call create to do so.
-# input:
-#   dbfile - the filename for the sqlite3 database
-# returns:
-#   sqlite3 connection, sqlite3 cursor
-#########################################################################
 def open(dbfile):
 def open(dbfile):
-    """Open the database file.  If necessary, set it up"""
-    try: 
+    """ Open a database, if the database is not yet set up, call create
+    to do so. It may raise Sqlite3DSError if failed to open sqlite3
+    database file or find bad database schema version in the database.
+
+    Arguments:
+        dbfile - the filename for the sqlite3 database.
+
+    Return sqlite3 connection, sqlite3 cursor.
+    """
+    try:
         conn = sqlite3.connect(dbfile)
         conn = sqlite3.connect(dbfile)
         cur = conn.cursor()
         cur = conn.cursor()
     except Exception as e:
     except Exception as e:
@@ -93,12 +92,15 @@ def open(dbfile):
 
 
     return conn, cur
     return conn, cur
 
 
-#########################################################################
-# get_zone_datas
-#   a generator function producing an iterable set of 
-#   the records in the zone with the given zone name.
-#########################################################################
+
 def get_zone_datas(zonename, dbfile):
 def get_zone_datas(zonename, dbfile):
+    """ A generator function producing an iterable set of
+    the records in the zone with the given zone name.
+
+    Arguments:
+        zonename - the zone's origin name.
+        dbfile - the filename for the sqlite3 database.
+    """
     conn, cur = open(dbfile)
     conn, cur = open(dbfile)
     zone_id = get_zoneid(zonename, cur)
     zone_id = get_zoneid(zonename, cur)
 
 
@@ -112,12 +114,14 @@ def get_zone_datas(zonename, dbfile):
     conn.close()
     conn.close()
 
 
 
 
-#########################################################################
-# get_zone_soa
-#   returns the soa record of the zone with the given zone name. 
-#   If the zone doesn't exist, return None. 
-#########################################################################
 def get_zone_soa(zonename, dbfile):
 def get_zone_soa(zonename, dbfile):
+    """Return the soa record of the zone with the given zone name.
+    If the zone doesn't exist, return None.
+
+    Arguments:
+        zonename - the zone's origin name.
+        dbfile - the filename for the sqlite3 database.
+    """
     conn, cur = open(dbfile)
     conn, cur = open(dbfile)
     id = get_zoneid(zonename, cur)
     id = get_zoneid(zonename, cur)
     cur.execute("SELECT * FROM records WHERE zone_id = ? and rdtype = ?", [id, 'SOA'])
     cur.execute("SELECT * FROM records WHERE zone_id = ? and rdtype = ?", [id, 'SOA'])
@@ -128,16 +132,20 @@ def get_zone_soa(zonename, dbfile):
     return datas
     return datas
 
 
 
 
-#########################################################################
-# get_zone_rrset
-#   returns the rrset of the zone with the given zone name, rrset name 
-#   and given rd type. 
-#   If the zone doesn't exist or rd type doesn't exist, return an empty list. 
-#########################################################################
 def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
 def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
+    """Return the rrset of the zone with the given zone name, rrset
+    name and given RR type. If the zone doesn't exist or RR type
+    doesn't exist, return an empty list.
+
+    Arguments:
+        zonename - the zone's origin name.
+        rr_name - rr name.
+        rdtype - RR type.
+        dbfile - the filename for the sqlite3 database.
+    """
     conn, cur = open(dbfile)
     conn, cur = open(dbfile)
     id = get_zoneid(zonename, cur)
     id = get_zoneid(zonename, cur)
-    cur.execute("SELECT * FROM records WHERE name = ? and zone_id = ? and rdtype = ?", 
+    cur.execute("SELECT * FROM records WHERE name = ? and zone_id = ? and rdtype = ?",
                 [rr_name, id, rdtype])
                 [rr_name, id, rdtype])
     datas = cur.fetchall()
     datas = cur.fetchall()
     cur.close()
     cur.close()
@@ -145,12 +153,13 @@ def get_zone_rrset(zonename, rr_name, rdtype, dbfile):
     return datas
     return datas
 
 
 
 
-#########################################################################
-# get_zones_info:
-#   returns all the zones' information.
-#########################################################################
-def get_zones_info(db_file):
-    conn, cur = open(db_file)
+def get_zones_info(dbfile):
+    """ Return all the zones' information in the database.
+
+    Arguments:
+        dbfile - the filename for the sqlite3 database.
+    """
+    conn, cur = open(dbfile)
     cur.execute("SELECT name, rdclass FROM zones")
     cur.execute("SELECT name, rdclass FROM zones")
     info = cur.fetchone()
     info = cur.fetchone()
     while info:
     while info:
@@ -160,44 +169,69 @@ def get_zones_info(db_file):
     cur.close()
     cur.close()
     conn.close()
     conn.close()
 
 
-#########################################################################
-# get_zoneid:
-#   returns the zone_id for a given zone name, or an empty
-#   string if the zone is not found
-#########################################################################
-def get_zoneid(zone, cur):
-    cur.execute("SELECT id FROM zones WHERE name = ?", [zone])
+
+def get_zoneid(zonename, cur):
+    """ Get the zone_id for a given zone name.
+
+    Arguments:
+        zonename - the zone's origin name.
+        cur - sqlite3 cursor.
+
+    Return zone id for the given zone name, or an empty string if the
+    zone is not found.
+    """
+    cur.execute("SELECT id FROM zones WHERE name = ?", [zonename])
     row = cur.fetchone()
     row = cur.fetchone()
     if row:
     if row:
         return row[0]
         return row[0]
     else:
     else:
         return ''
         return ''
-    
-#########################################################################
-# reverse_name:
-#   reverse the labels of a DNS name.  (for example,
-#   "bind10.isc.org." would become "org.isc.bind10.")
-#########################################################################
+
+
+def zone_exist(zonename, dbfile):
+    """ Search for the zone with the given zone name in databse. This
+    method may throw a Sqlite3DSError exception because its underlying
+    method open() can throw that exception.
+
+    Arguments:
+        zonename - the zone's origin name.
+        dbfile - the filename for the sqlite3 database.
+
+    Return True if the zone is found, otherwise False.
+    """
+    conn, cur = open(dbfile)
+    zoneid = get_zoneid(zonename, cur)
+    cur.close()
+    conn.close()
+    if zoneid:
+        return True
+    return False
+
+
 def reverse_name(name):
 def reverse_name(name):
     """Reverse the labels of a domain name; for example,
     """Reverse the labels of a domain name; for example,
-    given 'www.isc.org.', return 'org.isc.www.'  This is needed
-    for DNSSEC sort order."""
+    given 'www.example.org.', return 'org.example.www.'  This is needed
+    for DNSSEC sort order.
+
+    Arguments:
+        name - the DNS name will be reversed.
+    """
     new = name.split('.')
     new = name.split('.')
     new.reverse()
     new.reverse()
     if new[0] == '':
     if new[0] == '':
         new.pop(0)
         new.pop(0)
     return '.'.join(new)+'.'
     return '.'.join(new)+'.'
 
 
-#########################################################################
-# load:
-#   load a zone into the SQL database.
-# input:
-#   dbfile: the sqlite3 database fileanme
-#   zone: the zone origin
-#   reader: a generator function producing an iterable set of
-#           name/ttl/class/rrtype/rdata-text tuples
-#########################################################################
+
 def load(dbfile, zone, reader):
 def load(dbfile, zone, reader):
+    """  Load a zone into the SQL database.
+
+    Arguments:
+        dbfile - the sqlite3 database filename
+        zone - the zone origin
+        reader - a generator function producing an iterable set of
+        name/ttl/class/rrtype/rdata-text tuples.
+    """
     # if the zone name doesn't contain the trailing dot, automatically add it.
     # if the zone name doesn't contain the trailing dot, automatically add it.
     if zone[-1] != '.':
     if zone[-1] != '.':
         zone += '.'
         zone += '.'

+ 6 - 2
src/lib/python/isc/datasrc/tests/Makefile.am

@@ -1,16 +1,20 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = master_test.py
+PYTESTS = master_test.py sqlite3_ds_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
+EXTRA_DIST += testdata/brokendb.sqlite3
+EXTRA_DIST += testdata/example.com.sqlite3
+
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
 if ENABLE_PYTHON_COVERAGE
 if ENABLE_PYTHON_COVERAGE
-	touch $(abs_top_srcdir)/.coverage 
+	touch $(abs_top_srcdir)/.coverage
 	rm -f .coverage
 	rm -f .coverage
 	${LN_S} $(abs_top_srcdir)/.coverage .coverage
 	${LN_S} $(abs_top_srcdir)/.coverage .coverage
 endif
 endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
+	TESTDATA_PATH=$(abs_srcdir)/testdata \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 43 - 0
src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py

@@ -0,0 +1,43 @@
+# Copyright (C) 2011  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.
+
+from isc.datasrc import sqlite3_ds
+import os
+import socket
+import unittest
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+
+class TestSqlite3_ds(unittest.TestCase):
+    def test_zone_exist(self):
+        # The following file must be non existent and must be non
+        # "creatable"; the sqlite3 library will try to create a new
+        # DB file if it doesn't exist, so to test a failure case the
+        # create operation should also fail. The "nodir", a non
+        # existent directory, is inserted for this purpose.
+        nodir = "/nodir/notexist"
+        self.assertRaises(sqlite3_ds.Sqlite3DSError,
+                          sqlite3_ds.zone_exist, "example.com", nodir)
+        # Open a broken database file
+        self.assertRaises(sqlite3_ds.Sqlite3DSError,
+                          sqlite3_ds.zone_exist, "example.com",
+                          TESTDATA_PATH + "brokendb.sqlite3")
+        self.assertTrue(sqlite3_ds.zone_exist("example.com.",
+                            TESTDATA_PATH + "example.com.sqlite3"))
+        self.assertFalse(sqlite3_ds.zone_exist("example.org.",
+                            TESTDATA_PATH + "example.com.sqlite3"))
+
+if __name__ == '__main__':
+    unittest.main()

BIN
src/lib/python/isc/datasrc/tests/testdata/brokendb.sqlite3


BIN
src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3


+ 7 - 1
src/lib/testutils/testdata/Makefile.am

@@ -1,4 +1,4 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
 
 
 BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -25,5 +25,11 @@ EXTRA_DIST += example.com.zone example.net.zone example.org.zone example.zone
 EXTRA_DIST += example.com
 EXTRA_DIST += example.com
 EXTRA_DIST += example.sqlite3
 EXTRA_DIST += example.sqlite3
 
 
+EXTRA_DIST += test1.zone.in
+EXTRA_DIST += test1-new.zone.in
+EXTRA_DIST += test1-broken.zone.in
+EXTRA_DIST += test2.zone.in
+EXTRA_DIST += test2-new.zone.in
+
 .spec.wire:
 .spec.wire:
 	$(abs_top_builddir)/src/lib/dns/tests/testdata/gen-wiredata.py -o $@ $<
 	$(abs_top_builddir)/src/lib/dns/tests/testdata/gen-wiredata.py -o $@ $<

+ 5 - 0
src/lib/testutils/testdata/test1-broken.zone.in

@@ -0,0 +1,5 @@
+test1.example.    3600    IN  SOA ns.test1.example. admin.test1.example. 1241 3600 1800 2419200 7200
+test1.example.    3600    IN  NS ns.test1.example.
+ns.test1.example.	3600    IN  A 192.0.2.1
+;; out-of-zone RR
+www.example.com.	3600    IN  AAAA 2001:db8::80

+ 4 - 0
src/lib/testutils/testdata/test1-new.zone.in

@@ -0,0 +1,4 @@
+test1.example.    3600    IN  SOA ns.test1.example. admin.test1.example. 1238 3600 1800 2419200 7200
+test1.example.    3600    IN  NS ns.test1.example.
+ns.test1.example.	3600    IN  A 192.0.2.1
+ns.test1.example.	3600    IN  AAAA 2001:db8::1

+ 3 - 0
src/lib/testutils/testdata/test1.zone.in

@@ -0,0 +1,3 @@
+test1.example.    3600    IN  SOA ns.test1.example. admin.test1.example. 1235 3600 1800 2419200 7200
+test1.example.    3600    IN  NS ns.test1.example.
+ns.test1.example.	3600    IN  A 192.0.2.1

+ 4 - 0
src/lib/testutils/testdata/test2-new.zone.in

@@ -0,0 +1,4 @@
+test2.example.    3600    IN  SOA ns.test2.example. admin.test2.example. 1242 3600 1800 2419200 7200
+test2.example.    3600    IN  NS ns.test2.example.
+ns.test2.example.	3600    IN  A 192.0.2.2
+ns.test2.example.	3600    IN  AAAA 2001:db8::2

+ 3 - 0
src/lib/testutils/testdata/test2.zone.in

@@ -0,0 +1,3 @@
+test2.example.    3600    IN  SOA ns.test2.example. admin.test2.example. 1238 3600 1800 2419200 7200
+test2.example.    3600    IN  NS ns.test2.example.
+ns.test2.example.	3600    IN  A 192.0.2.2