Browse Source

sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac448@4131 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
006c32f46d
84 changed files with 3054 additions and 417 deletions
  1. 30 0
      ChangeLog
  2. 37 8
      Makefile.am
  3. 40 11
      README
  4. 23 2
      configure.ac
  5. 2 3
      doc/guide/bind10-guide.xml
  6. 1 0
      src/bin/auth/Makefile.am
  7. 5 0
      src/bin/auth/auth.spec.pre.in
  8. 53 25
      src/bin/auth/auth_srv.cc
  9. 47 0
      src/bin/auth/auth_srv.h
  10. 1 0
      src/bin/auth/benchmarks/Makefile.am
  11. 145 43
      src/bin/auth/benchmarks/query_bench.cc
  12. 10 5
      src/bin/auth/config.cc
  13. 41 1
      src/bin/auth/main.cc
  14. 22 2
      src/bin/auth/query.cc
  15. 36 2
      src/bin/auth/query.h
  16. 162 0
      src/bin/auth/statistics.cc
  17. 146 0
      src/bin/auth/statistics.h
  18. 2 0
      src/bin/auth/tests/Makefile.am
  19. 200 0
      src/bin/auth/tests/auth_srv_unittest.cc
  20. 72 22
      src/bin/auth/tests/config_unittest.cc
  21. 51 11
      src/bin/auth/tests/query_unittest.cc
  22. 215 0
      src/bin/auth/tests/statistics_unittest.cc
  23. 26 0
      src/bin/auth/tests/testdata/Makefile.am
  24. 10 0
      src/bin/auth/tests/testdata/badExampleQuery_fromWire.spec
  25. 8 0
      src/bin/auth/tests/testdata/example.com
  26. BIN
      src/bin/auth/tests/testdata/example.sqlite3
  27. 9 0
      src/bin/auth/tests/testdata/examplequery_fromWire.spec
  28. 9 0
      src/bin/auth/tests/testdata/iqueryresponse_fromWire.spec
  29. 12 0
      src/bin/auth/tests/testdata/multiquestion_fromWire.spec
  30. 12 0
      src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec
  31. 10 0
      src/bin/auth/tests/testdata/shortanswer_fromWire.spec
  32. 9 0
      src/bin/auth/tests/testdata/shortmessage_fromWire
  33. 13 0
      src/bin/auth/tests/testdata/shortquestion_fromWire
  34. 13 0
      src/bin/auth/tests/testdata/shortresponse_fromWire
  35. 8 0
      src/bin/auth/tests/testdata/simplequery_fromWire.spec
  36. 8 0
      src/bin/auth/tests/testdata/simpleresponse_fromWire.spec
  37. 8 3
      src/bin/bind10/tests/Makefile.am
  38. 7 3
      src/bin/bindctl/tests/Makefile.am
  39. 7 3
      src/bin/cfgmgr/tests/Makefile.am
  40. 7 3
      src/bin/cmdctl/tests/Makefile.am
  41. 3 8
      src/bin/loadzone/tests/correct/Makefile.am
  42. 3 9
      src/bin/loadzone/tests/error/Makefile.am
  43. 7 3
      src/bin/msgq/tests/Makefile.am
  44. 3 53
      src/bin/recurse/b10-recurse.8
  45. 1 62
      src/bin/recurse/b10-recurse.xml
  46. 7 3
      src/bin/stats/tests/Makefile.am
  47. 7 3
      src/bin/tests/Makefile.am
  48. 7 3
      src/bin/xfrin/tests/Makefile.am
  49. 7 3
      src/bin/xfrout/tests/Makefile.am
  50. 7 4
      src/bin/zonemgr/tests/Makefile.am
  51. 87 0
      src/lib/asiolink/asiolink.cc
  52. 95 1
      src/lib/asiolink/asiolink.h
  53. 247 0
      src/lib/asiolink/tests/asiolink_unittest.cc
  54. 198 3
      src/lib/datasrc/memory_datasrc.cc
  55. 62 1
      src/lib/datasrc/memory_datasrc.h
  56. 219 73
      src/lib/datasrc/rbtree.h
  57. 255 5
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  58. 85 0
      src/lib/datasrc/tests/rbtree_unittest.cc
  59. 4 0
      src/lib/datasrc/tests/testdata/duplicate_rrset.zone
  60. 30 2
      src/lib/datasrc/zone.h
  61. 2 0
      src/lib/datasrc/zonetable.cc
  62. 1 1
      src/lib/datasrc/zonetable.h
  63. 7 3
      src/lib/dns/python/tests/Makefile.am
  64. 8 3
      src/lib/python/isc/cc/tests/Makefile.am
  65. 10 9
      src/lib/python/isc/config/module_spec.py
  66. 7 3
      src/lib/python/isc/config/tests/Makefile.am
  67. 4 0
      src/lib/python/isc/config/tests/module_spec_test.py
  68. 7 3
      src/lib/python/isc/datasrc/tests/Makefile.am
  69. 7 3
      src/lib/python/isc/log/tests/Makefile.am
  70. 7 3
      src/lib/python/isc/net/tests/Makefile.am
  71. 7 3
      src/lib/python/isc/notify/tests/Makefile.am
  72. 7 3
      src/lib/python/isc/util/tests/Makefile.am
  73. 5 0
      src/lib/python/isc/utils/Makefile.am
  74. 0 0
      src/lib/python/isc/utils/__init__.py
  75. 37 0
      src/lib/python/isc/utils/process.py
  76. 16 0
      src/lib/python/isc/utils/tests/Makefile.am
  77. 39 0
      src/lib/python/isc/utils/tests/process_test.py
  78. 3 0
      src/lib/testutils/testdata/Makefile.am
  79. 3 0
      src/lib/testutils/testdata/example.com.zone
  80. 3 0
      src/lib/testutils/testdata/example.net.zone
  81. 3 0
      src/lib/testutils/testdata/example.org.zone
  82. 3 0
      src/lib/testutils/testdata/example.zone
  83. 8 0
      src/lib/testutils/testdata/iquery_fromWire.spec
  84. 9 0
      src/lib/testutils/testdata/iquery_response_fromWire.spec

+ 30 - 0
ChangeLog

@@ -1,3 +1,33 @@
+  142.	[func]		jinmei
+	b10-auth: updated query benchmark so that it can test in memory
+	data source.  Also fixed a bug that the output buffer isn't
+	cleared after query processing, resulting in misleading results
+	or program crash.  This is a regression due to change #135.
+	(Trac #465, svn r4103)
+
+  141.	[bug]		jinmei
+	b10-auth: Fixed a bug that the authoritative server includes
+	trailing garbage data in responses.  This is a regression due to
+	change #135. (Trac #462, svn r4081)
+
+  140.  [func]		y-aharen
+	src/bin/auth: Added a feature to count queries and send counter
+	values to statistics periodically. To support it, added wrapping
+	class of asio::deadline_timer to use as interval timer.
+	The counters can be seen using the "Stats show" command from
+	bindctl.  The result would look like:
+	  ... "auth.queries.tcp": 1, "auth.queries.udp": 1 ...
+	Using the "Auth sendstats" command you can make b10-auth send the
+	counters to b10-stats immediately.
+	(Trac #347, svn r4026)
+
+  139.  [build]		jreed
+	Introduced configure option and make targets for generating
+	Python code coverage report. This adds new make targets:
+	report-python-coverage and clean-python-coverage. The C++
+	code coverage targets were renamed to clean-cpp-coverage
+	and report-cpp-coverage. (Trac #362, svn r4023)
+
   138.	[func]*		jinmei
   138.	[func]*		jinmei
 	b10-auth: added a configuration interface to support in memory
 	b10-auth: added a configuration interface to support in memory
 	data sources.  For example, the following command to bindctl
 	data sources.  For example, the following command to bindctl

File diff suppressed because it is too large
+ 37 - 8
Makefile.am


+ 40 - 11
README

@@ -15,11 +15,11 @@ 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-cmdctl remote control daemon, b10-cfgmgr configuration manager,
-b10-xfrin AXFR inbound service, b10-xfrout outgoing AXFR service,
-b10-zonemgr secondary manager, b10-stats statistics collection and
-reporting daemon, and a new libdns++ library for C++ with a python
-wrapper.
+b10-recurse forwarding DNS server, b10-cmdctl remote control daemon,
+b10-cfgmgr configuration manager, b10-xfrin AXFR inbound service,
+b10-xfrout outgoing AXFR service, b10-zonemgr secondary manager,
+b10-stats statistics collection and reporting daemon, and a new
+libdns++ library for C++ with a python wrapper.
 
 
 Documentation is included and also available via the BIND 10
 Documentation is included and also available via the BIND 10
 website at http://bind10.isc.org/
 website at http://bind10.isc.org/
@@ -93,26 +93,55 @@ Then run "make check" to run these tests.
 
 
 TEST COVERAGE
 TEST COVERAGE
 
 
+Code coverage reports may be generated using make. These are
+based on running on the unit tests. The resulting reports are placed
+in coverage-cpp-html and coverage-python-html directories for C++
+and Python, respectively.
+
 The code coverage report for the C++ tests uses LCOV. It is available
 The code coverage report for the C++ tests uses LCOV. It is available
-from http://ltp.sourceforge.net/. To generate your own HTML report,
+from http://ltp.sourceforge.net/. To generate the HTML report,
 first configure BIND 10 with:
 first configure BIND 10 with:
  
  
   ./configure --with-lcov
   ./configure --with-lcov
 
 
+The code coverage report for the Python tests uses coverage.py (aka
+pycoverage). It is available from http://nedbatchelder.com/code/coverage/.
+To generate the HTML report, first configure BIND 10 with:
+
+  ./configure --with-pycoverage
+
 Doing code coverage tests:
 Doing code coverage tests:
 
 
   make coverage
   make coverage
-	Does the following:
+	Does the clean, perform, and report targets for C++ and Python.
 
 
   make clean-coverage
   make clean-coverage
-	Zeroes the lcov code coverage counters and removes the coverage HTML.
+	Zeroes the code coverage counters and removes the HTML reports
+	for C++ and Python.
 
 
   make perform-coverage
   make perform-coverage
-	Runs the C++ tests (using googletests framework).
+	Runs the C++ (using the googletests framework) and Python
+	tests.
 
 
   make report-coverage
   make report-coverage
-	Generates the coverage HTML, excluding some unrelated headers.
-	The HTML reports are placed in a directory called coverage/.
+	Generates the coverage reports in HTML for C++ and Python.
+
+  make clean-cpp-coverage
+	Zeroes the code coverage counters and removes the HTML report
+	for the C++ tests.
+
+  make clean-python-coverage
+	Zeroes the code coverage counters and removes the HTML report
+	for the Python tests.
+
+  make report-cpp-coverage
+	Generates the coverage report in HTML for C++, excluding
+	some unrelated headers.  The HTML reports are placed in a
+	directory called coverage-cpp-html/.
+
+  make report-python-coverage
+	Generates the coverage report in HTML for Python. The HTML
+	reports are placed in a directory called coverage-python-html/.
 
 
 DEVELOPERS
 DEVELOPERS
 
 

+ 23 - 2
configure.ac

@@ -287,6 +287,26 @@ AC_TRY_COMPILE([
         AC_DEFINE(HAVE_SA_LEN, 1, [Define to 1 if sockaddr has a sa_len member, and corresponding sin_len and sun_len])],
         AC_DEFINE(HAVE_SA_LEN, 1, [Define to 1 if sockaddr has a sa_len member, and corresponding sin_len and sun_len])],
         AC_MSG_RESULT(no))
         AC_MSG_RESULT(no))
 
 
+AC_ARG_WITH(pycoverage,
+[  --with-pycoverage[=PROGRAM]         enable python code coverage using the specified coverage], pycoverage="$withval", pycoverage="no")
+if test "$pycoverage" = "no" ; then
+	# just run the tests normally with python
+	PYCOVERAGE_RUN="${PYTHON}"
+	USE_PYCOVERAGE="no"
+elif test "$pycoverage" = "yes" ; then
+	PYCOVERAGE="coverage"
+	PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
+	USE_PYCOVERAGE="yes"
+else
+	PYCOVERAGE="$pycoverage"
+	PYCOVERAGE_RUN="${PYCOVERAGE} run --branch --append"
+	USE_PYCOVERAGE="yes"
+fi
+AM_CONDITIONAL(ENABLE_PYTHON_COVERAGE, test x$USE_PYCOVERAGE != xno)
+AC_SUBST(PYCOVERAGE)
+AC_SUBST(PYCOVERAGE_RUN)
+AC_SUBST(USE_PYCOVERAGE)
+
 AC_ARG_WITH(lcov,
 AC_ARG_WITH(lcov,
 [  --with-lcov[=PROGRAM]         enable gtest and coverage target using the specified lcov], lcov="$withval", lcov="no")
 [  --with-lcov[=PROGRAM]         enable gtest and coverage target using the specified lcov], lcov="$withval", lcov="no")
 
 
@@ -351,7 +371,7 @@ if test "${boost_include_path}" ; then
 	BOOST_INCLUDES="-I${boost_include_path}"
 	BOOST_INCLUDES="-I${boost_include_path}"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 fi
 fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp],,
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
   AC_MSG_ERROR([Missing required header files.]))
 CPPFLAGS="$CPPFLAGS_SAVES"
 CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(BOOST_INCLUDES)
 AC_SUBST(BOOST_INCLUDES)
@@ -731,7 +751,8 @@ Features:
 
 
 Developer:
 Developer:
   Google Tests:  $gtest_path
   Google Tests:  $gtest_path
-  Code Coverage: $USE_LCOV
+  C++ Code Coverage: $USE_LCOV
+  Python Code Coverage: $USE_PYCOVERAGE
   Generate Manuals:  $enable_man
   Generate Manuals:  $enable_man
 
 
 END
 END

+ 2 - 3
doc/guide/bind10-guide.xml

@@ -42,9 +42,8 @@
 
 
     <note>
     <note>
       <para>
       <para>
-        BIND 10, at this time, does not provide a recursive
-        DNS server. It does provide a EDNS0- and DNSSEC-capable
-        authoritative DNS server.
+        BIND 10 provides a EDNS0- and DNSSEC-capable
+        authoritative DNS server and a forwarding DNS server.
       </para>
       </para>
     </note>
     </note>
 
 

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

@@ -41,6 +41,7 @@ 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 += common.h
 b10_auth_SOURCES += common.h
+b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 b10_auth_SOURCES += main.cc
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la

+ 5 - 0
src/bin/auth/auth.spec.pre.in

@@ -59,6 +59,11 @@
         "command_name": "shutdown",
         "command_name": "shutdown",
         "command_description": "Shut down authoritative DNS server",
         "command_description": "Shut down authoritative DNS server",
         "command_args": []
         "command_args": []
+      },
+      {
+        "command_name": "sendstats",
+        "command_description": "Send data to a statistics module at once",
+        "command_args": []
       }
       }
     ]
     ]
   }
   }

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

@@ -55,6 +55,7 @@
 #include <auth/config.h>
 #include <auth/config.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
 #include <auth/query.h>
 #include <auth/query.h>
+#include <auth/statistics.h>
 
 
 using namespace std;
 using namespace std;
 
 
@@ -100,6 +101,9 @@ public:
 
 
     /// Hot spot cache
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
     isc::datasrc::HotCache cache_;
+
+    /// Query counters for statistics
+    AuthCounters counters_;
 private:
 private:
     std::string db_file_;
     std::string db_file_;
 
 
@@ -111,6 +115,9 @@ private:
 
 
     bool xfrout_connected_;
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
     AbstractXfroutClient& xfrout_client_;
+
+    /// Increment query counter
+    void incCounter(const int protocol);
 };
 };
 
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -118,6 +125,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()),
+    counters_(verbose_mode_),
     xfrout_connected_(false),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
     xfrout_client_(xfrout_client)
 {
 {
@@ -154,33 +162,20 @@ private:
     AuthSrv* server_;
     AuthSrv* server_;
 };
 };
 
 
-// This is a derived class of \c DNSAnswer, to serve as a
-// callback in the asiolink module.  It takes a completed
-// set of answer data from the DNS lookup and assembles it
-// into a wire-format response.
+// This is a derived class of \c DNSAnswer, to serve as a callback in the
+// asiolink module.  We actually shouldn't do anything in this class because
+// we build complete response messages in the process methods; otherwise
+// the response message will contain trailing garbage.  In future, we should
+// probably even drop the reliance on DNSAnswer.  We don't need the coroutine
+// tricks provided in that framework, and its overhead would be significant
+// in terms of performance consideration for the authoritative server
+// implementation.
 class MessageAnswer : public DNSAnswer {
 class MessageAnswer : public DNSAnswer {
 public:
 public:
-    MessageAnswer(AuthSrv* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage& io_message, MessagePtr message,
-                            OutputBufferPtr buffer) const
-    {
-        MessageRenderer renderer(*buffer);
-        if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-            ConstEDNSPtr edns(message->getEDNS());
-            renderer.setLengthLimit(edns ? edns->getUDPSize() :
-                Message::DEFAULT_MAX_UDPSIZE);
-        } else {
-            renderer.setLengthLimit(65535);
-        }
-        message->toWire(renderer);
-        if (server_->getVerbose()) {
-            cerr << "[b10-auth] sending a response (" << renderer.getLength()
-                 << " bytes):\n" << message->toText() << endl;
-        }
-    }
-
-private:
-    AuthSrv* server_;
+    MessageAnswer(AuthSrv*) {}
+    virtual void operator()(const IOMessage&, MessagePtr,
+                            OutputBufferPtr) const
+    {}
 };
 };
 
 
 // This is a derived class of \c SimpleCallback, to serve
 // This is a derived class of \c SimpleCallback, to serve
@@ -294,6 +289,11 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
     impl_->config_session_ = config_session;
     impl_->config_session_ = config_session;
 }
 }
 
 
+void
+AuthSrv::setStatisticsSession(AbstractSession* statistics_session) {
+    impl_->counters_.setStatisticsSession(statistics_session);
+}
+
 ModuleCCSession*
 ModuleCCSession*
 AuthSrv::getConfigSession() const {
 AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
     return (impl_->config_session_);
@@ -429,6 +429,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
     message->setHeaderFlag(Message::HEADERFLAG_AA);
     message->setHeaderFlag(Message::HEADERFLAG_AA);
     message->setRcode(Rcode::NOERROR());
     message->setRcode(Rcode::NOERROR());
 
 
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (remote_edns) {
     if (remote_edns) {
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         local_edns->setDNSSECAwareness(dnssec_ok);
         local_edns->setDNSSECAwareness(dnssec_ok);
@@ -476,6 +479,9 @@ bool
 AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
 AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
                               OutputBufferPtr buffer)
                               OutputBufferPtr buffer)
 {
 {
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         if (verbose_mode_) {
         if (verbose_mode_) {
             cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
             cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
@@ -601,6 +607,19 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, MessagePtr message,
     return (true);
     return (true);
 }
 }
 
 
+void
+AuthSrvImpl::incCounter(const int protocol) {
+    // Increment query counter.
+    if (protocol == IPPROTO_UDP) {
+        counters_.inc(AuthCounters::COUNTER_UDP_QUERY);
+    } else if (protocol == IPPROTO_TCP) {
+        counters_.inc(AuthCounters::COUNTER_TCP_QUERY);
+    } else {
+        // unknown protocol
+        isc_throw(Unexpected, "Unknown protocol: " << protocol);
+    }
+}
+
 ConstElementPtr
 ConstElementPtr
 AuthSrvImpl::setDbFile(ConstElementPtr config) {
 AuthSrvImpl::setDbFile(ConstElementPtr config) {
     ConstElementPtr answer = isc::config::createAnswer();
     ConstElementPtr answer = isc::config::createAnswer();
@@ -670,3 +689,12 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
         return (isc::config::createAnswer(1, error.what()));
         return (isc::config::createAnswer(1, error.what()));
     }
     }
 }
 }
+
+bool AuthSrv::submitStatistics() const {
+    return (impl_->counters_.submitStatistics());
+}
+
+uint64_t
+AuthSrv::getCounter(const AuthCounters::CounterType type) const {
+    return (impl_->counters_.getCounter(type));
+}

+ 47 - 0
src/bin/auth/auth_srv.h

@@ -27,6 +27,7 @@
 #include <config/ccsession.h>
 #include <config/ccsession.h>
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
+#include <auth/statistics.h>
 
 
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
@@ -62,6 +63,7 @@ class AuthSrvImpl;
 ///
 ///
 /// The design of this class is still in flux.  It's quite likely to change
 /// The design of this class is still in flux.  It's quite likely to change
 /// in future versions.
 /// in future versions.
+///
 class AuthSrv {
 class AuthSrv {
     ///
     ///
     /// \name Constructors, Assignment Operator and Destructor.
     /// \name Constructors, Assignment Operator and Destructor.
@@ -96,6 +98,8 @@ public:
     /// \param message Pointer to the \c Message object
     /// \param message Pointer to the \c Message object
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param server Pointer to the \c DNSServer
     /// \param server Pointer to the \c DNSServer
+    ///
+    /// \throw isc::Unexpected Protocol type of \a message is unexpected
     void processMessage(const asiolink::IOMessage& io_message,
     void processMessage(const asiolink::IOMessage& io_message,
                         isc::dns::MessagePtr message,
                         isc::dns::MessagePtr message,
                         isc::dns::OutputBufferPtr buffer,
                         isc::dns::OutputBufferPtr buffer,
@@ -281,6 +285,49 @@ public:
     void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
     void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
                           MemoryDataSrcPtr memory_datasrc);
                           MemoryDataSrcPtr memory_datasrc);
 
 
+    /// \brief Set the communication session with Statistics.
+    ///
+    /// This function never throws an exception as far as
+    /// AuthCounters::setStatisticsSession() doesn't throw.
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and
+    /// session frameworks, at which point the session will probably
+    /// be passed on construction of the server.
+    ///
+    /// \param statistics_session A Session object over which statistics
+    /// information is exchanged with statistics module.
+    /// The session must be established before setting in the server
+    /// object.
+    /// Ownership isn't transferred: the caller is responsible for keeping
+    /// this object to be valid while the server object is working and for
+    /// disconnecting the session and destroying the object when the server
+    /// is shutdown.
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This function can throw an exception from
+    /// AuthCounters::submitStatistics().
+    ///
+    /// \return true on success, false on failure (e.g. session timeout,
+    /// session error).
+    bool submitStatistics() const;
+
+    /// \brief Get the value of counter in the AuthCounters.
+    /// 
+    /// This function calls AuthCounters::getCounter() and
+    /// returns its return value.
+    ///
+    /// This function never throws an exception as far as
+    /// AuthCounters::getCounter() doesn't throw.
+    /// 
+    /// Note: Currently this function is for testing purpose only.
+    ///
+    /// \param type Type of a counter to get the value of
+    ///
+    /// \return the value of the counter.
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+
 private:
 private:
     AuthSrvImpl* impl_;
     AuthSrvImpl* impl_;
     asiolink::IOService* io_service_;
     asiolink::IOService* io_service_;

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

@@ -11,6 +11,7 @@ query_bench_SOURCES = query_bench.cc
 query_bench_SOURCES += ../query.h  ../query.cc
 query_bench_SOURCES += ../query.h  ../query.cc
 query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../config.h ../config.cc
 query_bench_SOURCES += ../config.h ../config.cc
+query_bench_SOURCES += ../statistics.h ../statistics.cc
 
 
 query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
 query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
 query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la

+ 145 - 43
src/bin/auth/benchmarks/query_bench.cc

@@ -33,6 +33,7 @@
 #include <xfr/xfrout_client.h>
 #include <xfr/xfrout_client.h>
 
 
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
+#include <auth/config.h>
 #include <auth/query.h>
 #include <auth/query.h>
 
 
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
@@ -53,24 +54,25 @@ XfroutClient xfrout_client("dummy_path"); // path doesn't matter
 // Just something to pass as the server to resume
 // Just something to pass as the server to resume
 class DummyServer : public DNSServer {
 class DummyServer : public DNSServer {
     public:
     public:
-        virtual void operator()(asio::error_code, size_t) { }
-        virtual void resume(const bool) { }
+        virtual void operator()(asio::error_code, size_t) {}
+        virtual void resume(const bool) {}
         virtual DNSServer* clone() {
         virtual DNSServer* clone() {
-            return new DummyServer(*this);
+            return (new DummyServer(*this));
         }
         }
 };
 };
 
 
 class QueryBenchMark {
 class QueryBenchMark {
-private:
+protected:
     // Maintain dynamically generated objects via shared pointers because
     // Maintain dynamically generated objects via shared pointers because
     // QueryBenchMark objects will be copied.
     // QueryBenchMark objects will be copied.
     typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
     typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
+private:
     typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
     typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
-public:
-    QueryBenchMark(const int cache_slots, const char* const datasrc_file,
+protected:
+    QueryBenchMark(const bool enable_cache,
                    const BenchQueries& queries, MessagePtr query_message,
                    const BenchQueries& queries, MessagePtr query_message,
                    OutputBufferPtr buffer) :
                    OutputBufferPtr buffer) :
-        server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
+        server_(new AuthSrv(enable_cache, xfrout_client)),
         queries_(queries),
         queries_(queries),
         query_message_(query_message),
         query_message_(query_message),
         buffer_(buffer),
         buffer_(buffer),
@@ -78,13 +80,8 @@ public:
         dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
         dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
                                                         IOAddress("192.0.2.1"),
                                                         IOAddress("192.0.2.1"),
                                                         5300)))
                                                         5300)))
-    {
-        if (cache_slots >= 0) {
-            server_->setCacheSlots(cache_slots);
-        }
-        server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
-                                                string(datasrc_file) + "\"}"));
-    }
+    {}
+public:
     unsigned int run() {
     unsigned int run() {
         BenchQueries::const_iterator query;
         BenchQueries::const_iterator query;
         const BenchQueries::const_iterator query_end = queries_.end();
         const BenchQueries::const_iterator query_end = queries_.end();
@@ -93,14 +90,16 @@ public:
             IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
             IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
                                  *dummy_endpoint);
                                  *dummy_endpoint);
             query_message_->clear(Message::PARSE);
             query_message_->clear(Message::PARSE);
+            buffer_->clear();
             server_->processMessage(io_message, query_message_, buffer_,
             server_->processMessage(io_message, query_message_, buffer_,
-                &server);
+                                    &server);
         }
         }
 
 
         return (queries_.size());
         return (queries_.size());
     }
     }
-private:
+protected:
     AuthSrvPtr server_;
     AuthSrvPtr server_;
+private:
     const BenchQueries& queries_;
     const BenchQueries& queries_;
     MessagePtr query_message_;
     MessagePtr query_message_;
     OutputBufferPtr buffer_;
     OutputBufferPtr buffer_;
@@ -108,26 +107,92 @@ private:
     IOEndpointPtr dummy_endpoint;
     IOEndpointPtr dummy_endpoint;
 };
 };
 
 
+class Sqlite3QueryBenchMark  : public QueryBenchMark {
+public:
+    Sqlite3QueryBenchMark(const int cache_slots,
+                          const char* const datasrc_file,
+                          const BenchQueries& queries,
+                          MessagePtr query_message,
+                          OutputBufferPtr buffer) :
+        QueryBenchMark(cache_slots >= 0 ? true : false, queries,
+                       query_message, buffer)
+    {
+        if (cache_slots >= 0) {
+            server_->setCacheSlots(cache_slots);
+        }
+        server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
+                                                string(datasrc_file) + "\"}"));
+    }
+};
+
+class MemoryQueryBenchMark  : public QueryBenchMark {
+public:
+    MemoryQueryBenchMark(const char* const zone_file,
+                         const char* const zone_origin,
+                          const BenchQueries& queries,
+                          MessagePtr query_message,
+                          OutputBufferPtr buffer) :
+        QueryBenchMark(false, queries, query_message, buffer)
+    {
+        configureAuthServer(*server_,
+                            Element::fromJSON(
+                                "{\"datasources\": "
+                                " [{\"type\": \"memory\","
+                                "   \"zones\": [{\"origin\": \"" +
+                                string(zone_origin) + "\","
+                                "    \"file\": \"" +
+                                string(zone_file) + "\"}]}]}"));
+    }
+};
+
+void
+printQPSResult(unsigned int iteration, double duration,
+            double iteration_per_second)
+{
+    cout.precision(6);
+    cout << "Processed " << iteration << " queries in "
+         << fixed << duration << "s";
+    cout.precision(2);
+    cout << " (" << fixed << iteration_per_second << "qps)" << endl;
+}
 }
 }
 
 
 namespace isc {
 namespace isc {
 namespace bench {
 namespace bench {
 template<>
 template<>
 void
 void
-BenchMark<QueryBenchMark>::printResult() const {
-    cout.precision(6);
-    cout << "Processed " << getIteration() << " queries in "
-         << fixed << getDuration() << "s";
-    cout.precision(2);
-    cout << " (" << fixed << getIterationPerSecond() << "qps)" << endl;
+BenchMark<Sqlite3QueryBenchMark>::printResult() const {
+    printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
+}
+
+template<>
+void
+BenchMark<MemoryQueryBenchMark>::printResult() const {
+    printQPSResult(getIteration(), getDuration(), getIterationPerSecond());
 }
 }
 }
 }
 }
 }
 
 
 namespace {
 namespace {
+const int ITERATION_DEFAULT = 1;
+enum DataSrcType {
+    SQLITE3,
+    MEMORY
+};
+
 void
 void
 usage() {
 usage() {
-    cerr << "Usage: query_bench [-n iterations] datasrc_file query_datafile"
+    cerr <<
+        "Usage: query_bench [-n iterations] [-t datasrc_type] [-o origin] "
+        "datasrc_file query_datafile\n"
+        "  -n Number of iterations per test case (default: "
+         << ITERATION_DEFAULT << ")\n"
+        "  -t Type of data source: sqlite3|memory (default: sqlite3)\n"
+        "  -o Origin name of datasrc_file necessary for \"memory\", "
+        "ignored for others\n"
+        "  datasrc_file: sqlite3 DB file for \"sqlite3\", "
+        "textual master file for \"memory\" datasrc\n"
+        "  query_datafile: queryperf style input data"
          << endl;
          << endl;
     exit (1);
     exit (1);
 }
 }
@@ -136,12 +201,20 @@ usage() {
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     int ch;
     int ch;
-    int iteration = 1;
-    while ((ch = getopt(argc, argv, "n:")) != -1) {
+    int iteration = ITERATION_DEFAULT;
+    const char* opt_datasrc_type = "sqlite3";
+    const char* origin = NULL;
+    while ((ch = getopt(argc, argv, "n:t:o:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'n':
         case 'n':
             iteration = atoi(optarg);
             iteration = atoi(optarg);
             break;
             break;
+        case 't':
+            opt_datasrc_type = optarg;
+            break;
+        case 'o':
+            origin = optarg;
+            break;
         case '?':
         case '?':
         default:
         default:
             usage();
             usage();
@@ -155,6 +228,21 @@ main(int argc, char* argv[]) {
     const char* const datasrc_file = argv[0];
     const char* const datasrc_file = argv[0];
     const char* const query_data_file = argv[1];
     const char* const query_data_file = argv[1];
 
 
+    DataSrcType datasrc_type = SQLITE3;
+    if (strcmp(opt_datasrc_type, "sqlite3") == 0) {
+        ;                       // no need to override
+    } else if (strcmp(opt_datasrc_type, "memory") == 0) {
+        datasrc_type = MEMORY;
+    } else {
+        cerr << "Unknown data source type: " << datasrc_type << endl;
+        return (1);
+    }
+
+    if (datasrc_type == MEMORY && origin == NULL) {
+        cerr << "'-o Origin' is missing for memory data source " << endl;
+        return (1);
+    }
+
     BenchQueries queries;
     BenchQueries queries;
     loadQueryData(query_data_file, queries, RRClass::IN());
     loadQueryData(query_data_file, queries, RRClass::IN());
     OutputBufferPtr buffer(new OutputBuffer(4096));
     OutputBufferPtr buffer(new OutputBuffer(4096));
@@ -162,32 +250,46 @@ main(int argc, char* argv[]) {
 
 
     cout << "Parameters:" << endl;
     cout << "Parameters:" << endl;
     cout << "  Iterations: " << iteration << endl;
     cout << "  Iterations: " << iteration << endl;
-    cout << "  Data Source: " << datasrc_file << endl;
+    cout << "  Data Source: type=" << opt_datasrc_type << ", file=" <<
+        datasrc_file << endl;
+    if (origin != NULL) {
+        cout << "  Origin: " << origin << endl;
+    }
     cout << "  Query data: file=" << query_data_file << " (" << queries.size()
     cout << "  Query data: file=" << query_data_file << " (" << queries.size()
          << " queries)" << endl << endl;
          << " queries)" << endl << endl;
 
 
-    cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
-         << endl;
-    BenchMark<QueryBenchMark>(iteration,
-                              QueryBenchMark(0, datasrc_file, queries, message,
-                                             buffer));
+    switch (datasrc_type) {
+    case SQLITE3:
+        cout << "Benchmark enabling Hot Spot Cache with unlimited slots "
+             << endl;
+        BenchMark<Sqlite3QueryBenchMark>(
+            iteration, Sqlite3QueryBenchMark(0, datasrc_file, queries,
+                                             message, buffer));
 
 
-    cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
-         << endl;
-    BenchMark<QueryBenchMark>(iteration,
-                              QueryBenchMark(10 * queries.size(), datasrc_file,
+        cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
+             << endl;
+        BenchMark<Sqlite3QueryBenchMark>(
+            iteration, Sqlite3QueryBenchMark(10 * queries.size(), datasrc_file,
                                              queries, message, buffer));
                                              queries, message, buffer));
 
 
-    cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
-         << endl;
-    BenchMark<QueryBenchMark>(iteration,
-                              QueryBenchMark(queries.size() / 2, datasrc_file,
+        cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
+             << endl;
+        BenchMark<Sqlite3QueryBenchMark>(
+            iteration, Sqlite3QueryBenchMark(queries.size() / 2, datasrc_file,
                                              queries, message, buffer));
                                              queries, message, buffer));
 
 
-    cout << "Benchmark disabling Hot Spot Cache" << endl;
-    BenchMark<QueryBenchMark>(iteration,
-                              QueryBenchMark(-1, datasrc_file, queries,
-                                             message, buffer));    
+        cout << "Benchmark disabling Hot Spot Cache" << endl;
+        BenchMark<Sqlite3QueryBenchMark>(
+            iteration, Sqlite3QueryBenchMark(-1, datasrc_file, queries,
+                                             message, buffer));
+        break;
+    case MEMORY:
+        cout << "Benchmark with In Memory Data Source" << endl;
+        BenchMark<MemoryQueryBenchMark>(
+            iteration, MemoryQueryBenchMark(datasrc_file, origin, queries,
+                                            message, buffer));
+        break;
+    }
 
 
     return (0);
     return (0);
 }
 }

+ 10 - 5
src/bin/auth/config.cc

@@ -151,16 +151,21 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
             isc_throw(AuthConfigError, "Missing zone file for zone: "
             isc_throw(AuthConfigError, "Missing zone file for zone: "
                       << origin->str());
                       << origin->str());
         }
         }
-        const result::Result result = memory_datasrc_->addZone(
-            ZonePtr(new MemoryZone(rrclass_, Name(origin->stringValue()))));
+        shared_ptr<MemoryZone> new_zone(new MemoryZone(rrclass_,
+            Name(origin->stringValue())));
+        const result::Result result = memory_datasrc_->addZone(new_zone);
         if (result == result::EXIST) {
         if (result == result::EXIST) {
             isc_throw(AuthConfigError, "zone "<< origin->str()
             isc_throw(AuthConfigError, "zone "<< origin->str()
                       << " already exists");
                       << " already exists");
         }
         }
 
 
-        // TODO
-        // then load the zone from 'file', which is currently not implemented.
-        //
+        /*
+         * TODO: Once we have better reloading of configuration (something
+         * else than throwing everything away and loading it again), we will
+         * need the load method to be split into some kind of build and
+         * commit/abort parts.
+         */
+        new_zone->load(file->stringValue());
     }
     }
 }
 }
 
 

+ 41 - 1
src/bin/auth/main.cc

@@ -25,7 +25,7 @@
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
 
 
-#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -62,6 +62,10 @@ static bool verbose_mode = false;
 static const string PROGRAM = "Auth";
 static const string PROGRAM = "Auth";
 static const char* DNSPORT = "5300";
 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;
+
 /* 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? */
@@ -84,6 +88,12 @@ my_command_handler(const string& command, ConstElementPtr args) {
         answer = createAnswer(0, args);
         answer = createAnswer(0, args);
     } else if (command == "shutdown") {
     } else if (command == "shutdown") {
         io_service.stop();
         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);
     return (answer);
@@ -103,6 +113,12 @@ 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
@@ -168,7 +184,10 @@ main(int argc, char* argv[]) {
     // XXX: we should eventually pass io_service here.
     // XXX: we should eventually pass io_service here.
     Session* cc_session = NULL;
     Session* cc_session = NULL;
     Session* xfrin_session = NULL;
     Session* xfrin_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)
     ModuleCCSession* config_session = NULL;
     ModuleCCSession* config_session = NULL;
     string xfrout_socket_path;
     string xfrout_socket_path;
     if (getenv("B10_FROM_BUILD") != NULL) {
     if (getenv("B10_FROM_BUILD") != NULL) {
@@ -230,12 +249,19 @@ main(int argc, char* argv[]) {
         xfrin_session_established = true;
         xfrin_session_established = true;
         cout << "[b10-auth] Xfrin session channel established." << endl;
         cout << "[b10-auth] Xfrin session channel established." << endl;
 
 
+        statistics_session = new Session(io_service.get_io_service());
+        cout << "[b10-auth] Statistics session channel created." << endl;
+        statistics_session->establish(NULL);
+        statistics_session_established = true;
+        cout << "[b10-auth] Statistics session channel established." << endl;
+
         // XXX: with the current interface to asiolink we have to create
         // XXX: with the current interface to asiolink we have to create
         // auth_server before io_service while Session needs io_service.
         // auth_server before io_service while Session needs io_service.
         // In a next step of refactoring we should make asiolink independent
         // In a next step of refactoring we should make asiolink independent
         // from auth_server, and create io_service, auth_server, and
         // from auth_server, and create io_service, auth_server, and
         // sessions in that order.
         // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
         auth_server->setXfrinSession(xfrin_session);
+        auth_server->setStatisticsSession(statistics_session);
 
 
         // Configure the server.  configureAuthServer() is expected to install
         // Configure the server.  configureAuthServer() is expected to install
         // all initial configurations, but as a short term workaround we
         // all initial configurations, but as a short term workaround we
@@ -245,6 +271,14 @@ 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();
 
 
@@ -254,10 +288,16 @@ main(int argc, char* argv[]) {
         ret = 1;
         ret = 1;
     }
     }
 
 
+    if (statistics_session_established) {
+        statistics_session->disconnect();
+    }
+
     if (xfrin_session_established) {
     if (xfrin_session_established) {
         xfrin_session->disconnect();
         xfrin_session->disconnect();
     }
     }
 
 
+    delete itimer;
+    delete statistics_session;
     delete xfrin_session;
     delete xfrin_session;
     delete config_session;
     delete config_session;
     delete cc_session;
     delete cc_session;

+ 22 - 2
src/bin/auth/query.cc

@@ -26,6 +26,24 @@ namespace isc {
 namespace auth {
 namespace auth {
 
 
 void
 void
+Query::putSOA(const Zone& zone) const {
+    Zone::FindResult soa_result(zone.find(zone.getOrigin(),
+        RRType::SOA()));
+    if (soa_result.code != Zone::SUCCESS) {
+        isc_throw(NoSOA, "There's no SOA record in zone " <<
+            zone.getOrigin().toText());
+    } else {
+        /*
+         * FIXME:
+         * The const-cast is wrong, but the Message interface seems
+         * to insist.
+         */
+        response_.addRRset(Message::SECTION_AUTHORITY,
+            boost::const_pointer_cast<RRset>(soa_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);
@@ -59,12 +77,14 @@ Query::process() const {
                 // TODO : add NS to authority section, fill in additional section.
                 // TODO : add NS to authority section, fill in additional section.
                 break;
                 break;
             case Zone::NXDOMAIN:
             case Zone::NXDOMAIN:
+                // Just empty answer with SOA in authority section
                 response_.setRcode(Rcode::NXDOMAIN());
                 response_.setRcode(Rcode::NXDOMAIN());
-                // TODO : add SOA to authority section
+                putSOA(*result.zone);
                 break;
                 break;
             case Zone::NXRRSET:
             case Zone::NXRRSET:
+                // Just empty answer with SOA in authority section
                 response_.setRcode(Rcode::NOERROR());
                 response_.setRcode(Rcode::NOERROR());
-                // TODO : add SOA to authority section
+                putSOA(*result.zone);
                 break;
                 break;
             case Zone::CNAME:
             case Zone::CNAME:
             case Zone::DNAME:
             case Zone::DNAME:

+ 36 - 2
src/bin/auth/query.h

@@ -14,6 +14,8 @@
  * PERFORMANCE OF THIS SOFTWARE.
  * PERFORMANCE OF THIS SOFTWARE.
  */
  */
 
 
+#include <exceptions/exceptions.h>
+
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 class Message;
 class Message;
@@ -23,6 +25,7 @@ class RRType;
 
 
 namespace datasrc {
 namespace datasrc {
 class MemoryDataSrc;
 class MemoryDataSrc;
+class Zone;
 }
 }
 
 
 namespace auth {
 namespace auth {
@@ -100,15 +103,46 @@ public:
     /// providing compatible behavior may have its own benefit, so this point
     /// providing compatible behavior may have its own benefit, so this point
     /// should be revisited later.
     /// should be revisited later.
     ///
     ///
-    /// Right now this method never throws an exception, but it may in a
-    /// future version.
+    /// This might throw BadZone or any of its specific subclasses, but that
+    /// shouldn't happen in real-life (as BadZone means wrong data, it should
+    /// have been rejected upon loading).
     void process() const;
     void process() const;
 
 
+    /// \short Bad zone data encountered.
+    ///
+    /// This is thrown when process encounteres misconfigured zone in a way
+    /// it can't continue. This throws, not sets the Rcode, because such
+    /// misconfigured zone should not be present in the data source and
+    /// should have been rejected sooner.
+    struct BadZone : public isc::Exception {
+        BadZone(const char* file, size_t line, const char* what) :
+            Exception(file, line, what)
+        {}
+    };
+
+    /// \short Zone is missing its SOA record.
+    ///
+    /// We tried to add a SOA into the authoritative section, but the zone
+    /// does not contain one.
+    struct NoSOA : public BadZone {
+        NoSOA(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_;
     const isc::dns::RRType& qtype_;
     const isc::dns::RRType& qtype_;
     isc::dns::Message& response_;
     isc::dns::Message& response_;
+
+    /**
+     * \short Adds a SOA.
+     *
+     * Adds a SOA of the zone into the authority zone of response_.
+     * Can throw NoSOA.
+     */
+    void putSOA(const isc::datasrc::Zone& zone) const;
 };
 };
 
 
 }
 }

+ 162 - 0
src/bin/auth/statistics.cc

@@ -0,0 +1,162 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <auth/statistics.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <sstream>
+#include <iostream>
+
+// TODO: We need a namespace ("auth_server"?) to hold
+// AuthSrv and AuthCounters.
+
+class AuthCountersImpl {
+private:
+    // prohibit copy
+    AuthCountersImpl(const AuthCountersImpl& source);
+    AuthCountersImpl& operator=(const AuthCountersImpl& source);
+public:
+    // References verbose_mode flag in AuthSrvImpl
+    // TODO: Fix this short term workaround for logging
+    // after we have logging framework
+    AuthCountersImpl(const bool& verbose_mode);
+    ~AuthCountersImpl();
+    void inc(const AuthCounters::CounterType type);
+    bool submitStatistics() const;
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+    // Currently for testing purpose only
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+private:
+    std::vector<uint64_t> counters_;
+    isc::cc::AbstractSession* statistics_session_;
+    const bool& verbose_mode_;
+};
+
+AuthCountersImpl::AuthCountersImpl(const bool& verbose_mode) :
+    // initialize counter
+    // size: AuthCounters::COUNTER_TYPES, initial value: 0
+    counters_(AuthCounters::COUNTER_TYPES, 0),
+    statistics_session_(NULL),
+    verbose_mode_(verbose_mode)
+{}
+
+AuthCountersImpl::~AuthCountersImpl()
+{}
+
+void
+AuthCountersImpl::inc(const AuthCounters::CounterType type) {
+    ++counters_.at(type);
+}
+
+bool
+AuthCountersImpl::submitStatistics() const {
+    if (statistics_session_ == NULL) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "session interface for statistics"
+                      << " is not available" << std::endl;
+        }
+        return (false);
+    }
+    std::stringstream statistics_string;
+    statistics_string << "{\"command\": [\"set\","
+                      <<   "{ \"stats_data\": "
+                      <<     "{ \"auth.queries.udp\": "
+                      <<     counters_.at(AuthCounters::COUNTER_UDP_QUERY)
+                      <<     ", \"auth.queries.tcp\": "
+                      <<     counters_.at(AuthCounters::COUNTER_TCP_QUERY)
+                      <<   " }"
+                      <<   "}"
+                      << "]}";
+    isc::data::ConstElementPtr statistics_element =
+        isc::data::Element::fromJSON(statistics_string);
+    try {
+        // group_{send,recv}msg() can throw an exception when encountering
+        // an error, and group_recvmsg() will throw an exception on timeout.
+        // We don't want to kill the main server just due to this, so we
+        // handle them here.
+        const int seq =
+            statistics_session_->group_sendmsg(statistics_element, "Stats");
+        isc::data::ConstElementPtr env, answer;
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "send statistics data" << std::endl;
+        }
+        // TODO: parse and check response from statistics module
+        // currently it just returns empty message
+        statistics_session_->group_recvmsg(env, answer, false, seq);
+    } catch (const isc::cc::SessionError& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "communication error in sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    } catch (const isc::cc::SessionTimeout& ex) {
+        if (verbose_mode_) {
+            std::cerr << "[b10-auth] "
+                      << "timeout happened while sending statistics data: "
+                      << ex.what() << std::endl;
+        }
+        return (false);
+    }
+    return (true);
+}
+
+void
+AuthCountersImpl::setStatisticsSession
+    (isc::cc::AbstractSession* statistics_session)
+{
+    statistics_session_ = statistics_session;
+}
+
+// Currently for testing purpose only
+uint64_t
+AuthCountersImpl::getCounter(const AuthCounters::CounterType type) const {
+    return (counters_.at(type));
+}
+
+AuthCounters::AuthCounters(const bool& verbose_mode) :
+    impl_(new AuthCountersImpl(verbose_mode))
+{}
+
+AuthCounters::~AuthCounters() {
+    delete impl_;
+}
+
+void
+AuthCounters::inc(const AuthCounters::CounterType type) {
+    impl_->inc(type);
+}
+
+bool
+AuthCounters::submitStatistics() const {
+    return (impl_->submitStatistics());
+}
+
+void
+AuthCounters::setStatisticsSession
+    (isc::cc::AbstractSession* statistics_session)
+{
+    impl_->setStatisticsSession(statistics_session);
+}
+
+uint64_t
+AuthCounters::getCounter(const AuthCounters::CounterType type) const {
+    return (impl_->getCounter(type));
+}

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

@@ -0,0 +1,146 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __STATISTICS_H
+#define __STATISTICS_H 1
+
+#include <cc/session.h>
+
+class AuthCountersImpl;
+
+/// \brief Set of query counters.
+///
+/// \c AuthCounters is set of query counters class. It holds query counters
+/// and provides an interface to increment the counter of specified type
+/// (e.g. UDP query, TCP query).
+///
+/// This class also provides a function to send statistics information to
+/// statistics module.
+///
+/// This class is designed to be a part of \c AuthSrv.
+/// Call \c setStatisticsSession() to set a session to communicate with
+/// statistics module like Xfrin session.
+/// Call \c inc() to increment a counter for specific type of query in
+/// the query processing function. use \c enum \c CounterType to specify
+/// the type of query.
+/// Call \c submitStatistics() to submit statistics information to statistics
+/// module with statistics_session, periodically or at a time the command
+/// \c sendstats is received.
+///
+/// We may eventually want to change the structure to hold values that are
+/// not counters (such as concurrent TCP connections), or seperate generic
+/// part to src/lib to share with the other modules.
+///
+/// This class uses pimpl idiom and hides detailed implementation.
+/// This class is constructed on startup of the server, so
+/// construction overhead of this approach should be acceptable.
+///
+/// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
+/// \todo Consider overhead of \c AuthCounters::inc()
+class AuthCounters {
+private:
+    AuthCountersImpl* impl_;
+public:
+    // Enum for the type of counter
+    enum CounterType {
+        COUNTER_UDP_QUERY = 0,  ///< COUNTER_UDP_QUERY: counter for UDP queries
+        COUNTER_TCP_QUERY = 1,  ///< COUNTER_TCP_QUERY: counter for TCP queries
+        COUNTER_TYPES = 2 ///< The number of defined counters
+    };
+    /// The constructor.
+    ///
+    /// \param verbose_mode reference to verbose_mode_ of AuthSrvImpl
+    ///
+    /// This constructor is mostly exception free. But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \todo Fix this short term workaround for logging
+    /// after we have logging framework.
+    ///
+    AuthCounters(const bool& verbose_mode);
+    /// The destructor.
+    ///
+    /// This method never throws an exception.
+    ///
+    ~AuthCounters();
+
+    /// \brief Increment the counter specified by the parameter.
+    ///
+    /// \param type Type of a counter to increment.
+    ///
+    /// \throw std::out_of_range \a type is unknown.
+    ///
+    /// usage: counter.inc(CounterType::COUNTER_UDP_QUERY);
+    /// 
+    void inc(const CounterType type);
+
+    /// \brief Submit statistics counters to statistics module.
+    ///
+    /// This method is desinged to be called periodically
+    /// with \c asio_link::StatisticsSendTimer, or arbitrary
+    /// by the command 'sendstats'.
+    ///
+    /// Note: Set the session to communicate with statistics module
+    /// by \c setStatisticsSession() before calling \c submitStatistics().
+    ///
+    /// This method is mostly exception free (error conditions are
+    /// represented via the return value). But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    /// \return true on success, false on error.
+    ///
+    /// \todo Do not block message handling in auth_srv while submitting
+    /// statistics data.
+    ///
+    bool submitStatistics() const;
+
+    /// \brief Set the session to communicate with Statistics
+    /// module.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and session
+    /// frameworks, at which point the session will probably be passed on
+    /// construction of the server.
+    ///
+    /// Ownership isn't transferred: the caller is responsible for keeping
+    /// this object to be valid while the server object is working and for
+    /// disconnecting the session and destroying the object when the server
+    /// is shutdown.
+    ///
+    /// \param statistics_session A pointer to the session
+    ///
+    void setStatisticsSession(isc::cc::AbstractSession* statistics_session);
+
+    /// \brief Get a value of a counter in the AuthCounters.
+    ///
+    /// This function returns a value of the counter specified by \a type.
+    /// This method never throws an exception.
+    ///
+    /// Note: Currently this function is for testing purpose only.
+    ///
+    /// \param type Type of a counter to get the value of
+    ///
+    /// \return the value of the counter specified by \a type.
+    ///
+    uint64_t getCounter(const AuthCounters::CounterType type) const;
+};
+
+#endif // __STATISTICS_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -22,10 +22,12 @@ 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 += ../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 += 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 += 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)

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

@@ -15,14 +15,30 @@
 // $Id$
 // $Id$
 
 
 #include <config.h>
 #include <config.h>
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/memory_datasrc.h>
 #include <auth/auth_srv.h>
 #include <auth/auth_srv.h>
+#include <auth/statistics.h>
 
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 #include <testutils/srv_test.h>
 
 
+using namespace std;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::xfr;
 using namespace isc::xfr;
 using namespace asiolink;
 using namespace asiolink;
@@ -41,16 +57,114 @@ class AuthSrvTest : public SrvTestBase {
 protected:
 protected:
     AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
     AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
         server.setXfrinSession(&notify_session);
         server.setXfrinSession(&notify_session);
+        server.setStatisticsSession(&statistics_session);
     }
     }
     virtual void processMessage() {
     virtual void processMessage() {
         server.processMessage(*io_message, parse_message, response_obuffer,
         server.processMessage(*io_message, parse_message, response_obuffer,
                               &dnsserv);
                               &dnsserv);
     }
     }
+    MockSession statistics_session;
     MockXfroutClient xfrout;
     MockXfroutClient xfrout;
     AuthSrv server;
     AuthSrv server;
     const RRClass rrclass;
     const RRClass rrclass;
+    vector<uint8_t> response_data;
 };
 };
 
 
+// A helper function that builds a response to version.bind/TXT/CH that
+// should be identical to the response from our builtin (static) data source
+// by default.  The resulting wire-format data will be stored in 'data'.
+void
+createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
+    const Name version_name("version.bind");
+    Message message(Message::RENDER);
+
+    UnitTestUtil::createRequestMessage(message, Opcode::QUERY(),
+                                       qid, version_name,
+                                       RRClass::CH(), RRType::TXT());
+    message.setHeaderFlag(Message::HEADERFLAG_QR);
+    message.setHeaderFlag(Message::HEADERFLAG_AA);
+    RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
+                                                RRType::TXT(), RRTTL(0)));
+    rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+    message.addRRset(Message::SECTION_ANSWER, rrset_version);
+
+    RRsetPtr rrset_version_ns = RRsetPtr(new RRset(version_name, RRClass::CH(),
+                                                   RRType::NS(), RRTTL(0)));
+    rrset_version_ns->addRdata(generic::NS(version_name));
+    message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
+
+    OutputBuffer obuffer(0);
+    MessageRenderer renderer(obuffer);
+    message.toWire(renderer);
+
+    data.clear();
+    data.assign(static_cast<const uint8_t*>(renderer.getData()),
+                static_cast<const uint8_t*>(renderer.getData()) +
+                renderer.getLength());
+}
+
+// In the following tests we confirm the response data is rendered in
+// wire format in the expected way.
+
+// The most primitive check: checking the result of the processMessage()
+// method
+TEST_F(AuthSrvTest, builtInQuery) {
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("version.bind"),
+                                       RRClass::CH(), RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+    createBuiltinVersionResponse(default_qid, response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+}
+
+// Same test emulating the UDPServer class behavior (defined in libasiolink).
+// This is not a good test in that it assumes internal implementation details
+// of UDPServer, but we've encountered a regression due to the introduction
+// of that class, so we add a test for that case to prevent such a regression
+// in future.
+// Besides, the generalization of UDPServer is probably too much for the
+// authoritative only server in terms of performance, and it's quite likely
+// we need to drop it for the authoritative server implementation.
+// At that point we can drop this test, too.
+TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("version.bind"),
+                                       RRClass::CH(), RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP);
+
+    (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_obuffer, &dnsserv);
+    (*server.getDNSAnswerProvider())(*io_message, parse_message,
+                                     response_obuffer);
+
+    createBuiltinVersionResponse(default_qid, response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+}
+
+// Same type of test as builtInQueryViaDNSServer but for an error response.
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
+    createDataFromFile("iquery_fromWire.wire");
+    (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_obuffer, &dnsserv);
+    (*server.getDNSAnswerProvider())(*io_message, parse_message,
+                                     response_obuffer);
+
+    UnitTestUtil::readWireData("iquery_response_fromWire.wire",
+                               response_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        response_obuffer->getData(),
+                        response_obuffer->getLength(),
+                        &response_data[0], response_data.size());
+}
+
 // Unsupported requests.  Should result in NOTIMP.
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(AuthSrvTest, unsupportedRequest) {
 TEST_F(AuthSrvTest, unsupportedRequest) {
     unsupportedRequest();
     unsupportedRequest();
@@ -440,4 +554,90 @@ TEST_F(AuthSrvTest, cacheSlots) {
     server.setCacheSlots(0);
     server.setCacheSlots(0);
     EXPECT_EQ(00, server.getCacheSlots());
     EXPECT_EQ(00, server.getCacheSlots());
 }
 }
+
+// Submit UDP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterUDPNormal) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    // Create UDP message and process.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+    // After processing UDP query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+// Submit TCP normal query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPNormal) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    // Create TCP message and process.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_obuffer,
+                          &dnsserv);
+    // After processing TCP query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// Submit TCP AXFR query and check query counter
+TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                         Name("example.com"), RRClass::IN(), RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_TCP);
+    // On success, the AXFR query has been passed to a separate process,
+    // so we shouldn't have to respond.
+    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    // After processing TCP AXFR query, the counter should be 1.
+    EXPECT_EQ(1, server.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+// class for queryCounterUnexpected test
+// getProtocol() returns IPPROTO_IP
+class DummyUnknownSocket : public IOSocket {
+public:
+    DummyUnknownSocket() {}
+    virtual int getNative() const { return (0); }
+    virtual int getProtocol() const { return (IPPROTO_IP); }
+};
+
+// function for queryCounterUnexpected test
+// returns a reference to a static object of DummyUnknownSocket
+IOSocket&
+getDummyUnknownSocket() {
+    static DummyUnknownSocket socket;
+    return (socket);
+}
+
+// Submit unexpected type of query and check it throws isc::Unexpected
+TEST_F(AuthSrvTest, queryCounterUnexpected) {
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // Create UDP query packet.
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_UDP);
+
+    // Modify the message.
+    delete io_message;
+    endpoint = IOEndpoint::create(IPPROTO_UDP,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    io_message = new IOMessage(request_renderer.getData(),
+                               request_renderer.getLength(),
+                               getDummyUnknownSocket(), *endpoint);
+
+    EXPECT_THROW(server.processMessage(*io_message, parse_message,
+                                       response_obuffer, &dnsserv),
+                 isc::Unexpected);
+}
 }
 }

+ 72 - 22
src/bin/auth/tests/config_unittest.cc

@@ -17,6 +17,7 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
+#include <dns/masterload.h>
 
 
 #include <cc/data.h>
 #include <cc/data.h>
 
 
@@ -143,33 +144,42 @@ TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
 }
 }
 
 
 TEST_F(MemoryDatasrcConfigTest, addOneZone) {
 TEST_F(MemoryDatasrcConfigTest, addOneZone) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    // Check it actually loaded something
+    EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(rrclass)->findZone(
+        Name("ns.example.com.")).zone->find(Name("ns.example.com."),
+        RRType::A()).code);
 }
 }
 
 
 TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
 TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"},"
                       "              {\"origin\": \"example.org\","
                       "              {\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
                       "              {\"origin\": \"example.net\","
-                      "               \"file\": \"example.net.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.net.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
 }
 }
 
 
 TEST_F(MemoryDatasrcConfigTest, replace) {
 TEST_F(MemoryDatasrcConfigTest, replace) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(isc::datasrc::result::SUCCESS,
     EXPECT_EQ(isc::datasrc::result::SUCCESS,
               server.getMemoryDataSrc(rrclass)->findZone(
               server.getMemoryDataSrc(rrclass)->findZone(
@@ -179,31 +189,69 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
     // should replace the old one.
     // should replace the old one.
     delete parser;
     delete parser;
     parser = createAuthConfigParser(server, "datasources"); 
     parser = createAuthConfigParser(server, "datasources"); 
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.org\","
                       "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
                       "              {\"origin\": \"example.net\","
-                      "               \"file\": \"example.net.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.net.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(2, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(2, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(isc::datasrc::result::NOTFOUND,
     EXPECT_EQ(isc::datasrc::result::NOTFOUND,
               server.getMemoryDataSrc(rrclass)->findZone(
               server.getMemoryDataSrc(rrclass)->findZone(
                   Name("example.com")).code);
                   Name("example.com")).code);
 }
 }
 
 
+TEST_F(MemoryDatasrcConfigTest, exception) {
+    // Load a zone
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.com\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
+    EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    EXPECT_EQ(isc::datasrc::result::SUCCESS,
+              server.getMemoryDataSrc(rrclass)->findZone(
+                  Name("example.com")).code);
+
+    // create a new parser, and try to load something. It will throw,
+    // the given master file should not exist
+    delete parser;
+    parser = createAuthConfigParser(server, "datasources");
+    EXPECT_THROW(parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.org\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
+                      "              {\"origin\": \"example.net\","
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
+    // As that one throwed exception, it is not expected from us to
+    // commit it
+
+    // The original should be untouched
+    EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+    EXPECT_EQ(isc::datasrc::result::SUCCESS,
+              server.getMemoryDataSrc(rrclass)->findZone(
+                  Name("example.com")).code);
+}
+
 TEST_F(MemoryDatasrcConfigTest, remove) {
 TEST_F(MemoryDatasrcConfigTest, remove) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"}]}]"));
-    parser->commit();
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"}]}]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
     EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
 
 
     delete parser;
     delete parser;
     parser = createAuthConfigParser(server, "datasources"); 
     parser = createAuthConfigParser(server, "datasources"); 
-    parser->build(Element::fromJSON("[]"));
-    parser->commit();
+    EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
+    EXPECT_NO_THROW(parser->commit());
     EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
     EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
 }
 }
 
 
@@ -212,9 +260,11 @@ TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
                      Element::fromJSON(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\","
                          "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"example.zone\"},"
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.zone\"},"
                          "              {\"origin\": \"example.com\","
                          "              {\"origin\": \"example.com\","
-                         "               \"file\": \"example.com.zone\"}]}]")),
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.com.zone\"}]}]")),
                  AuthConfigError);
                  AuthConfigError);
 }
 }
 
 

+ 51 - 11
src/bin/auth/tests/query_unittest.cc

@@ -28,10 +28,14 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using namespace isc::auth;
 using namespace isc::auth;
 
 
+namespace {
+
 RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
 RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
                                       RRClass::IN(), RRType::A(),
                                       RRClass::IN(), RRType::A(),
                                       RRTTL(3600)));
                                       RRTTL(3600)));
-namespace {
+RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
+                                        RRClass::IN(), RRType::SOA(),
+                                        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()
 // It is a derived class of Zone, and simply hardcode the results of find()
 // return SUCCESS for "www.example.com",
 // return SUCCESS for "www.example.com",
@@ -41,16 +45,20 @@ namespace {
 // else return DNAME
 // else return DNAME
 class MockZone : public Zone {
 class MockZone : public Zone {
 public:
 public:
-    MockZone() : origin_(Name("example.com"))
+    MockZone(bool has_SOA = true) :
+        origin_(Name("example.com")),
+        has_SOA_(has_SOA)
     {}
     {}
     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;
 
 
     FindResult find(const isc::dns::Name& name,
     FindResult find(const isc::dns::Name& name,
-            const isc::dns::RRType& type) const;
+                    const isc::dns::RRType& type,
+                    const FindOptions options = FIND_DEFAULT) const;
 
 
 private:
 private:
     Name origin_;
     Name origin_;
+    bool has_SOA_;
 };
 };
 
 
 const Name&
 const Name&
@@ -64,20 +72,24 @@ MockZone::getClass() const {
 }
 }
 
 
 Zone::FindResult
 Zone::FindResult
-MockZone::find(const Name& name, const RRType&) const {
+MockZone::find(const Name& name, const RRType& type, const FindOptions) const {
     // hardcode the find results
     // hardcode the find results
     if (name == Name("www.example.com")) {
     if (name == Name("www.example.com")) {
-        return FindResult(SUCCESS, a_rrset);
+        return (FindResult(SUCCESS, a_rrset));
+    } else if (name == Name("example.com") && type == RRType::SOA() &&
+        has_SOA_)
+    {
+        return (FindResult(SUCCESS, soa_rrset));
     } else if (name == Name("delegation.example.com")) {
     } else if (name == Name("delegation.example.com")) {
-        return FindResult(DELEGATION, RRsetPtr());
+        return (FindResult(DELEGATION, RRsetPtr()));
     } else if (name == Name("nxdomain.example.com")) {
     } else if (name == Name("nxdomain.example.com")) {
-        return FindResult(NXDOMAIN, RRsetPtr());
+        return (FindResult(NXDOMAIN, RRsetPtr()));
     } else if (name == Name("nxrrset.example.com")) {
     } else if (name == Name("nxrrset.example.com")) {
-        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, RRsetPtr());
+        return (FindResult(CNAME, RRsetPtr()));
     } else {
     } else {
-        return FindResult(DNAME, RRsetPtr());
+        return (FindResult(DNAME, RRsetPtr()));
     }
     }
 }
 }
 
 
@@ -106,7 +118,7 @@ TEST_F(QueryTest, noZone) {
 }
 }
 
 
 TEST_F(QueryTest, matchZone) {
 TEST_F(QueryTest, matchZone) {
-    // match qname, normal query
+    // add a matching zone.
     memory_datasrc.addZone(ZonePtr(new MockZone()));
     memory_datasrc.addZone(ZonePtr(new MockZone()));
     query.process();
     query.process();
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
@@ -119,12 +131,39 @@ TEST_F(QueryTest, matchZone) {
     Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
     Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
     nxdomain_query.process();
     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_ADDITIONAL));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+        Name("example.com"), RRClass::IN(), RRType::SOA()));
 
 
     // NXRRSET
     // NXRRSET
     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();
     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_ADDITIONAL));
+    EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
+        Name("example.com"), RRClass::IN(), RRType::SOA()));
+}
+
+/*
+ * This tests that when there's no SOA and we need a negative answer. It should
+ * throw in that case.
+ */
+TEST_F(QueryTest, noSOA) {
+    memory_datasrc.addZone(ZonePtr(new MockZone(false)));
+
+    // The NX Domain
+    const Name nxdomain_name(Name("nxdomain.example.com"));
+    Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
+    EXPECT_THROW(nxdomain_query.process(), Query::NoSOA);
+    // Of course, we don't look into the response, as it throwed
+
+    // NXRRSET
+    const Name nxrrset_name(Name("nxrrset.example.com"));
+    Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
+    EXPECT_THROW(nxrrset_query.process(), Query::NoSOA);
 }
 }
 
 
 TEST_F(QueryTest, noMatchZone) {
 TEST_F(QueryTest, noMatchZone) {
@@ -136,4 +175,5 @@ TEST_F(QueryTest, noMatchZone) {
     nomatch_query.process();
     nomatch_query.process();
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 }
+
 }
 }

+ 215 - 0
src/bin/auth/tests/statistics_unittest.cc

@@ -0,0 +1,215 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <cc/data.h>
+#include <cc/session.h>
+
+#include <auth/statistics.h>
+
+#include <dns/tests/unittest_util.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::cc;
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace {
+
+class AuthCountersTest : public ::testing::Test {
+private:
+    class MockSession : public AbstractSession {
+    public:
+        MockSession() :
+            // by default we return a simple "success" message.
+            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+            throw_session_error_(false), throw_session_timeout_(false)
+        {}
+        virtual void establish(const char* socket_file);
+        virtual void disconnect();
+        virtual int group_sendmsg(ConstElementPtr msg, string group,
+                                  string instance, string to);
+        virtual bool group_recvmsg(ConstElementPtr& envelope,
+                                   ConstElementPtr& msg,
+                                   bool nonblock, int seq);
+        virtual void subscribe(string group, string instance);
+        virtual void unsubscribe(string group, string instance);
+        virtual void startRead(boost::function<void()> read_callback);
+        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
+        virtual bool hasQueuedMsgs() const;
+        virtual void setTimeout(size_t) {}
+        virtual size_t getTimeout() const { return (0); };
+
+        void setThrowSessionError(bool flag);
+        void setThrowSessionTimeout(bool flag);
+
+        void setMessage(ConstElementPtr msg) { msg_ = msg; }
+
+        ConstElementPtr sent_msg;
+        string msg_destination;
+    private:
+        ConstElementPtr msg_;
+        bool throw_session_error_;
+        bool throw_session_timeout_;
+    };
+
+protected:
+    AuthCountersTest() : verbose_mode_(false), counters(verbose_mode_) {
+        counters.setStatisticsSession(&statistics_session_);
+    }
+    ~AuthCountersTest() {
+    }
+    MockSession statistics_session_;
+    bool verbose_mode_;
+    AuthCounters counters;
+};
+
+void
+AuthCountersTest::MockSession::establish(const char*) {}
+
+void
+AuthCountersTest::MockSession::disconnect() {}
+
+void
+AuthCountersTest::MockSession::subscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::unsubscribe(string, string)
+{}
+
+void
+AuthCountersTest::MockSession::startRead(boost::function<void()>)
+{}
+
+int
+AuthCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
+    return (-1);
+}
+
+bool
+AuthCountersTest::MockSession::hasQueuedMsgs() const {
+    return (false);
+}
+
+int
+AuthCountersTest::MockSession::group_sendmsg(ConstElementPtr msg,
+                                              string group, string, string)
+{
+    if (throw_session_error_) {
+        isc_throw(SessionError, "Session Error");
+    }
+    sent_msg = msg;
+    msg_destination = group;
+    return (0);
+}
+
+bool
+AuthCountersTest::MockSession::group_recvmsg(ConstElementPtr&,
+                                              ConstElementPtr& msg, bool, int)
+{
+    if (throw_session_timeout_) {
+        isc_throw(SessionTimeout, "Session Timeout");
+    }
+    msg = msg_;
+    return (true);
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionError(bool flag) {
+    throw_session_error_ = flag;
+}
+
+void
+AuthCountersTest::MockSession::setThrowSessionTimeout(bool flag) {
+    throw_session_timeout_ = flag;
+}
+
+TEST_F(AuthCountersTest, incrementUDPCounter) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_UDP_QUERY));
+    // After increment, the counter should be 1.
+    EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementTCPCounter) {
+    // The counter should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+    EXPECT_NO_THROW(counters.inc(AuthCounters::COUNTER_TCP_QUERY));
+    // After increment, the counter should be 1.
+    EXPECT_EQ(1, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+}
+
+TEST_F(AuthCountersTest, incrementInvalidCounter) {
+    // Expect to throw isc::InvalidParameter if the type of the counter is
+    // invalid.
+    EXPECT_THROW(counters.inc(AuthCounters::COUNTER_TYPES),
+                 std::out_of_range);
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithoutSession) {
+    // Set statistics_session to NULL and call submitStatistics().
+    // Expect to return false.
+    counters.setStatisticsSession(NULL);
+    EXPECT_FALSE(counters.submitStatistics());
+}
+
+TEST_F(AuthCountersTest, submitStatisticsWithException) {
+    // Exception SessionError and SessionTimeout will be thrown
+    // while sending statistics data.
+    // Both expect to return false.
+    statistics_session_.setThrowSessionError(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    statistics_session_.setThrowSessionError(false);
+    statistics_session_.setThrowSessionTimeout(true);
+    EXPECT_FALSE(counters.submitStatistics());
+    statistics_session_.setThrowSessionTimeout(false);
+}
+
+TEST_F(AuthCountersTest, submitStatistics) {
+    // Submit statistics data.
+    // Validate if it submits correct data.
+
+    // Counters should be initialized to 0.
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_UDP_QUERY));
+    EXPECT_EQ(0, counters.getCounter(AuthCounters::COUNTER_TCP_QUERY));
+
+    // UDP query counter is set to 2.
+    counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+    counters.inc(AuthCounters::COUNTER_UDP_QUERY);
+    // TCP query counter is set to 1.
+    counters.inc(AuthCounters::COUNTER_TCP_QUERY);
+    counters.submitStatistics();
+
+    // Destination is "Stats".
+    EXPECT_EQ("Stats", statistics_session_.msg_destination);
+    // Command is "set".
+    EXPECT_EQ("set", statistics_session_.sent_msg->get("command")
+                         ->get(0)->stringValue());
+    ConstElementPtr statistics_data = statistics_session_.sent_msg
+                                          ->get("command")->get(1)
+                                          ->get("stats_data");
+    // UDP query counter is 2 and TCP query counter is 1.
+    EXPECT_EQ(2, statistics_data->get("auth.queries.udp")->intValue());
+    EXPECT_EQ(1, statistics_data->get("auth.queries.tcp")->intValue());
+}
+
+}

+ 26 - 0
src/bin/auth/tests/testdata/Makefile.am

@@ -0,0 +1,26 @@
+CLEANFILES = *.wire
+
+BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
+BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
+BUILT_SOURCES += queryBadEDNS_fromWire.wire shortanswer_fromWire.wire
+BUILT_SOURCES += simplequery_fromWire.wire simpleresponse_fromWire.wire
+
+# NOTE: keep this in sync with real file listing
+# so is included in tarball
+EXTRA_DIST = badExampleQuery_fromWire.spec
+EXTRA_DIST += examplequery_fromWire.spec
+EXTRA_DIST += iqueryresponse_fromWire.spec
+EXTRA_DIST += multiquestion_fromWire.spec
+EXTRA_DIST += queryBadEDNS_fromWire.spec
+EXTRA_DIST += shortanswer_fromWire.spec
+EXTRA_DIST += shortmessage_fromWire
+EXTRA_DIST += shortquestion_fromWire
+EXTRA_DIST += shortresponse_fromWire
+EXTRA_DIST += simplequery_fromWire.spec
+EXTRA_DIST += simpleresponse_fromWire.spec
+
+EXTRA_DIST += example.com
+EXTRA_DIST += example.sqlite3
+
+.spec.wire:
+	$(abs_top_builddir)/src/lib/dns/tests/testdata/gen-wiredata.py -o $@ $<

+ 10 - 0
src/bin/auth/tests/testdata/badExampleQuery_fromWire.spec

@@ -0,0 +1,10 @@
+#
+# A simple QUERY message for the example.com zone that would hit a broken
+# record of the data source.
+#
+
+[header]
+# use default
+[question]
+name: broken.example.com
+rrtype: AAAA

+ 8 - 0
src/bin/auth/tests/testdata/example.com

@@ -0,0 +1,8 @@
+$TTL 3600
+@    SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+        	NS ns.example.com.
+ns.example.com.	A 192.0.2.1
+
+;; bogus RDATA for CNAME RR, but the loadzone tool accepts it.  looking up this
+;; record will trigger an exception.
+broken.example.com. CNAME 0123456789012345678901234567890123456789012345678901234567890123456789.example.com.

BIN
src/bin/auth/tests/testdata/example.sqlite3


+ 9 - 0
src/bin/auth/tests/testdata/examplequery_fromWire.spec

@@ -0,0 +1,9 @@
+#
+# A simple QUERY message for the example.com zone
+#
+
+[header]
+# use default
+[question]
+# use default
+name: ns.example.com

+ 9 - 0
src/bin/auth/tests/testdata/iqueryresponse_fromWire.spec

@@ -0,0 +1,9 @@
+#
+# A response to an IQUERY request.
+#
+
+[header]
+qr: response
+opcode: iquery
+[question]
+# use default

+ 12 - 0
src/bin/auth/tests/testdata/multiquestion_fromWire.spec

@@ -0,0 +1,12 @@
+#
+# A QUERY message with multiple questions.
+#
+
+[custom]
+sections: header:question/0:question/1
+[header]
+qdcount: 2
+[question/0]
+# use default
+[question/1]
+rrtype: AAAA 

+ 12 - 0
src/bin/auth/tests/testdata/queryBadEDNS_fromWire.spec

@@ -0,0 +1,12 @@
+#
+# A QUERY message with unsupported version of EDNS.
+#
+
+[header]
+arcount: 1
+# use default
+[question]
+# use default
+[edns]
+version: 1
+do: 1

+ 10 - 0
src/bin/auth/tests/testdata/shortanswer_fromWire.spec

@@ -0,0 +1,10 @@
+#
+# A QUERY message with a broken answer section (ancount > 0 but the section
+# is empty)
+#
+
+[header]
+# use default
+arcount: 1
+[question]
+# use default

+ 9 - 0
src/bin/auth/tests/testdata/shortmessage_fromWire

@@ -0,0 +1,9 @@
+###
+### DNS message-like data but doesn't contain sufficient length of data.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, (ARCNT is missing)
+0001 0000 0000

+ 13 - 0
src/bin/auth/tests/testdata/shortquestion_fromWire

@@ -0,0 +1,13 @@
+###
+### A query-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS missing)
+076578616d706c6503636f6d00 0001

+ 13 - 0
src/bin/auth/tests/testdata/shortresponse_fromWire

@@ -0,0 +1,13 @@
+###
+### A response-like data, but missing QCLASS field in the Question section.
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
+0001 0000 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) (QCLASS is missing)
+076578616d706c6503636f6d00 0001

+ 8 - 0
src/bin/auth/tests/testdata/simplequery_fromWire.spec

@@ -0,0 +1,8 @@
+#
+# A simple QUERY message.
+#
+
+[header]
+# use default
+[question]
+# use default

+ 8 - 0
src/bin/auth/tests/testdata/simpleresponse_fromWire.spec

@@ -0,0 +1,8 @@
+#
+# A simple response message.
+#
+
+[header]
+qr: response
+[question]
+# use default

+ 8 - 3
src/bin/bind10/tests/Makefile.am

@@ -1,12 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+#PYTESTS = args_test.py bind10_test.py
 PYTESTS = bind10_test.py
 PYTESTS = bind10_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/bin/bind10 \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/bin/bindctl/tests/Makefile.am

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = bindctl_test.py
 PYTESTS = bindctl_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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_srcdir)/src/bin \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/bin/cfgmgr/tests/Makefile.am

@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = b10-cfgmgr_test.py
 PYTESTS = b10-cfgmgr_test.py
 
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/bin/cfgmgr \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr \
-	$(PYCOVERAGE) $(abs_builddir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/bin/cmdctl/tests/Makefile.am

@@ -1,14 +1,18 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = cmdctl_test.py
 PYTESTS = cmdctl_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/bin/cmdctl \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 3 - 8
src/bin/loadzone/tests/correct/Makefile.am

@@ -1,4 +1,3 @@
-PYTESTS = correct_test.sh
 EXTRA_DIST = get_zonedatas.py
 EXTRA_DIST = get_zonedatas.py
 EXTRA_DIST += include.db
 EXTRA_DIST += include.db
 EXTRA_DIST += inclsub.db
 EXTRA_DIST += inclsub.db
@@ -14,12 +13,8 @@ EXTRA_DIST += ttl2.db
 EXTRA_DIST += ttlext.db
 EXTRA_DIST += ttlext.db
 EXTRA_DIST += example.db
 EXTRA_DIST += example.db
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
+# TODO: maybe use TESTS?
 # 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:
-	for pytest in $(PYTESTS) ; do \
-	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/loadzone \
-	$(SHELL) $(abs_builddir)/$$pytest || exit ; \
-	done
+	echo Running test: correct_test.sh 
+	$(SHELL) $(abs_builddir)/correct_test.sh

+ 3 - 9
src/bin/loadzone/tests/error/Makefile.am

@@ -1,5 +1,3 @@
-PYTESTS = error_test.sh
-
 EXTRA_DIST = error.known
 EXTRA_DIST = error.known
 EXTRA_DIST += formerr1.db 
 EXTRA_DIST += formerr1.db 
 EXTRA_DIST += formerr2.db
 EXTRA_DIST += formerr2.db
@@ -14,12 +12,8 @@ EXTRA_DIST += keyerror3.db
 EXTRA_DIST += originerr1.db
 EXTRA_DIST += originerr1.db
 EXTRA_DIST += originerr2.db
 EXTRA_DIST += originerr2.db
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
+# TODO: use TESTS ?
 # 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:
-	for pytest in $(PYTESTS) ; do \
-	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/bin/loadzone \
-	$(SHELL) $(abs_builddir)/$$pytest || exit ; \
-	done
+	echo Running test: error_test.sh
+	$(SHELL) $(abs_builddir)/error_test.sh

+ 7 - 3
src/bin/msgq/tests/Makefile.am

@@ -1,14 +1,18 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@          
 PYTESTS = msgq_test.py
 PYTESTS = msgq_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/msgq:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/msgq:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done
 
 

+ 3 - 53
src/bin/recurse/b10-recurse.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-recurse
 .\"     Title: b10-recurse
 .\"    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: September 16, 2010
+.\"      Date: December 27, 2010
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "B10\-RECURSE" "8" "September 16, 2010" "BIND10" "BIND10"
+.TH "B10\-RECURSE" "8" "December 27, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
 b10-recurse \- Recursive DNS server
 b10-recurse \- Recursive DNS server
 .SH "SYNOPSIS"
 .SH "SYNOPSIS"
 .HP \w'\fBb10\-recurse\fR\ 'u
 .HP \w'\fBb10\-recurse\fR\ 'u
-\fBb10\-recurse\fR [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+\fBb10\-recurse\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
 .SH "DESCRIPTION"
 .SH "DESCRIPTION"
 .PP
 .PP
 The
 The
@@ -60,55 +60,6 @@ This prototype version only supports forwarding\&. Future versions will introduc
 .PP
 .PP
 The arguments are as follows:
 The arguments are as follows:
 .PP
 .PP
-\fB\-4\fR
-.RS 4
-Enables IPv4 only mode\&. This switch may not be used with
-\fB\-6\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-6\fR
-.RS 4
-Enables IPv6 only mode\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&.
-.RE
-.PP
-\fB\-a \fR\fB\fIaddress\fR\fR
-.RS 4
-The IPv4 or IPv6 address to listen on\&. This switch may not be used with
-\fB\-4\fR
-nor
-\fB\-6\fR\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.)
-.RE
-.PP
-\fB\-n\fR
-.RS 4
-Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&.
-.RE
-.PP
-\fB\-p \fR\fB\fInumber\fR\fR
-.RS 4
-The port number it listens on\&. The default is 5300\&.
-.if n \{\
-.sp
-.\}
-.RS 4
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.ps +1
-\fBNote\fR
-.ps -1
-.br
-The Y1 prototype runs on all interfaces and on this nonstandard port\&.
-.sp .5v
-.RE
-.RE
-.PP
 \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
@@ -130,7 +81,6 @@ None\&.
 
 
 \fBb10-cfgmgr\fR(8),
 \fBb10-cfgmgr\fR(8),
 \fBb10-cmdctl\fR(8),
 \fBb10-cmdctl\fR(8),
-\fBb10-loadzone\fR(8),
 \fBb10-msgq\fR(8),
 \fBb10-msgq\fR(8),
 \fBbind10\fR(8),
 \fBbind10\fR(8),
 BIND 10 Guide\&.
 BIND 10 Guide\&.

+ 1 - 62
src/bin/recurse/b10-recurse.xml

@@ -21,7 +21,7 @@
 <refentry>
 <refentry>
 
 
   <refentryinfo>
   <refentryinfo>
-    <date>September 16, 2010</date>
+    <date>December 27, 2010</date>
   </refentryinfo>
   </refentryinfo>
 
 
   <refmeta>
   <refmeta>
@@ -45,11 +45,6 @@
   <refsynopsisdiv>
   <refsynopsisdiv>
     <cmdsynopsis>
     <cmdsynopsis>
       <command>b10-recurse</command>
       <command>b10-recurse</command>
-      <arg><option>-4</option></arg>
-      <arg><option>-6</option></arg>
-      <arg><option>-a <replaceable>address</replaceable></option></arg>
-      <arg><option>-n</option></arg>
-      <arg><option>-p <replaceable>number</replaceable></option></arg>
       <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>
@@ -89,59 +84,6 @@
     <para>The arguments are as follows:</para>
     <para>The arguments are as follows:</para>
 
 
     <variablelist>
     <variablelist>
-      <varlistentry>
-        <term><option>-4</option></term>
-        <listitem><para>
-          Enables IPv4 only mode.
-          This switch may not be used with <option>-6</option> nor
-          <option>-a</option>.
-          By default, it listens on both IPv4 and IPv6 (if capable).
-        </para></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-6</option></term>
-        <listitem><para>
-          Enables IPv6 only mode.
-          This switch may not be used with <option>-4</option> nor
-          <option>-a</option>.
-          By default, it listens on both IPv4 and IPv6 (if capable).
-        </para></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-a <replaceable>address</replaceable></option></term>
-
-        <listitem>
-          <para>The IPv4 or IPv6 address to listen on.
-            This switch may not be used with <option>-4</option> nor
-            <option>-6</option>.
-            The default is to listen on all addresses.
-            (This is a short term workaround. This argument may change.)   
-          </para>                      
-         </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-n</option></term>
-        <listitem><para>
-          Do not cache answers in memory.
-          The default is to use the cache for faster responses.
-	  The cache keeps the most recent 30,000 answers (positive
-	  and negative) in memory for 30 seconds (instead of querying
-	  the data source, such as SQLite3 database, each time).
-        </para></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-p <replaceable>number</replaceable></option></term>
-        <listitem><para>
-          The port number it listens on.
-          The default is 5300.</para>
-	  <note><simpara>The Y1 prototype runs on all interfaces
-	  and on this nonstandard port.</simpara></note>
-        </listitem>
-      </varlistentry>
 
 
       <varlistentry>
       <varlistentry>
         <term><option>-u <replaceable>username</replaceable></option></term>
         <term><option>-u <replaceable>username</replaceable></option></term>
@@ -187,9 +129,6 @@
         <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
         <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       </citerefentry>,
       <citerefentry>
       <citerefentry>
-        <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
         <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
         <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       </citerefentry>,
       <citerefentry>
       <citerefentry>

+ 7 - 3
src/bin/stats/tests/Makefile.am

@@ -1,15 +1,19 @@
 SUBDIRS = isc testdata
 SUBDIRS = isc testdata
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = b10-stats_test.py b10-stats_stub_test.py
 PYTESTS = b10-stats_test.py b10-stats_stub_test.py
 EXTRA_DIST = $(PYTESTS) fake_time.py
 EXTRA_DIST = $(PYTESTS) fake_time.py
 CLEANFILES = fake_time.pyc
 CLEANFILES = fake_time.pyc
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
 	B10_FROM_BUILD=$(abs_top_builddir) \
 	B10_FROM_BUILD=$(abs_top_builddir) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

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

@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = process_rename_test.py
 PYTESTS = process_rename_test.py
 # .py will be generated by configure, so we don't have to include it
 # .py will be generated by configure, so we don't have to include it
 # in EXTRA_DIST.
 # in EXTRA_DIST.
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
-	$(PYCOVERAGE) $(abs_builddir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/bin/xfrin/tests/Makefile.am

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrin_test.py
 PYTESTS = xfrin_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
@@ -8,13 +9,16 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 endif
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	env PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/bin/xfrout/tests/Makefile.am

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrout_test.py
 PYTESTS = xfrout_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
@@ -8,13 +9,16 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 endif
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 4
src/bin/zonemgr/tests/Makefile.am

@@ -1,14 +1,17 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = zonemgr_test.py
 PYTESTS = zonemgr_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
-
 CLEANFILES = initdb.file
 CLEANFILES = initdb.file
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
 	env PYTHONPATH=$(abs_top_builddir)/src/bin/zonemgr:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 87 - 0
src/lib/asiolink/asiolink.cc

@@ -26,6 +26,7 @@
 #include <asio.hpp>
 #include <asio.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
@@ -374,4 +375,90 @@ RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
          timeout_, retries_);
          timeout_, retries_);
 }
 }
 
 
+class IntervalTimerImpl {
+private:
+    // prohibit copy
+    IntervalTimerImpl(const IntervalTimerImpl& source);
+    IntervalTimerImpl& operator=(const IntervalTimerImpl& source);
+public:
+    IntervalTimerImpl(IOService& io_service);
+    ~IntervalTimerImpl();
+    void setupTimer(const IntervalTimer::Callback& cbfunc,
+                    const uint32_t interval);
+    void callback(const asio::error_code& error);
+private:
+    // a function to update timer_ when it expires
+    void updateTimer();
+    // a function to call back when timer_ expires
+    IntervalTimer::Callback cbfunc_;
+    // interval in seconds
+    uint32_t interval_;
+    // asio timer
+    asio::deadline_timer timer_;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+    timer_(io_service.get_io_service())
+{}
+
+IntervalTimerImpl::~IntervalTimerImpl()
+{}
+
+void
+IntervalTimerImpl::setupTimer(const IntervalTimer::Callback& cbfunc,
+                              const uint32_t interval)
+{
+    // Interval should not be 0.
+    if (interval == 0) {
+        isc_throw(isc::BadValue, "Interval should not be 0");
+    }
+    // Call back function should not be empty.
+    if (cbfunc.empty()) {
+        isc_throw(isc::InvalidParameter, "Callback function is empty");
+    }
+    cbfunc_ = cbfunc;
+    interval_ = interval;
+    // Set initial expire time.
+    // At this point the timer is not running yet and will not expire.
+    // After calling IOService::run(), the timer will expire.
+    updateTimer();
+    return;
+}
+
+void
+IntervalTimerImpl::updateTimer() {
+    try {
+        // Update expire time to (current time + interval_).
+        timer_.expires_from_now(boost::posix_time::seconds(interval_));
+    } catch (const asio::system_error& e) {
+        isc_throw(isc::Unexpected, "Failed to update timer");
+    }
+    // Reset timer.
+    timer_.async_wait(boost::bind(&IntervalTimerImpl::callback, this, _1));
+}
+
+void
+IntervalTimerImpl::callback(const asio::error_code& cancelled) {
+    // Do not call cbfunc_ in case the timer was cancelled.
+    // The timer will be canelled in the destructor of asio::deadline_timer.
+    if (!cancelled) {
+        cbfunc_();
+        // Set next expire time.
+        updateTimer();
+    }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) {
+    impl_ = new IntervalTimerImpl(io_service);
+}
+
+IntervalTimer::~IntervalTimer() {
+    delete impl_;
+}
+
+void
+IntervalTimer::setupTimer(const Callback& cbfunc, const uint32_t interval) {
+    return (impl_->setupTimer(cbfunc, interval));
+}
+
 }
 }

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

@@ -23,6 +23,7 @@
 #include <unistd.h>             // for some network system calls
 #include <unistd.h>             // for some network system calls
 #include <asio/ip/address.hpp>
 #include <asio/ip/address.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
 
 
 #include <functional>
 #include <functional>
 #include <string>
 #include <string>
@@ -98,6 +99,7 @@ class io_service;
 namespace asiolink {
 namespace asiolink {
 class DNSServiceImpl;
 class DNSServiceImpl;
 struct IOServiceImpl;
 struct IOServiceImpl;
+struct IntervalTimerImpl;
 
 
 /// \brief An exception that is thrown if an error occurs within the IO
 /// \brief An exception that is thrown if an error occurs within the IO
 /// module.  This is mainly intended to be a wrapper exception class for
 /// module.  This is mainly intended to be a wrapper exception class for
@@ -233,7 +235,7 @@ public:
     /// that share the same \c io_service with the authoritative server.
     /// that share the same \c io_service with the authoritative server.
     /// It will eventually be removed once the wrapper interface is
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
     /// generalized.
-    asio::io_service& get_io_service() { return io_service_.get_io_service(); };
+    asio::io_service& get_io_service() { return io_service_.get_io_service(); }
 private:
 private:
     DNSServiceImpl* impl_;
     DNSServiceImpl* impl_;
     IOService& io_service_;
     IOService& io_service_;
@@ -567,6 +569,98 @@ private:
     unsigned retries_;
     unsigned retries_;
 };
 };
 
 
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c asio::deadline_timer class.
+///
+/// This class is implemented to use \c asio::deadline_timer as
+/// interval timer.
+///
+/// \c setupTimer() sets a timer to expire on (now + interval) and
+/// a call back function.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when
+/// it expires.
+///
+/// The function calls the call back function set by \c setupTimer()
+/// and updates the timer to expire in (now + interval) seconds.
+/// The type of call back function is \c void(void).
+/// 
+/// The call back function will not be called if the instance of this
+/// class is destructed before the timer is expired.
+///
+/// Note: Destruction of an instance of this class while call back
+/// is pending causes throwing an exception from \c IOService.
+///
+/// Sample code:
+/// \code
+///  void function_to_call_back() {
+///      // this function will be called periodically
+///  }
+///  int interval_in_seconds = 1;
+///  IOService io_service;
+///
+///  IntervalTimer intervalTimer(io_service);
+///  intervalTimer.setupTimer(function_to_call_back, interval_in_seconds);
+///  io_service.run();
+/// \endcode
+///
+class IntervalTimer {
+public:
+    /// \name The type of timer callback function
+    typedef boost::function<void()> Callback;
+
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IntervalTimer(const IntervalTimer& source);
+    IntervalTimer& operator=(const IntervalTimer& source);
+public:
+    /// \brief The constructor with \c IOService.
+    ///
+    /// This constructor may throw a standard exception if
+    /// memory allocation fails inside the method.
+    /// This constructor may also throw \c asio::system_error.
+    ///
+    /// \param io_service A reference to an instance of IOService
+    ///
+    IntervalTimer(IOService& io_service);
+
+    /// \brief The destructor.
+    ///
+    /// This destructor never throws an exception.
+    ///
+    /// On the destruction of this class the timer will be canceled
+    /// inside \c asio::deadline_timer.
+    ///
+    ~IntervalTimer();
+    //@}
+
+    /// \brief Register timer callback function and interval.
+    ///
+    /// This function sets callback function and interval in seconds.
+    /// Timer will actually start after calling \c IOService::run().
+    ///
+    /// \param cbfunc A reference to a function \c void(void) to call back
+    /// when the timer is expired (should not be an empty functor)
+    /// \param interval Interval in seconds (greater than 0)
+    ///
+    /// Note: IntervalTimer will not pass \c asio::error_code to
+    /// call back function. In case the timer is cancelled, the function
+    /// will not be called.
+    ///
+    /// \throw isc::InvalidParameter cbfunc is empty
+    /// \throw isc::BadValue interval is 0
+    /// \throw isc::Unexpected ASIO library error
+    ///
+    void setupTimer(const Callback& cbfunc, const uint32_t interval);
+private:
+    IntervalTimerImpl* impl_;
+};
+
 }      // asiolink
 }      // asiolink
 #endif // __ASIOLINK_H
 #endif // __ASIOLINK_H
 
 

+ 247 - 0
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -24,6 +24,7 @@
 
 
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -59,6 +60,9 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1";
 // two octets encode the length of the rest of the data.  This is crucial
 // two octets encode the length of the rest of the data.  This is crucial
 // for the tests below.
 // for the tests below.
 const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
 const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+    boost::posix_time::milliseconds(50);
 
 
 TEST(IOAddressTest, fromText) {
 TEST(IOAddressTest, fromText) {
     IOAddress io_address_v4("192.0.2.1");
     IOAddress io_address_v4("192.0.2.1");
@@ -742,4 +746,247 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
     EXPECT_EQ(3, num);
     EXPECT_EQ(3, num);
 }
 }
 
 
+// This fixture is for testing IntervalTimer. Some callback functors are 
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+    IntervalTimerTest() : io_service_() {};
+    ~IntervalTimerTest() {}
+    class TimerCallBack : public std::unary_function<void, void> {
+    public:
+        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+        void operator()() const {
+            test_obj_->timer_called_ = true;
+            test_obj_->io_service_.stop();
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCounter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+            counter_ = 0;
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+        int counter_;
+    private:
+        IntervalTimerTest* test_obj_;
+    };
+    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+                                   IntervalTimer* timer,
+                                   TimerCallBackCounter& counter)
+            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Store the value of counter_.counter_.
+                prev_counter_ = counter_.counter_;
+                delete timer_;
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // Stop io_service to stop all timers.
+                test_obj_->io_service_.stop();
+                // Compare the value of counter_.counter_ with stored one.
+                // If TimerCallBackCounter was not called (expected behavior),
+                // they are same.
+                if (counter_.counter_ == prev_counter_) {
+                    test_obj_->timer_cancel_success_ = true;
+                }
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer* timer_;
+        TimerCallBackCounter& counter_;
+        int count_;
+        int prev_counter_;
+    };
+    class TimerCallBackOverwriter : public std::unary_function<void, void> {
+    public:
+        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+                                IntervalTimer& timer)
+            : test_obj_(test_obj), timer_(timer), count_(0)
+        {}
+        void operator()() {
+            ++count_;
+            if (count_ == 1) {
+                // First time of call back.
+                // Call setupTimer() to update callback function
+                // to TimerCallBack.
+                test_obj_->timer_called_ = false;
+                timer_.setupTimer(TimerCallBack(test_obj_), 1);
+            } else if (count_ == 2) {
+                // Second time of call back.
+                // If it reaches here, re-setupTimer() is failed (unexpected).
+                // We should stop here.
+                test_obj_->io_service_.stop();
+            }
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        IntervalTimer& timer_;
+        int count_;
+    };
+protected:
+    IOService io_service_;
+    bool timer_called_;
+    bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    IntervalTimer itimer(io_service_);
+    // expect throw if call back function is empty
+    EXPECT_THROW(itimer.setupTimer(IntervalTimer::Callback(), 1),
+                     isc::InvalidParameter);
+    // expect throw if interval is 0
+    EXPECT_THROW(itimer.setupTimer(TimerCallBack(this), 0), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+    // Create asio_link::IntervalTimer and setup.
+    // Then run IOService and test if the callback function is called.
+    IntervalTimer itimer(io_service_);
+    timer_called_ = false;
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    // setup timer
+    itimer.setupTimer(TimerCallBack(this), 1);
+    io_service_.run();
+    // reaches here after timer expired
+    // delta: difference between elapsed time and 1 second
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::seconds(1);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect TimerCallBack is called; timer_called_ is true
+    EXPECT_TRUE(timer_called_);
+    // expect interval is 1 second +/- TIMER_MARGIN_MSEC.
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+    // Note: This test currently takes 6 seconds. The timer should have
+    // finer granularity and timer periods in this test should be shorter
+    // in the future.
+    // This code isn't exception safe, but we'd rather keep the code
+    // simpler and more readable as this is only for tests and if it throws
+    // the program would immediately terminate anyway.
+
+    // The call back function will not be called after the timer is
+    // destructed.
+    //
+    // There are two timers:
+    //  itimer_counter (A)
+    //   (Calls TimerCallBackCounter)
+    //     - increments internal counter in callback function
+    //  itimer_canceller (B)
+    //   (Calls TimerCallBackCancelDeleter)
+    //     - first time of callback, it stores the counter value of
+    //       callback_canceller and destructs itimer_counter
+    //     - second time of callback, it compares the counter value of
+    //       callback_canceller with stored value
+    //       if they are same the timer was not called; expected result
+    //       if they are different the timer was called after destructed
+    //
+    //     0  1  2  3  4  5  6 (s)
+    // (A) i-----+--x
+    //              ^
+    //              |destruct itimer_counter
+    // (B) i--------+--------s
+    //                       ^stop io_service
+    //                        and test itimer_counter have been stopped
+    //
+
+    // itimer_counter will be deleted in TimerCallBackCancelDeleter
+    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+    IntervalTimer itimer_canceller(io_service_);
+    timer_cancel_success_ = false;
+    TimerCallBackCounter callback_canceller(this);
+    itimer_counter->setupTimer(callback_canceller, 2);
+    itimer_canceller.setupTimer(
+        TimerCallBackCancelDeleter(this, itimer_counter,
+                                   callback_canceller),
+        3);
+    io_service_.run();
+    EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+    // Note: This test currently takes 4 seconds. The timer should have
+    // finer granularity and timer periods in this test should be shorter
+    // in the future.
+
+    // Calling setupTimer() multiple times updates call back function
+    // and interval.
+    //
+    // There are two timers:
+    //  itimer (A)
+    //   (Calls TimerCallBackCounter / TimerCallBack)
+    //     - increments internal counter in callback function
+    //       (TimerCallBackCounter)
+    //       interval: 2 seconds
+    //     - io_service_.stop() (TimerCallBack)
+    //       interval: 1 second
+    //  itimer_overwriter (B)
+    //   (Calls TimerCallBackOverwriter)
+    //     - first time of callback, it calls setupTimer() to change
+    //       call back function and interval of itimer to
+    //       TimerCallBack / 1 second
+    //       after 3 + 1 seconds from the beginning of this test,
+    //       TimerCallBack() will be called and io_service_ stops.
+    //     - second time of callback, it means the test fails.
+    //
+    //     0  1  2  3  4  5  6 (s)
+    // (A) i-----+--C--s
+    //              ^  ^stop io_service
+    //              |change call back function
+    // (B) i--------+--------S
+    //                       ^(stop io_service on fail)
+    //
+
+    IntervalTimer itimer(io_service_);
+    IntervalTimer itimer_overwriter(io_service_);
+    // store start time
+    boost::posix_time::ptime start;
+    start = boost::posix_time::microsec_clock::universal_time();
+    itimer.setupTimer(TimerCallBackCounter(this), 2);
+    itimer_overwriter.setupTimer(TimerCallBackOverwriter(this, itimer), 3);
+    io_service_.run();
+    // reaches here after timer expired
+    // if interval is updated, it takes
+    //   3 seconds for TimerCallBackOverwriter
+    //   + 1 second for TimerCallBack (stop)
+    //   = 4 seconds.
+    // otherwise (test fails), it takes
+    //   3 seconds for TimerCallBackOverwriter
+    //   + 3 seconds for TimerCallBackOverwriter (stop)
+    //   = 6 seconds.
+    // delta: difference between elapsed time and 3 + 1 seconds
+    boost::posix_time::time_duration delta =
+        (boost::posix_time::microsec_clock::universal_time() - start)
+         - boost::posix_time::seconds(3 + 1);
+    if (delta.is_negative()) {
+        delta.invert_sign();
+    }
+    // expect callback function is updated: TimerCallBack is called
+    EXPECT_TRUE(timer_called_);
+    // expect interval is updated
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
 }
 }

+ 198 - 3
src/lib/datasrc/memory_datasrc.cc

@@ -13,10 +13,13 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <map>
 #include <map>
+#include <cassert>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
+#include <dns/masterload.h>
 
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
 #include <datasrc/rbtree.h>
@@ -51,12 +54,186 @@ struct MemoryZone::MemoryZoneImpl {
      * that.
      * that.
      */
      */
     typedef map<RRType, ConstRRsetPtr> Domain;
     typedef map<RRType, ConstRRsetPtr> Domain;
+    typedef Domain::value_type DomainPair;
     typedef boost::shared_ptr<Domain> DomainPtr;
     typedef boost::shared_ptr<Domain> DomainPtr;
     // The tree stores domains
     // The tree stores domains
     typedef RBTree<Domain> DomainTree;
     typedef RBTree<Domain> DomainTree;
     typedef RBNode<Domain> DomainNode;
     typedef RBNode<Domain> DomainNode;
     // The actual zone data
     // The actual zone data
     DomainTree domains_;
     DomainTree domains_;
+
+    /*
+     * Implementation of longer methods. We put them here, because the
+     * access is without the impl_-> and it will get inlined anyway.
+     */
+    // Implementation of MemoryZone::add
+    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+        // Sanitize input
+        if (!rrset) {
+            isc_throw(NullRRset, "The rrset provided is NULL");
+        }
+        Name name(rrset->getName());
+        NameComparisonResult compare(origin_.compare(name));
+        if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
+            compare.getRelation() != NameComparisonResult::EQUAL)
+        {
+            isc_throw(OutOfZone, "The name " << name <<
+                " is not contained in zone " << origin_);
+        }
+        // Get the node
+        DomainNode* node;
+        switch (domains->insert(name, &node)) {
+            // Just check it returns reasonable results
+            case DomainTree::SUCCEED:
+            case DomainTree::ALREADYEXIST:
+                break;
+            // Something odd got out
+            default:
+                assert(0);
+        }
+        assert(node);
+
+        // Now get the domain
+        DomainPtr domain;
+        // It didn't exist yet, create it
+        if (node->isEmpty()) {
+            domain.reset(new Domain);
+            node->setData(domain);
+        } else { // Get existing one
+            domain = node->getData();
+        }
+
+        // Try inserting the rrset there
+        if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
+            // Ok, we just put it in
+
+            // If this RRset creates a zone cut at this node, mark the node
+            // indicating the need for callback in find().
+            // TBD: handle DNAME, too
+            if (rrset->getType() == RRType::NS() &&
+                rrset->getName() != origin_) {
+                node->enableCallback();
+            }
+
+            return (result::SUCCESS);
+        } else {
+            // The RRSet of given type was already there
+            return (result::EXIST);
+        }
+    }
+
+    /*
+     * Same as above, but it checks the return value and if it already exists,
+     * it throws.
+     */
+    void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
+            switch (add(set, domains)) {
+                case result::EXIST:
+                    isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+                        set->toText());
+                case result::SUCCESS:
+                    return;
+                default:
+                    assert(0);
+            }
+    }
+
+    // Maintain intermediate data specific to the search context used in
+    /// \c find().
+    ///
+    /// It will be passed to \c zonecutCallback() and record a possible
+    /// zone cut node and related RRset (normally NS or DNAME).
+    struct FindState {
+        FindState(FindOptions options) : zonecut_node_(NULL),
+                                         options_(options)
+        {}
+        const DomainNode* zonecut_node_;
+        ConstRRsetPtr rrset_;
+        const FindOptions options_;
+    };
+
+    // A callback called from possible zone cut nodes.  This will be passed
+    // from the \c find() method to \c RBTree::find().
+    static bool zonecutCallback(const DomainNode& node, FindState* state) {
+        // We perform callback check only for the highest zone cut in the
+        // rare case of nested zone cuts.
+        if (state->zonecut_node_ != NULL) {
+            return (false);
+        }
+
+        const Domain::const_iterator found(node.getData()->find(RRType::NS()));
+        if (found != node.getData()->end()) {
+            // BIND 9 checks if this node is not the origin.  But it cannot
+            // be the origin because we don't enable the callback at the
+            // origin node (see MemoryZoneImpl::add()).  Or should we do a
+            // double check for it?
+            state->zonecut_node_ = &node;
+            state->rrset_ = found->second;
+
+            // Unless glue is allowed the search stops here, so we return
+            // false; otherwise return true to continue the search.
+            return ((state->options_ & FIND_GLUE_OK) == 0);
+        }
+
+        // This case should not happen because we enable callback only
+        // when we add an RR searched for above.
+        assert(0);
+        // This is here to avoid warning (therefore compilation error)
+        // in case assert is turned off. Otherwise we could get "Control
+        // reached end of non-void function".
+        return (false);
+    }
+
+    // Implementation of MemoryZone::find
+    FindResult find(const Name& name, RRType type,
+                    const FindOptions options) const
+    {
+        // Get the node
+        DomainNode* node(NULL);
+        FindState state(options);
+        switch (domains_.find(name, &node, zonecutCallback, &state)) {
+            case DomainTree::PARTIALMATCH:
+                if (state.zonecut_node_ != NULL) {
+                    return (FindResult(DELEGATION, state.rrset_));
+                }
+                // TODO: we should also cover empty non-terminal cases, which
+                // will require non trivial code and is deferred for later
+                // development.  For now, we regard any partial match that
+                // didn't hit a zone cut as "not found".
+            case DomainTree::NOTFOUND:
+                return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+            case DomainTree::EXACTMATCH: // This one is OK, handle it
+                break;
+            default:
+                assert(0);
+        }
+        assert(node);
+        assert(!node->isEmpty());
+
+        Domain::const_iterator found;
+
+        // If the node callback is enabled, this may be a zone cut.  If it
+        // has a NS RR, we should return a delegation.
+        if (node->isCallbackEnabled()) {
+            found = node->getData()->find(RRType::NS());
+            if (found != node->getData()->end()) {
+                return (FindResult(DELEGATION, found->second));
+            }
+        }
+
+        found = node->getData()->find(type);
+        if (found != node->getData()->end()) {
+            // Good, it is here
+            return (FindResult(SUCCESS, found->second));
+        } else {
+            /*
+             * TODO Look for CNAME and DNAME (it should be OK to do so when
+             * the value is not found, as CNAME/DNAME domain should be
+             * empty otherwise.)
+             */
+            return (FindResult(NXRRSET, ConstRRsetPtr()));
+        }
+    }
 };
 };
 
 
 MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
 MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
@@ -79,9 +256,27 @@ MemoryZone::getClass() const {
 }
 }
 
 
 Zone::FindResult
 Zone::FindResult
-MemoryZone::find(const Name&, const RRType&) const {
-    // This is a tentative implementation that always returns NXDOMAIN.
-    return (FindResult(NXDOMAIN, RRsetPtr()));
+MemoryZone::find(const Name& name, const RRType& type,
+                 const FindOptions options) const
+{
+    return (impl_->find(name, type, options));
+}
+
+result::Result
+MemoryZone::add(const ConstRRsetPtr& rrset) {
+    return (impl_->add(rrset, &impl_->domains_));
+}
+
+
+void
+MemoryZone::load(const string& filename) {
+    // Load it into a temporary tree
+    MemoryZoneImpl::DomainTree tmp;
+    masterLoad(filename.c_str(), getOrigin(), getClass(),
+        boost::bind(&MemoryZoneImpl::addFromLoad, impl_, _1, &tmp));
+    // If it went well, put it inside
+    tmp.swap(impl_->domains_);
+    // And let the old data die with tmp
 }
 }
 
 
 /// Implementation details for \c MemoryDataSrc hidden from the public
 /// Implementation details for \c MemoryDataSrc hidden from the public

+ 62 - 1
src/lib/datasrc/memory_datasrc.h

@@ -58,9 +58,70 @@ public:
     /// \brief Looks up an RRset in the zone.
     /// \brief Looks up an RRset in the zone.
     ///
     ///
     /// See documentation in \c Zone.
     /// See documentation in \c Zone.
+    ///
+    /// It returns NULL pointer in case of NXDOMAIN and NXRRSET
+    /// (the base class documentation does not seem to require that).
     virtual FindResult find(const isc::dns::Name& name,
     virtual FindResult find(const isc::dns::Name& name,
-                            const isc::dns::RRType& type) const;
+                            const isc::dns::RRType& type,
+                            const FindOptions options = FIND_DEFAULT) const;
+
+    /// \brief Inserts an rrset into the zone.
+    ///
+    /// It puts another RRset into the zone.
+    ///
+    /// It throws NullRRset or OutOfZone if the provided rrset is invalid. It
+    /// might throw standard allocation exceptions, in which case this function
+    /// does not guarantee strong exception safety (it is currently not needed,
+    /// if it is needed in future, it should be implemented).
+    ///
+    /// \param rrset The set to add.
+    /// \return SUCCESS or EXIST (if an rrset for given name and type already
+    ///    exists).
+    result::Result add(const isc::dns::ConstRRsetPtr& rrset);
 
 
+    /// \brief RRSet out of zone exception.
+    ///
+    /// This is thrown if addition of an RRset that doesn't belong under the
+    /// zone's origin is requested.
+    struct OutOfZone : public InvalidParameter {
+        OutOfZone(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \brief RRset is NULL exception.
+    ///
+    /// This is thrown if the provided RRset parameter is NULL.
+    struct NullRRset : public InvalidParameter {
+        NullRRset(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \brief Load zone from masterfile.
+    ///
+    /// This loads data from masterfile specified by filename. It replaces
+    /// current content. The masterfile parsing ability is kind of limited,
+    /// see isc::dns::masterLoad.
+    ///
+    /// This throws isc::dns::MasterLoadError if there is problem with loading
+    /// (missing file, malformed, it contains different zone, etc - see
+    /// isc::dns::masterLoad for details).
+    ///
+    /// In case of internal problems, OutOfZone, NullRRset or AssertError could
+    /// be thrown, but they should not be expected. Exceptions caused by
+    /// allocation may be thrown as well.
+    ///
+    /// If anything is thrown, the previous content is preserved (so it can
+    /// be used to update the data, but if user makes a typo, the old one
+    /// is kept).
+    ///
+    /// \param filename The master file to load.
+    ///
+    /// \todo We may need to split it to some kind of build and commit/abort.
+    ///     This will probably be needed when a better implementation of
+    ///     configuration reloading is written.
+    void load(const std::string& filename);
 private:
 private:
     /// \name Hidden private data
     /// \name Hidden private data
     //@{
     //@{

+ 219 - 73
src/lib/datasrc/rbtree.h

@@ -38,8 +38,11 @@ namespace helper {
 /// Helper function to remove the base domain from super domain
 /// 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 \code Name a("a.b.c"); Name b("b.c");
-/// Name c = a - b; \\c will be "a" \endcode
+/// sub_name so
+/// \code Name a("a.b.c");
+/// Name b("b.c");
+/// Name c = a - b;
+/// \endcode
 ///
 ///
 /// \note function in this namespace is not intended to be used outside.
 /// \note function in this namespace is not intended to be used outside.
 inline isc::dns::Name
 inline isc::dns::Name
@@ -51,8 +54,9 @@ operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
 
 
 template <typename T>
 template <typename T>
 class RBTree;
 class RBTree;
-/// \brief \c RBNode use by RBTree to store any data related to one domain name
 
 
+/// \brief \c RBNode use by RBTree to store any data related to one domain name
+///
 /// It has two roles, the first one is as one node in the \c RBTree,
 /// 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 second one is to store the data related to one domain name and maintain
 /// the domain name hierarchy struct in one domain name space.
 /// the domain name hierarchy struct in one domain name space.
@@ -74,11 +78,11 @@ public:
     friend class RBTree<T>;
     friend class RBTree<T>;
     typedef boost::shared_ptr<T> NodeDataPtr;
     typedef boost::shared_ptr<T> NodeDataPtr;
 
 
-    /// \name Deonstructor
+    /// \name Destructor
     /// \note it's seems a little strange that constructor is private
     /// \note it's seems a little strange that constructor is private
     /// but deconstructor left public, the reason is for some smart pointer
     /// but deconstructor left public, the reason is for some smart pointer
     /// like std::auto_ptr, they needs to delete RBNode in sometimes, but
     /// like std::auto_ptr, they needs to delete RBNode in sometimes, but
-    /// \code delete *pointer_to_node \codeend shouldn't be called directly
+    /// \code delete *pointer_to_node \endcode shouldn't be called directly
     //@{
     //@{
     ~RBNode();
     ~RBNode();
     //@}
     //@}
@@ -111,6 +115,24 @@ public:
     void setData(const NodeDataPtr& data) { data_ = data; }
     void setData(const NodeDataPtr& data) { data_ = data; }
     //@}
     //@}
 
 
+    /// \name Callback related methods
+    ///
+    /// See the description of \c RBTree<T>::find() about callbacks.
+    ///
+    /// These methods never throw an exception.
+    //@{
+    /// Return if callback is enabled at the node.
+    ///
+    /// This method never throws an exception.
+    bool isCallbackEnabled() const { return (callback_required_); }
+
+    /// Enable callback at the node.
+    void enableCallback() { callback_required_ = true; }
+
+    /// Disable callback at the node.
+    void disableCallback() { callback_required_ = false; }
+    //@}
+
 
 
 private:
 private:
     /// \brief Define rbnode color
     /// \brief Define rbnode color
@@ -147,6 +169,7 @@ private:
 
 
     isc::dns::Name     name_;
     isc::dns::Name     name_;
     NodeDataPtr       data_;
     NodeDataPtr       data_;
+
     /// the down pointer points to the root node of sub domains of current
     /// the down pointer points to the root node of sub domains of current
     /// domain
     /// domain
     /// \par Adding down pointer to \c RBNode is for two purpose:
     /// \par Adding down pointer to \c RBNode is for two purpose:
@@ -154,6 +177,10 @@ private:
     /// big flat tree into several hierarchy trees
     /// big flat tree into several hierarchy trees
     /// \li It save memory useage, so same label won't be saved several times
     /// \li It save memory useage, so same label won't be saved several 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")
+    bool callback_required_;
 };
 };
 
 
 
 
@@ -167,7 +194,8 @@ RBNode<T>::RBNode() :
     color_(BLACK),
     color_(BLACK),
     // dummy name, the value doesn't matter:
     // dummy name, the value doesn't matter:
     name_(isc::dns::Name::ROOT_NAME()),
     name_(isc::dns::Name::ROOT_NAME()),
-    down_(this)
+    down_(this),
+    callback_required_(false)
 {
 {
 }
 }
 
 
@@ -178,7 +206,8 @@ RBNode<T>::RBNode(const isc::dns::Name& name) :
     right_(NULL_NODE()),
     right_(NULL_NODE()),
     color_(RED),
     color_(RED),
     name_(name),
     name_(name),
-    down_(NULL_NODE())
+    down_(NULL_NODE()),
+    callback_required_(false)
 {
 {
 }
 }
 
 
@@ -186,51 +215,55 @@ RBNode<T>::RBNode(const isc::dns::Name& name) :
 template <typename T>
 template <typename T>
 RBNode<T>::~RBNode() {
 RBNode<T>::~RBNode() {
 }
 }
-/// \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.
-
-/*
-/// \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
-///                             /   \
-///                            a    d.e.f
-///                                   /|\
-///                                  c | g.h
-///                                    |
-///                                   w.y
-///                                   /|\
-///                                  x | z
-///                                    |
-///                                    p
-///                                   / \
-///                                  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
-/// \todo
-/// - 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
-*/
+
+// note: the following class description is documented using C-style comments
+// because the verbatim diagram contain a backslash, which could be interpreted
+// as part of a multi-line comment with C++ style comments.
+/**
+ *  \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.
+ * 
+ *  \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
+                              /   \
+                             a    d.e.f
+                                    /|\
+                                   c | g.h
+                                     |
+                                    w.y
+                                    /|\
+                                   x | z
+                                     |
+                                     p
+                                    / \
+                                   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
+ *  \todo
+ *  - 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
+ */
 template <typename T>
 template <typename T>
 class RBTree : public boost::noncopyable {
 class RBTree : public boost::noncopyable {
     friend class RBNode<T>;
     friend class RBNode<T>;
@@ -247,7 +280,7 @@ public:
 
 
     /// \name Constructor and Destructor
     /// \name Constructor and Destructor
     //@{
     //@{
-    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
     /// is not virtual
     /// is not virtual
@@ -256,13 +289,98 @@ public:
 
 
     /// \name Inquery methods
     /// \name Inquery methods
     //@{
     //@{
-    /// \brief Find the node with the name
+    /// \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.
+    /// If that happens the exception will be propagated to the caller.
+    /// The callback function should generally not throw an exception, but
+    /// if it throws, the exception will be propagated to the caller.
+    ///
     /// \param name Target to be found
     /// \param name Target to be found
-    /// \param node Point to the node when the return vaule is \c not
-    /// NOTFOUND, if the return value is NOTFOUND, the value of node is
-    /// \c unknown
-    Result find(const isc::dns::Name& name, RBNode<T>** node) const;
-    Result find(const isc::dns::Name& name, const RBNode<T>** node) const;
+    /// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
+    /// it will store a pointer to the matching node
+    /// \param callback If non \c NULL, a call back function to be called
+    /// at "delegation" nodes (see above).
+    /// \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.
+    template <typename CBARG>
+    Result find(const isc::dns::Name& name, RBNode<T>** node,
+                bool (*callback)(const RBNode<T>&, CBARG),
+                CBARG callback_arg) const;
+
+    /// Same as the other version, but the returned \c node will be immutable.
+    template <typename CBARG>
+    Result find(const isc::dns::Name& name, const RBNode<T>** node,
+                bool (*callback)(const RBNode<T>&, CBARG),
+                CBARG callback_arg) const;
+
+    /// Same as the templated version, but does not use callback.
+    ///
+    /// Applications except the zone implementation should generally use the
+    /// non templated version.
+    Result find(const isc::dns::Name& name, RBNode<T>** node) const {
+        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.
+    ///
+    /// In general, this version should be preferred over the other non
+    /// templated version, unless the caller knows it should modify the
+    /// returned node.
+    Result find(const isc::dns::Name& name, const RBNode<T>** node) const {
+        return (find<void*>(name, node, NULL, NULL));
+    }
 
 
     /// \brief Get the total node count in the tree
     /// \brief Get the total node count in the tree
     /// the node count including the node created common suffix node,
     /// the node count including the node created common suffix node,
@@ -289,11 +407,21 @@ public:
     /// - ALREADYEXIST means already has the node with the given name
     /// - 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
     /// \node To modify the data related with one name but not sure the name has
-    /// inserted or not, it is better to call \code insert \endcode,instead of
-    /// \code find() \endcode, in case the name isn't exist and needs to insert again
+    /// 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
     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.
+    ///
+    /// This acts the same as many std::*.swap functions, exchanges the
+    /// contents. This doesn't throw anything.
+    void swap(RBTree<T>& other) {
+        std::swap(root_, other.root_);
+        std::swap(NULLNODE, other.NULLNODE);
+        std::swap(node_count_, other.node_count_);
+    }
+
 private:
 private:
     /// \name RBTree balance functions
     /// \name RBTree balance functions
     //@{
     //@{
@@ -315,8 +443,11 @@ private:
     /// and node will points to c.b.a
     /// and node will points to c.b.a
     /// \note parameter up now is not used by any funciton, but we are gonna
     /// \note parameter up now is not used by any funciton, but we are gonna
     /// need it soon to implement function like remove
     /// need it soon to implement function like remove
+    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) const;
+                      RBNode<T>** node,
+                      bool (*callback)(const RBNode<T>&, CBARG),
+                      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
     /// for indent purpose, add certian mount empty charachter to output stream
@@ -380,30 +511,39 @@ void RBTree<T> ::deleteHelper(RBNode<T> *root) {
     --node_count_;
     --node_count_;
 }
 }
 
 
-template <typename T>
+template <typename T> template <typename CBARG>
 typename RBTree<T>::Result
 typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node) const {
+RBTree<T>::find(const isc::dns::Name& name, RBNode<T>** node,
+                bool (*callback)(const RBNode<T>&, CBARG),
+                CBARG callback_arg) const
+{
     const RBNode<T>* up_node = NULLNODE;
     const RBNode<T>* up_node = NULLNODE;
-    return (findHelper(name, &up_node, node));
+    return (findHelper(name, &up_node, node, callback, callback_arg));
 }
 }
 
 
-template <typename T>
+template <typename T> template <typename CBARG>
 typename RBTree<T>::Result
 typename RBTree<T>::Result
-RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node) const {
+RBTree<T>::find(const isc::dns::Name& name, const RBNode<T>** node,
+                bool (*callback)(const RBNode<T>&, CBARG),
+                CBARG callback_arg) const
+{
     const RBNode<T>* up_node;
     const RBNode<T>* up_node;
     RBNode<T>* target_node;
     RBNode<T>* target_node;
     const typename RBTree<T>::Result ret =
     const typename RBTree<T>::Result ret =
-        findHelper(name, &up_node, &target_node);
+        findHelper(name, &up_node, &target_node, callback, callback_arg);
     if (ret != NOTFOUND) {
     if (ret != NOTFOUND) {
         *node = target_node;
         *node = target_node;
     }
     }
     return (ret);
     return (ret);
 }
 }
 
 
-template <typename T>
+template <typename T> template <typename CBARG>
 typename RBTree<T>::Result
 typename RBTree<T>::Result
-RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_node,
-                      RBNode<T>** target) const
+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
 {
 {
     using namespace helper;
     using namespace helper;
 
 
@@ -431,12 +571,17 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_no
                 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) {
-                *up_node = node;
-                name = name - node->name_;
                 if (!node->isEmpty()) {
                 if (!node->isEmpty()) {
                     ret = RBTree<T>::PARTIALMATCH;
                     ret = RBTree<T>::PARTIALMATCH;
                     *target = node;
                     *target = node;
+                    if (callback != NULL && node->callback_required_) {
+                        if ((callback)(*node, callback_arg)) {
+                            break;
+                        }
+                    }
                 }
                 }
+                *up_node = node;
+                name = name - node->name_;
                 node = node->down_;
                 node = node->down_;
             } else {
             } else {
                 break;
                 break;
@@ -531,6 +676,7 @@ RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
     // 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));
     std::swap(node.data_, down_node->data_);
     std::swap(node.data_, down_node->data_);
+    std::swap(node.callback_required_, down_node->callback_required_);
     down_node->down_ = node.down_;
     down_node->down_ = node.down_;
     node.name_ = base_name;
     node.name_ = base_name;
     node.down_ = down_node.get();
     node.down_ = down_node.get();

+ 255 - 5
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -16,6 +16,8 @@
 
 
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/masterload.h>
 
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/memory_datasrc.h>
 
 
@@ -25,6 +27,9 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 
 
 namespace {
 namespace {
+// Commonly used result codes (Who should write the prefix all the time)
+using result::SUCCESS;
+using result::EXIST;
 
 
 class MemoryDataSrcTest : public ::testing::Test {
 class MemoryDataSrcTest : public ::testing::Test {
 protected:
 protected:
@@ -136,13 +141,94 @@ public:
     MemoryZoneTest() :
     MemoryZoneTest() :
         class_(RRClass::IN()),
         class_(RRClass::IN()),
         origin_("example.org"),
         origin_("example.org"),
-        zone_(class_, origin_)
-    { }
+        ns_name_("ns.example.org"),
+        child_ns_name_("child.example.org"),
+        child_glue_name_("ns.child.example.org"),
+        grandchild_ns_name_("grand.child.example.org"),
+        grandchild_glue_name_("ns.grand.child.example.org"),
+        zone_(class_, origin_),
+        rr_out_(new RRset(Name("example.com"), class_, RRType::A(),
+            RRTTL(300))),
+        rr_ns_(new RRset(origin_, class_, RRType::NS(), RRTTL(300))),
+        rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
+        rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
+        rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
+        rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
+                               RRTTL(300))),
+        rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
+                              RRTTL(300))),
+        rr_grandchild_ns_(new RRset(grandchild_ns_name_, class_, RRType::NS(),
+                                    RRTTL(300))),
+        rr_grandchild_glue_(new RRset(grandchild_glue_name_, class_,
+                                      RRType::AAAA(), RRTTL(300)))
+    {
+    }
     // Some data to test with
     // Some data to test with
-    RRClass class_;
-    Name origin_;
+    const RRClass class_;
+    const Name origin_, ns_name_, child_ns_name_, child_glue_name_,
+        grandchild_ns_name_, grandchild_glue_name_;
     // The zone to torture by tests
     // The zone to torture by tests
     MemoryZone zone_;
     MemoryZone zone_;
+
+    /*
+     * Some RRsets to put inside the zone.
+     * They are empty, but the MemoryZone does not have a reason to look
+     * inside anyway. We will check it finds them and does not change
+     * the pointer.
+     */
+    ConstRRsetPtr
+        // Out of zone RRset
+        rr_out_,
+        // NS of example.org
+        rr_ns_,
+        // A of ns.example.org
+        rr_ns_a_,
+        // AAAA of ns.example.org
+        rr_ns_aaaa_,
+        // A of example.org
+        rr_a_;
+    ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+    ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
+    ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+    ConstRRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+
+    /**
+     * \brief Test one find query to the zone.
+     *
+     * Asks a query to the zone and checks it does not throw and returns
+     * expected results. It returns nothing, it just signals failures
+     * to GTEST.
+     *
+     * \param name The name to ask for.
+     * \param rrtype The RRType to ask of.
+     * \param result The expected code of the result.
+     * \param check_answer Should a check against equality of the answer be
+     *     done?
+     * \param answer The expected rrset, if any should be returned.
+     * \param zone Check different MemoryZone object than zone_ (if NULL,
+     *     uses zone_)
+     */
+    void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
+                  bool check_answer = true,
+                  const ConstRRsetPtr& answer = ConstRRsetPtr(),
+                  MemoryZone *zone = NULL,
+                  Zone::FindOptions options = Zone::FIND_DEFAULT)
+    {
+        if (!zone) {
+            zone = &zone_;
+        }
+        // The whole block is inside, because we need to check the result and
+        // we can't assign to FindResult
+        EXPECT_NO_THROW({
+                Zone::FindResult find_result(zone->find(name, rrtype,
+                                                        options));
+                // Check it returns correct answers
+                EXPECT_EQ(result, find_result.code);
+                if (check_answer) {
+                    EXPECT_EQ(answer, find_result.rrset);
+                }
+            });
+    }
 };
 };
 
 
 /**
 /**
@@ -151,8 +237,172 @@ public:
  * Takes the created zone and checks its properties they are the same
  * Takes the created zone and checks its properties they are the same
  * as passed parameters.
  * as passed parameters.
  */
  */
-TEST_F(MemoryZoneTest, Constructor) {
+TEST_F(MemoryZoneTest, constructor) {
     ASSERT_EQ(class_, zone_.getClass());
     ASSERT_EQ(class_, zone_.getClass());
     ASSERT_EQ(origin_, zone_.getOrigin());
     ASSERT_EQ(origin_, zone_.getOrigin());
 }
 }
+/**
+ * \brief Test adding.
+ *
+ * We test that it throws at the correct moments and the correct exceptions.
+ * And we test the return value.
+ */
+TEST_F(MemoryZoneTest, add) {
+    // This one does not belong to this zone
+    EXPECT_THROW(zone_.add(rr_out_), MemoryZone::OutOfZone);
+    // Test null pointer
+    EXPECT_THROW(zone_.add(ConstRRsetPtr()), MemoryZone::NullRRset);
+
+    // Now put all the data we have there. It should throw nothing
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+
+    // Try putting there something twice, it should be rejected
+    EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_)));
+    EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_a_)));
+}
+
+// Test adding child zones and zone cut handling
+TEST_F(MemoryZoneTest, delegationNS) {
+    // add in-zone data
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+
+    // install a zone cut
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+
+    // below the zone cut
+    findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+             true, rr_child_ns_);
+
+    // at the zone cut
+    findTest(Name("child.example.org"), RRType::A(), Zone::DELEGATION,
+             true, rr_child_ns_);
+    findTest(Name("child.example.org"), RRType::NS(), Zone::DELEGATION,
+             true, rr_child_ns_);
+
+    // finding NS for the apex (origin) node.  This must not be confused
+    // with delegation due to the existence of an NS RR.
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+
+    // unusual case of "nested delegation": the highest cut should be used.
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+    findTest(Name("www.grand.child.example.org"), RRType::A(),
+             Zone::DELEGATION, true, rr_child_ns_); // note: !rr_grandchild_ns_
+}
+
+TEST_F(MemoryZoneTest, glue) {
+    // install zone data:
+    // a zone cut
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+    // glue for this cut
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
+    // a nested zone cut (unusual)
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_ns_)));
+    // glue under the deeper zone cut
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_grandchild_glue_)));
+
+    // by default glue is hidden due to the zone cut
+    findTest(child_glue_name_, RRType::A(), Zone::DELEGATION, true,
+             rr_child_ns_);
+
+
+    // If we do it in the "glue OK" mode, we should find the exact match.
+    findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
+             rr_child_glue_, NULL, Zone::FIND_GLUE_OK);
+
+    // glue OK + NXRRSET case
+    findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
+             ConstRRsetPtr(), NULL, Zone::FIND_GLUE_OK);
+
+    // glue OK + NXDOMAIN case
+    findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
+             true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+
+    // TODO:
+    // glue name would match a wildcard under a zone cut: wildcard match
+    // shouldn't happen under a cut and result must be PARTIALMATCH
+    // (This case cannot be tested yet)
+
+    // nested cut case.  The glue should be found.
+    findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
+             true, rr_grandchild_glue_, NULL, Zone::FIND_GLUE_OK);    
+
+    // A non-existent name in nested cut.  This should result in delegation
+    // at the highest zone cut.
+    findTest(Name("www.grand.child.example.org"), RRType::TXT(),
+             Zone::DELEGATION, true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+}
+
+// Test adding DNAMEs and resulting delegation handling
+// Listing ideas only for now
+TEST_F(MemoryZoneTest, delegationDNAME) {
+    // apex DNAME: allowed by spec.  No DNAME delegation at the apex;
+    // descendants are subject to delegation.
+
+    // Other cases of NS and DNAME mixture are prohibited.
+    // BIND 9 doesn't reject such cases at load time, however.
+
+    // DNAME and ordinary types (allowed by spec)
+}
+
+/**
+ * \brief Test searching.
+ *
+ * Check it finds or does not find correctly and does not throw exceptions.
+ * \todo This doesn't do any kind of CNAME and so on. If it isn't
+ *     directly there, it just tells it doesn't exist.
+ */
+TEST_F(MemoryZoneTest, find) {
+    // Fill some data inside
+    // Now put all the data we have there. It should throw nothing
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_a_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_aaaa_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+
+    // These two should be successful
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+    findTest(ns_name_, RRType::A(), Zone::SUCCESS, true, rr_ns_a_);
+
+    // These domain exist but don't have the provided RRType
+    findTest(origin_, RRType::AAAA(), Zone::NXRRSET);
+    findTest(ns_name_, RRType::NS(), Zone::NXRRSET);
+
+    // These domains don't exist (and one is out of the zone)
+    findTest(Name("nothere.example.org"), RRType::A(), Zone::NXDOMAIN);
+    findTest(Name("example.net"), RRType::A(), Zone::NXDOMAIN);
+}
+
+TEST_F(MemoryZoneTest, load) {
+    // Put some data inside the zone
+    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, zone_.add(rr_ns_)));
+    // Loading with different origin should fail
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/root.zone"), MasterLoadError);
+    // See the original data is still there, survived the exception
+    findTest(origin_, RRType::NS(), Zone::SUCCESS, true, rr_ns_);
+    // Create correct zone
+    MemoryZone rootzone(class_, Name("."));
+    // Try putting something inside
+    EXPECT_NO_THROW(EXPECT_EQ(result::SUCCESS, rootzone.add(rr_ns_aaaa_)));
+    // Load the zone. It should overwrite/remove the above RRset
+    EXPECT_NO_THROW(rootzone.load(TEST_DATA_DIR "/root.zone"));
+
+    // Now see there are some rrsets (we don't look inside, though)
+    findTest(Name("."), RRType::SOA(), Zone::SUCCESS, false, ConstRRsetPtr(),
+        &rootzone);
+    findTest(Name("."), RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
+        &rootzone);
+    findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
+        ConstRRsetPtr(), &rootzone);
+    // But this should no longer be here
+    findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
+        &rootzone);
+
+    // Try loading zone that is wrong in a different way
+    EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
+        MasterLoadError);
+}
+
 }
 }

File diff suppressed because it is too large
+ 85 - 0
src/lib/datasrc/tests/rbtree_unittest.cc


+ 4 - 0
src/lib/datasrc/tests/testdata/duplicate_rrset.zone

@@ -0,0 +1,4 @@
+example.org.    3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org.    3600 IN NS ns1.example.org.
+example.org.    3600 IN MX 10 mail.example.org.
+example.org.    3600 IN NS ns2.example.org.

+ 30 - 2
src/lib/datasrc/zone.h

@@ -100,6 +100,16 @@ public:
         const isc::dns::ConstRRsetPtr rrset;
         const isc::dns::ConstRRsetPtr rrset;
     };
     };
 
 
+    /// Find options.
+    ///
+    /// The option values are used as a parameter for \c find().
+    /// These are values of a bitmask type.  Bitwise operations can be
+    /// performed on these values to express compound options.
+    enum FindOptions {
+        FIND_DEFAULT = 0,       ///< The default options
+        FIND_GLUE_OK = 1        ///< Allow search under a zone cut
+    };
+
     ///
     ///
     /// \name Constructors and Destructor.
     /// \name Constructors and Destructor.
     ///
     ///
@@ -150,6 +160,17 @@ public:
     /// - If the search name matches a delegation point of DNAME, it returns
     /// - If the search name matches a delegation point of DNAME, it returns
     ///   the code of \c DNAME and that DNAME RR.
     ///   the code of \c DNAME and that DNAME RR.
     ///
     ///
+    /// The \c options parameter specifies customized behavior of the search.
+    /// Their semantics is as follows:
+    /// - \c GLUE_OK Allow search under a zone cut.  By default the search
+    ///   will stop once it encounters a zone cut.  If this option is specified
+    ///   it remembers information about the highest zone cut and continues
+    ///   the search until it finds an exact match for the given name or it
+    ///   detects there is no exact match.  If an exact match is found,
+    ///   RRsets for that name are searched just like the normal case;
+    ///   otherwise, if the search has encountered a zone cut, \c DELEGATION
+    ///   with the information of the highest zone cut will be returned.
+    ///
     /// A derived version of this method may involve internal resource
     /// A derived version of this method may involve internal resource
     /// allocation, especially for constructing the resulting RRset, and may
     /// allocation, especially for constructing the resulting RRset, and may
     /// throw an exception if it fails.
     /// throw an exception if it fails.
@@ -162,9 +183,12 @@ public:
     ///
     ///
     /// \param name The domain name to be searched for.
     /// \param name The domain name to be searched for.
     /// \param type The RR type to be searched for.
     /// \param type The RR type to be searched for.
+    /// \param options The search options.
     /// \return A \c FindResult object enclosing the search result (see above).
     /// \return A \c FindResult object enclosing the search result (see above).
     virtual FindResult find(const isc::dns::Name& name,
     virtual FindResult find(const isc::dns::Name& name,
-                            const isc::dns::RRType& type) const = 0;
+                            const isc::dns::RRType& type,
+                            const FindOptions options
+                            = FIND_DEFAULT) const = 0;
     //@}
     //@}
 };
 };
 
 
@@ -177,4 +201,8 @@ typedef boost::shared_ptr<const Zone> ConstZonePtr;
 }
 }
 }
 }
 
 
-#endif
+#endif  // __ZONE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 0
src/lib/datasrc/zonetable.cc

@@ -89,6 +89,8 @@ struct ZoneTable::ZoneTableImpl {
             // Can Not Happen
             // Can Not Happen
             default:
             default:
                 assert(0);
                 assert(0);
+                // Because of warning
+                return (FindResult(result::NOTFOUND, ConstZonePtr()));
         }
         }
 
 
         // Can Not Happen (remember, NOTFOUND is handled)
         // Can Not Happen (remember, NOTFOUND is handled)

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

@@ -25,7 +25,7 @@ namespace isc {
 namespace dns {
 namespace dns {
 class Name;
 class Name;
 class RRClass;
 class RRClass;
-};
+}
 
 
 namespace datasrc {
 namespace datasrc {
 
 

+ 7 - 3
src/lib/dns/python/tests/Makefile.am

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = edns_python_test.py
 PYTESTS = edns_python_test.py
 PYTESTS += message_python_test.py
 PYTESTS += message_python_test.py
 PYTESTS += messagerenderer_python_test.py
 PYTESTS += messagerenderer_python_test.py
@@ -22,14 +23,17 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 endif
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
 	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 8 - 3
src/lib/python/isc/cc/tests/Makefile.am

@@ -1,16 +1,21 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@          
+
 PYTESTS = message_test.py data_test.py session_test.py
 PYTESTS = message_test.py data_test.py session_test.py
 # NOTE: test_session.py is to be run manually, so not automated.
 # NOTE: test_session.py is to be run manually, so not automated.
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += sendcmd.py
 EXTRA_DIST += sendcmd.py
 EXTRA_DIST += test_session.py
 EXTRA_DIST += test_session.py
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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 \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_socket.sock \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_socket.sock \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 10 - 9
src/lib/python/isc/config/module_spec.py

@@ -339,13 +339,14 @@ def _validate_spec_list(module_spec, full, data, errors):
 
 
     # check if there are items in our data that are not in the
     # check if there are items in our data that are not in the
     # specification
     # specification
-    for item_name in data:
-        found = False
-        for spec_item in module_spec:
-            if spec_item["item_name"] == item_name:
-                found = True
-        if not found:
-            if errors != None:
-                errors.append("unknown item " + item_name)
-            validated = False
+    if data is not None:
+        for item_name in data:
+            found = False
+            for spec_item in module_spec:
+                if spec_item["item_name"] == item_name:
+                    found = True
+            if not found:
+                if errors != None:
+                    errors.append("unknown item " + item_name)
+                validated = False
     return validated
     return validated

+ 7 - 3
src/lib/python/isc/config/tests/Makefile.am

@@ -1,16 +1,20 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = ccsession_test.py cfgmgr_test.py config_data_test.py
 PYTESTS = ccsession_test.py cfgmgr_test.py config_data_test.py
 PYTESTS += module_spec_test.py
 PYTESTS += module_spec_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += unittest_fakesession.py
 EXTRA_DIST += unittest_fakesession.py
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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 \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
 	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
 	CONFIG_WR_TESTDATA_PATH=$(abs_top_builddir)/src/lib/config/tests/testdata \
 	CONFIG_WR_TESTDATA_PATH=$(abs_top_builddir)/src/lib/config/tests/testdata \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 4 - 0
src/lib/python/isc/config/tests/module_spec_test.py

@@ -109,6 +109,9 @@ class TestModuleSpec(unittest.TestCase):
         return dd.validate_command(cmd_name, params)
         return dd.validate_command(cmd_name, params)
 
 
     def test_command_validation(self):
     def test_command_validation(self):
+        # tests for a command that doesn't take an argument
+        self.assertEqual(True, self.read_spec_file("spec2.spec").validate_command("shutdown", None));
+        self.assertEqual(False, self.read_spec_file("spec2.spec").validate_command("shutdown", '{"val": 1}'));
         self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_1.data", 'cmd1'))
         self.assertEqual(True, self.validate_command_params("spec27.spec", "data22_1.data", 'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
@@ -321,6 +324,7 @@ class TestModuleSpec(unittest.TestCase):
                }]
                }]
 
 
         errors = []
         errors = []
+        self.assertEqual(True, isc.config.module_spec._validate_spec_list(spec, True, None, None))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, None))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, None))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, errors))
         self.assertEqual(False, isc.config.module_spec._validate_spec_list(spec, True, { 'does_not_exist': 1 }, errors))
         self.assertEqual(['unknown item does_not_exist'], errors)
         self.assertEqual(['unknown item does_not_exist'], errors)

+ 7 - 3
src/lib/python/isc/datasrc/tests/Makefile.am

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = master_test.py
 PYTESTS = master_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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 \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/lib/python/isc/log/tests/Makefile.am

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = log_test.py
 PYTESTS = log_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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 \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/lib/python/isc/net/tests/Makefile.am

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = addr_test.py parse_test.py
 PYTESTS = addr_test.py parse_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/lib/python/isc/notify/tests/Makefile.am

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = notify_out_test.py
 PYTESTS = notify_out_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
@@ -8,13 +9,16 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 endif
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 7 - 3
src/lib/python/isc/util/tests/Makefile.am

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = process_test.py socketserver_mixin_test.py
 PYTESTS = process_test.py socketserver_mixin_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage 
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+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/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 5 - 0
src/lib/python/isc/utils/Makefile.am

@@ -0,0 +1,5 @@
+SUBDIRS = tests
+
+python_PYTHON = __init__.py process.py
+
+pythondir = $(pyexecdir)/isc/utils

+ 0 - 0
src/lib/python/isc/utils/__init__.py


+ 37 - 0
src/lib/python/isc/utils/process.py

@@ -0,0 +1,37 @@
+# Copyright (C) 2010  CZ NIC
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Module to manipulate the python processes.
+
+It contains only function to rename the process, which is currently
+wrapper around setproctitle library. Does not fail if the setproctitle
+module is missing, but does nothing in that case.
+"""
+try:
+    from setproctitle import setproctitle
+except ImportError:
+    def setproctitle(_): pass
+import sys
+import os.path
+
+"""
+Rename the current process to given name (so it can be found in ps).
+If name is None, use zero'th command line argument.
+"""
+def rename(name=None):
+    if name is None:
+        name = os.path.basename(sys.argv[0])
+    setproctitle(name)

+ 16 - 0
src/lib/python/isc/utils/tests/Makefile.am

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

+ 39 - 0
src/lib/python/isc/utils/tests/process_test.py

@@ -0,0 +1,39 @@
+# Copyright (C) 2010  CZ NIC
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Tests for isc.utils.process."""
+import unittest
+import isc.utils.process
+run_tests = True
+try:
+    import setproctitle
+except ImportError:
+    run_tests = False
+
+class TestRename(unittest.TestCase):
+    """Testcase for isc.process.rename."""
+    def __get_self_name(self):
+        return setproctitle.getproctitle()
+
+    @unittest.skipIf(not run_tests, "Setproctitle not installed, not testing")
+    def test_rename(self):
+        """Test if the renaming function works."""
+        isc.utils.process.rename("rename-test")
+        self.assertEqual("rename-test", self.__get_self_name())
+        isc.utils.process.rename()
+        self.assertEqual("process_test.py", self.__get_self_name())
+
+if __name__ == "__main__":
+    unittest.main()

+ 3 - 0
src/lib/testutils/testdata/Makefile.am

@@ -4,6 +4,7 @@ BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
 BUILT_SOURCES += queryBadEDNS_fromWire.wire shortanswer_fromWire.wire
 BUILT_SOURCES += queryBadEDNS_fromWire.wire shortanswer_fromWire.wire
 BUILT_SOURCES += simplequery_fromWire.wire simpleresponse_fromWire.wire
 BUILT_SOURCES += simplequery_fromWire.wire simpleresponse_fromWire.wire
+BUILT_SOURCES += iquery_fromWire.wire iquery_response_fromWire.wire
 
 
 # NOTE: keep this in sync with real file listing
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
 # so is included in tarball
@@ -18,6 +19,8 @@ EXTRA_DIST += shortquestion_fromWire
 EXTRA_DIST += shortresponse_fromWire
 EXTRA_DIST += shortresponse_fromWire
 EXTRA_DIST += simplequery_fromWire.spec
 EXTRA_DIST += simplequery_fromWire.spec
 EXTRA_DIST += simpleresponse_fromWire.spec
 EXTRA_DIST += simpleresponse_fromWire.spec
+EXTRA_DIST += iquery_fromWire.spec iquery_response_fromWire.spec
+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

+ 3 - 0
src/lib/testutils/testdata/example.com.zone

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

+ 3 - 0
src/lib/testutils/testdata/example.net.zone

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

+ 3 - 0
src/lib/testutils/testdata/example.org.zone

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

+ 3 - 0
src/lib/testutils/testdata/example.zone

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

+ 8 - 0
src/lib/testutils/testdata/iquery_fromWire.spec

@@ -0,0 +1,8 @@
+#
+# An IQUERY message
+#
+
+[header]
+opcode: iquery
+[question]
+# use default

+ 9 - 0
src/lib/testutils/testdata/iquery_response_fromWire.spec

@@ -0,0 +1,9 @@
+#
+# A response to IQUERY message (NOTIMP)
+#
+
+[header]
+qr: response
+opcode: iquery
+rcode: notimp
+qdcount: 0