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
 	b10-auth: added a configuration interface to support in memory
 	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
 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
 website at http://bind10.isc.org/
@@ -93,26 +93,55 @@ Then run "make check" to run these tests.
 
 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
-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:
  
   ./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:
 
   make coverage
-	Does the following:
+	Does the clean, perform, and report targets for C++ and Python.
 
   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
-	Runs the C++ tests (using googletests framework).
+	Runs the C++ (using the googletests framework) and Python
+	tests.
 
   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
 

+ 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_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,
 [  --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}"
 	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
 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.]))
 CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(BOOST_INCLUDES)
@@ -731,7 +751,8 @@ Features:
 
 Developer:
   Google Tests:  $gtest_path
-  Code Coverage: $USE_LCOV
+  C++ Code Coverage: $USE_LCOV
+  Python Code Coverage: $USE_PYCOVERAGE
   Generate Manuals:  $enable_man
 
 END

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

@@ -42,9 +42,8 @@
 
     <note>
       <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>
     </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 += config.cc config.h
 b10_auth_SOURCES += common.h
+b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.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_description": "Shut down authoritative DNS server",
         "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/auth_srv.h>
 #include <auth/query.h>
+#include <auth/statistics.h>
 
 using namespace std;
 
@@ -100,6 +101,9 @@ public:
 
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
+
+    /// Query counters for statistics
+    AuthCounters counters_;
 private:
     std::string db_file_;
 
@@ -111,6 +115,9 @@ private:
 
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
+
+    /// Increment query counter
+    void incCounter(const int protocol);
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
@@ -118,6 +125,7 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     config_session_(NULL), verbose_mode_(false),
     xfrin_session_(NULL),
     memory_datasrc_class_(RRClass::IN()),
+    counters_(verbose_mode_),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
 {
@@ -154,33 +162,20 @@ private:
     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 {
 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
@@ -294,6 +289,11 @@ AuthSrv::setConfigSession(ModuleCCSession* config_session) {
     impl_->config_session_ = config_session;
 }
 
+void
+AuthSrv::setStatisticsSession(AbstractSession* statistics_session) {
+    impl_->counters_.setStatisticsSession(statistics_session);
+}
+
 ModuleCCSession*
 AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
@@ -429,6 +429,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, MessagePtr message,
     message->setHeaderFlag(Message::HEADERFLAG_AA);
     message->setRcode(Rcode::NOERROR());
 
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (remote_edns) {
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         local_edns->setDNSSECAwareness(dnssec_ok);
@@ -476,6 +479,9 @@ bool
 AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, MessagePtr message,
                               OutputBufferPtr buffer)
 {
+    // Increment query counter.
+    incCounter(io_message.getSocket().getProtocol());
+
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         if (verbose_mode_) {
             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);
 }
 
+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
 AuthSrvImpl::setDbFile(ConstElementPtr config) {
     ConstElementPtr answer = isc::config::createAnswer();
@@ -670,3 +689,12 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
         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 <asiolink/asiolink.h>
+#include <auth/statistics.h>
 
 namespace isc {
 namespace datasrc {
@@ -62,6 +63,7 @@ class AuthSrvImpl;
 ///
 /// The design of this class is still in flux.  It's quite likely to change
 /// in future versions.
+///
 class AuthSrv {
     ///
     /// \name Constructors, Assignment Operator and Destructor.
@@ -96,6 +98,8 @@ public:
     /// \param message Pointer to the \c Message object
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param server Pointer to the \c DNSServer
+    ///
+    /// \throw isc::Unexpected Protocol type of \a message is unexpected
     void processMessage(const asiolink::IOMessage& io_message,
                         isc::dns::MessagePtr message,
                         isc::dns::OutputBufferPtr buffer,
@@ -281,6 +285,49 @@ public:
     void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
                           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:
     AuthSrvImpl* impl_;
     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 += ../auth_srv.h ../auth_srv.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/exceptions/libexceptions.la

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

@@ -33,6 +33,7 @@
 #include <xfr/xfrout_client.h>
 
 #include <auth/auth_srv.h>
+#include <auth/config.h>
 #include <auth/query.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
 class DummyServer : public DNSServer {
     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() {
-            return new DummyServer(*this);
+            return (new DummyServer(*this));
         }
 };
 
 class QueryBenchMark {
-private:
+protected:
     // Maintain dynamically generated objects via shared pointers because
     // QueryBenchMark objects will be copied.
     typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
+private:
     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,
                    OutputBufferPtr buffer) :
-        server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
+        server_(new AuthSrv(enable_cache, xfrout_client)),
         queries_(queries),
         query_message_(query_message),
         buffer_(buffer),
@@ -78,13 +80,8 @@ public:
         dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP,
                                                         IOAddress("192.0.2.1"),
                                                         5300)))
-    {
-        if (cache_slots >= 0) {
-            server_->setCacheSlots(cache_slots);
-        }
-        server_->updateConfig(Element::fromJSON("{\"database_file\": \"" +
-                                                string(datasrc_file) + "\"}"));
-    }
+    {}
+public:
     unsigned int run() {
         BenchQueries::const_iterator query;
         const BenchQueries::const_iterator query_end = queries_.end();
@@ -93,14 +90,16 @@ public:
             IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
                                  *dummy_endpoint);
             query_message_->clear(Message::PARSE);
+            buffer_->clear();
             server_->processMessage(io_message, query_message_, buffer_,
-                &server);
+                                    &server);
         }
 
         return (queries_.size());
     }
-private:
+protected:
     AuthSrvPtr server_;
+private:
     const BenchQueries& queries_;
     MessagePtr query_message_;
     OutputBufferPtr buffer_;
@@ -108,26 +107,92 @@ private:
     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 bench {
 template<>
 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 {
+const int ITERATION_DEFAULT = 1;
+enum DataSrcType {
+    SQLITE3,
+    MEMORY
+};
+
 void
 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;
     exit (1);
 }
@@ -136,12 +201,20 @@ usage() {
 int
 main(int argc, char* argv[]) {
     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) {
         case 'n':
             iteration = atoi(optarg);
             break;
+        case 't':
+            opt_datasrc_type = optarg;
+            break;
+        case 'o':
+            origin = optarg;
+            break;
         case '?':
         default:
             usage();
@@ -155,6 +228,21 @@ main(int argc, char* argv[]) {
     const char* const datasrc_file = argv[0];
     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;
     loadQueryData(query_data_file, queries, RRClass::IN());
     OutputBufferPtr buffer(new OutputBuffer(4096));
@@ -162,32 +250,46 @@ main(int argc, char* argv[]) {
 
     cout << "Parameters:" << 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()
          << " 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));
 
-    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));
 
-    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);
 }

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

@@ -151,16 +151,21 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
             isc_throw(AuthConfigError, "Missing zone file for zone: "
                       << 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) {
             isc_throw(AuthConfigError, "zone "<< origin->str()
                       << " 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 <iostream>
 
-#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
 
 #include <exceptions/exceptions.h>
 
@@ -62,6 +62,10 @@ static bool verbose_mode = false;
 static const string PROGRAM = "Auth";
 static const char* DNSPORT = "5300";
 
+// Note: this value must be greater than 0.
+// TODO: make it configurable via command channel.
+const uint32_t STATISTICS_SEND_INTERVAL_SEC = 60;
+
 /* need global var for config/command handlers.
  * todo: turn this around, and put handlers in the authserver
  * class itself? */
@@ -84,6 +88,12 @@ my_command_handler(const string& command, ConstElementPtr args) {
         answer = createAnswer(0, args);
     } else if (command == "shutdown") {
         io_service.stop();
+    } else if (command == "sendstats") {
+        if (verbose_mode) {
+            cerr << "[b10-auth] command 'sendstats' received" << endl;
+        }
+        assert(auth_server != NULL);
+        auth_server->submitStatistics();
     }
 
     return (answer);
@@ -103,6 +113,12 @@ usage() {
     cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
+
+void
+statisticsTimerCallback(AuthSrv* auth_server) {
+    assert(auth_server != NULL);
+    auth_server->submitStatistics();
+}
 } // end of anonymous namespace
 
 int
@@ -168,7 +184,10 @@ main(int argc, char* argv[]) {
     // XXX: we should eventually pass io_service here.
     Session* cc_session = NULL;
     Session* xfrin_session = NULL;
+    Session* statistics_session = NULL;
+    IntervalTimer* itimer = NULL;
     bool xfrin_session_established = false; // XXX (see Trac #287)
+    bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
     string xfrout_socket_path;
     if (getenv("B10_FROM_BUILD") != NULL) {
@@ -230,12 +249,19 @@ main(int argc, char* argv[]) {
         xfrin_session_established = true;
         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
         // auth_server before io_service while Session needs io_service.
         // In a next step of refactoring we should make asiolink independent
         // from auth_server, and create io_service, auth_server, and
         // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
+        auth_server->setStatisticsSession(statistics_session);
 
         // Configure the server.  configureAuthServer() is expected to install
         // 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());
         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;
         io_service.run();
 
@@ -254,10 +288,16 @@ main(int argc, char* argv[]) {
         ret = 1;
     }
 
+    if (statistics_session_established) {
+        statistics_session->disconnect();
+    }
+
     if (xfrin_session_established) {
         xfrin_session->disconnect();
     }
 
+    delete itimer;
+    delete statistics_session;
     delete xfrin_session;
     delete config_session;
     delete cc_session;

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

@@ -26,6 +26,24 @@ namespace isc {
 namespace auth {
 
 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 {
     bool keep_doing = true;
     response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
@@ -59,12 +77,14 @@ Query::process() const {
                 // TODO : add NS to authority section, fill in additional section.
                 break;
             case Zone::NXDOMAIN:
+                // Just empty answer with SOA in authority section
                 response_.setRcode(Rcode::NXDOMAIN());
-                // TODO : add SOA to authority section
+                putSOA(*result.zone);
                 break;
             case Zone::NXRRSET:
+                // Just empty answer with SOA in authority section
                 response_.setRcode(Rcode::NOERROR());
-                // TODO : add SOA to authority section
+                putSOA(*result.zone);
                 break;
             case Zone::CNAME:
             case Zone::DNAME:

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

@@ -14,6 +14,8 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <exceptions/exceptions.h>
+
 namespace isc {
 namespace dns {
 class Message;
@@ -23,6 +25,7 @@ class RRType;
 
 namespace datasrc {
 class MemoryDataSrc;
+class Zone;
 }
 
 namespace auth {
@@ -100,15 +103,46 @@ public:
     /// providing compatible behavior may have its own benefit, so this point
     /// should be revisited later.
     ///
-    /// Right now this method never throws an exception, but it may in a
-    /// future version.
+    /// 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;
 
+    /// \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:
     const isc::datasrc::MemoryDataSrc& memory_datasrc_;
     const isc::dns::Name& qname_;
     const isc::dns::RRType& qtype_;
     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 += ../change_user.h ../change_user.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 += config_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
+run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

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

@@ -15,14 +15,30 @@
 // $Id$
 
 #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 <auth/auth_srv.h>
+#include <auth/statistics.h>
 
 #include <dns/tests/unittest_util.h>
 #include <testutils/srv_test.h>
 
+using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::xfr;
 using namespace asiolink;
@@ -41,16 +57,114 @@ class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() : server(true, xfrout), rrclass(RRClass::IN()) {
         server.setXfrinSession(&notify_session);
+        server.setStatisticsSession(&statistics_session);
     }
     virtual void processMessage() {
         server.processMessage(*io_message, parse_message, response_obuffer,
                               &dnsserv);
     }
+    MockSession statistics_session;
     MockXfroutClient xfrout;
     AuthSrv server;
     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.
 TEST_F(AuthSrvTest, unsupportedRequest) {
     unsupportedRequest();
@@ -440,4 +554,90 @@ TEST_F(AuthSrvTest, cacheSlots) {
     server.setCacheSlots(0);
     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 <dns/rrclass.h>
+#include <dns/masterload.h>
 
 #include <cc/data.h>
 
@@ -143,33 +144,42 @@ TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
 }
 
 TEST_F(MemoryDatasrcConfigTest, addOneZone) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"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());
+    // 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) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
-                      "               \"file\": \"example.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.zone\"},"
                       "              {\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"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());
 }
 
 TEST_F(MemoryDatasrcConfigTest, replace) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"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(isc::datasrc::result::SUCCESS,
               server.getMemoryDataSrc(rrclass)->findZone(
@@ -179,31 +189,69 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
     // should replace the old one.
     delete parser;
     parser = createAuthConfigParser(server, "datasources"); 
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.org\","
-                      "               \"file\": \"example.org.zone\"},"
+                      "               \"file\": \"" TEST_DATA_DIR
+                      "/example.org.zone\"},"
                       "              {\"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(isc::datasrc::result::NOTFOUND,
               server.getMemoryDataSrc(rrclass)->findZone(
                   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) {
-    parser->build(Element::fromJSON(
+    EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"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());
 
     delete parser;
     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));
 }
 
@@ -212,9 +260,11 @@ TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\","
-                         "               \"file\": \"example.zone\"},"
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.zone\"},"
                          "              {\"origin\": \"example.com\","
-                         "               \"file\": \"example.com.zone\"}]}]")),
+                         "               \"file\": \"" TEST_DATA_DIR
+                         "/example.com.zone\"}]}]")),
                  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::auth;
 
+namespace {
+
 RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
                                       RRClass::IN(), RRType::A(),
                                       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.
 // It is a derived class of Zone, and simply hardcode the results of find()
 // return SUCCESS for "www.example.com",
@@ -41,16 +45,20 @@ namespace {
 // else return DNAME
 class MockZone : public Zone {
 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::RRClass& getClass() const;
 
     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:
     Name origin_;
+    bool has_SOA_;
 };
 
 const Name&
@@ -64,20 +72,24 @@ MockZone::getClass() const {
 }
 
 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
     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")) {
-        return FindResult(DELEGATION, RRsetPtr());
+        return (FindResult(DELEGATION, RRsetPtr()));
     } else if (name == Name("nxdomain.example.com")) {
-        return FindResult(NXDOMAIN, RRsetPtr());
+        return (FindResult(NXDOMAIN, RRsetPtr()));
     } else if (name == Name("nxrrset.example.com")) {
-        return FindResult(NXRRSET, RRsetPtr());
+        return (FindResult(NXRRSET, RRsetPtr()));
     } else if (name == Name("cname.example.com")) {
-        return FindResult(CNAME, RRsetPtr());
+        return (FindResult(CNAME, RRsetPtr()));
     } else {
-        return FindResult(DNAME, RRsetPtr());
+        return (FindResult(DNAME, RRsetPtr()));
     }
 }
 
@@ -106,7 +118,7 @@ TEST_F(QueryTest, noZone) {
 }
 
 TEST_F(QueryTest, matchZone) {
-    // match qname, normal query
+    // add a matching zone.
     memory_datasrc.addZone(ZonePtr(new MockZone()));
     query.process();
     EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
@@ -119,12 +131,39 @@ TEST_F(QueryTest, matchZone) {
     Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
     nxdomain_query.process();
     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
     const Name nxrrset_name(Name("nxrrset.example.com"));
     Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
     nxrrset_query.process();
     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) {
@@ -136,4 +175,5 @@ TEST_F(QueryTest, noMatchZone) {
     nomatch_query.process();
     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
 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
 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/bin/bind10 \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = bindctl_test.py
 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
 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_srcdir)/src/bin \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = b10-cfgmgr_test.py
 
 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
 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/bin/cfgmgr \
-	$(PYCOVERAGE) $(abs_builddir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done

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

@@ -1,14 +1,18 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = cmdctl_test.py
 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
 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/bin/cmdctl \
 	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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 += include.db
 EXTRA_DIST += inclsub.db
@@ -14,12 +13,8 @@ EXTRA_DIST += ttl2.db
 EXTRA_DIST += ttlext.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
 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 += formerr1.db 
 EXTRA_DIST += formerr2.db
@@ -14,12 +12,8 @@ EXTRA_DIST += keyerror3.db
 EXTRA_DIST += originerr1.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
 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
 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
 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_builddir)/src/bin/msgq:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 

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

@@ -2,12 +2,12 @@
 .\"     Title: b10-recurse
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: September 16, 2010
+.\"      Date: December 27, 2010
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-RECURSE" "8" "September 16, 2010" "BIND10" "BIND10"
+.TH "B10\-RECURSE" "8" "December 27, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
 b10-recurse \- Recursive DNS server
 .SH "SYNOPSIS"
 .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"
 .PP
 The
@@ -60,55 +60,6 @@ This prototype version only supports forwarding\&. Future versions will introduc
 .PP
 The arguments are as follows:
 .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
 .RS 4
 The user name of the
@@ -130,7 +81,6 @@ None\&.
 
 \fBb10-cfgmgr\fR(8),
 \fBb10-cmdctl\fR(8),
-\fBb10-loadzone\fR(8),
 \fBb10-msgq\fR(8),
 \fBbind10\fR(8),
 BIND 10 Guide\&.

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

@@ -21,7 +21,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>September 16, 2010</date>
+    <date>December 27, 2010</date>
   </refentryinfo>
 
   <refmeta>
@@ -45,11 +45,6 @@
   <refsynopsisdiv>
     <cmdsynopsis>
       <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>-v</option></arg>
     </cmdsynopsis>
@@ -89,59 +84,6 @@
     <para>The arguments are as follows:</para>
 
     <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>
         <term><option>-u <replaceable>username</replaceable></option></term>
@@ -187,9 +129,6 @@
         <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
-        <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
         <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>

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

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

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

@@ -1,13 +1,17 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = process_rename_test.py
 # .py will be generated by configure, so we don't have to include it
 # 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
 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) $(abs_builddir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done

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

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrin_test.py
 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)
 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
 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_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) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrout_test.py
 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)
 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
 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_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) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,14 +1,17 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = zonemgr_test.py
 EXTRA_DIST = $(PYTESTS)
-
 CLEANFILES = initdb.file
 
-# later will have configure option to choose this, like: coverage run --branch
-PYCOVERAGE = $(PYTHON)
 # test using command-line arguments, so use check-local target instead of TESTS
 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_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

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

@@ -26,6 +26,7 @@
 #include <asio.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 #include <boost/shared_ptr.hpp>
 
@@ -374,4 +375,90 @@ RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
          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 <asio/ip/address.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
 
 #include <functional>
 #include <string>
@@ -98,6 +99,7 @@ class io_service;
 namespace asiolink {
 class DNSServiceImpl;
 struct IOServiceImpl;
+struct IntervalTimerImpl;
 
 /// \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
@@ -233,7 +235,7 @@ public:
     /// that share the same \c io_service with the authoritative server.
     /// It will eventually be removed once the wrapper interface is
     /// 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:
     DNSServiceImpl* impl_;
     IOService& io_service_;
@@ -567,6 +569,98 @@ private:
     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
 #endif // __ASIOLINK_H
 

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

@@ -24,6 +24,7 @@
 
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
 
 #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
 // for the tests below.
 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) {
     IOAddress io_address_v4("192.0.2.1");
@@ -742,4 +746,247 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
     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.
 
 #include <map>
+#include <cassert>
 #include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
@@ -51,12 +54,186 @@ struct MemoryZone::MemoryZoneImpl {
      * that.
      */
     typedef map<RRType, ConstRRsetPtr> Domain;
+    typedef Domain::value_type DomainPair;
     typedef boost::shared_ptr<Domain> DomainPtr;
     // The tree stores domains
     typedef RBTree<Domain> DomainTree;
     typedef RBNode<Domain> DomainNode;
     // The actual zone data
     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) :
@@ -79,9 +256,27 @@ MemoryZone::getClass() const {
 }
 
 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

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

@@ -58,9 +58,70 @@ public:
     /// \brief Looks up an RRset in the 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,
-                            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:
     /// \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
 ///
 /// 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.
 inline isc::dns::Name
@@ -51,8 +54,9 @@ operator-(const isc::dns::Name& super_name, const isc::dns::Name& sub_name) {
 
 template <typename T>
 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,
 /// 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.
@@ -74,11 +78,11 @@ public:
     friend class RBTree<T>;
     typedef boost::shared_ptr<T> NodeDataPtr;
 
-    /// \name Deonstructor
+    /// \name Destructor
     /// \note it's seems a little strange that constructor is private
     /// but deconstructor left public, the reason is for some smart pointer
     /// like std::auto_ptr, they needs to delete RBNode in sometimes, but
-    /// \code delete *pointer_to_node \codeend shouldn't be called directly
+    /// \code delete *pointer_to_node \endcode shouldn't be called directly
     //@{
     ~RBNode();
     //@}
@@ -111,6 +115,24 @@ public:
     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:
     /// \brief Define rbnode color
@@ -147,6 +169,7 @@ private:
 
     isc::dns::Name     name_;
     NodeDataPtr       data_;
+
     /// the down pointer points to the root node of sub domains of current
     /// domain
     /// \par Adding down pointer to \c RBNode is for two purpose:
@@ -154,6 +177,10 @@ private:
     /// big flat tree into several hierarchy trees
     /// \li It save memory useage, so same label won't be saved several times
     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),
     // dummy name, the value doesn't matter:
     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()),
     color_(RED),
     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>
 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>
 class RBTree : public boost::noncopyable {
     friend class RBNode<T>;
@@ -247,7 +280,7 @@ public:
 
     /// \name Constructor and Destructor
     //@{
-    RBTree();
+    explicit RBTree();
 
     /// \b Note: RBTree is not intended to be inherited so the destructor
     /// is not virtual
@@ -256,13 +289,98 @@ public:
 
     /// \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 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
     /// 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
     //
     /// \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);
     //@}
 
+    /// \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:
     /// \name RBTree balance functions
     //@{
@@ -315,8 +443,11 @@ private:
     /// and node will points to c.b.a
     /// \note parameter up now is not used by any funciton, but we are gonna
     /// need it soon to implement function like remove
+    template <typename CBARG>
     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,
                         unsigned int depth) const;
     /// for indent purpose, add certian mount empty charachter to output stream
@@ -380,30 +511,39 @@ void RBTree<T> ::deleteHelper(RBNode<T> *root) {
     --node_count_;
 }
 
-template <typename T>
+template <typename T> template <typename CBARG>
 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;
-    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
-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;
     RBNode<T>* target_node;
     const typename RBTree<T>::Result ret =
-        findHelper(name, &up_node, &target_node);
+        findHelper(name, &up_node, &target_node, callback, callback_arg);
     if (ret != NOTFOUND) {
         *node = target_node;
     }
     return (ret);
 }
 
-template <typename T>
+template <typename T> template <typename CBARG>
 typename RBTree<T>::Result
-RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_node,
-                      RBNode<T>** target) 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;
 
@@ -431,12 +571,17 @@ RBTree<T>::findHelper(const isc::dns::Name& target_name, const RBNode<T>** up_no
                 node = (compare_result.getOrder() < 0) ?
                     node->left_ : node->right_;
             } else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
-                *up_node = node;
-                name = name - node->name_;
                 if (!node->isEmpty()) {
                     ret = RBTree<T>::PARTIALMATCH;
                     *target = node;
+                    if (callback != NULL && node->callback_required_) {
+                        if ((callback)(*node, callback_arg)) {
+                            break;
+                        }
+                    }
                 }
+                *up_node = node;
+                name = name - node->name_;
                 node = node->down_;
             } else {
                 break;
@@ -531,6 +676,7 @@ RBTree<T>::nodeFission(RBNode<T>& node, const isc::dns::Name& base_name) {
     // after the RBNode creation
     std::auto_ptr<RBNode<T> > down_node(new RBNode<T>(sub_name));
     std::swap(node.data_, down_node->data_);
+    std::swap(node.callback_required_, down_node->callback_required_);
     down_node->down_ = node.down_;
     node.name_ = base_name;
     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/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
 
@@ -25,6 +27,9 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 
 namespace {
+// Commonly used result codes (Who should write the prefix all the time)
+using result::SUCCESS;
+using result::EXIST;
 
 class MemoryDataSrcTest : public ::testing::Test {
 protected:
@@ -136,13 +141,94 @@ public:
     MemoryZoneTest() :
         class_(RRClass::IN()),
         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
-    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
     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
  * as passed parameters.
  */
-TEST_F(MemoryZoneTest, Constructor) {
+TEST_F(MemoryZoneTest, constructor) {
     ASSERT_EQ(class_, zone_.getClass());
     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;
     };
 
+    /// 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.
     ///
@@ -150,6 +160,17 @@ public:
     /// - If the search name matches a delegation point of DNAME, it returns
     ///   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
     /// allocation, especially for constructing the resulting RRset, and may
     /// throw an exception if it fails.
@@ -162,9 +183,12 @@ public:
     ///
     /// \param name The domain name 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).
     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
             default:
                 assert(0);
+                // Because of warning
+                return (FindResult(result::NOTFOUND, ConstZonePtr()));
         }
 
         // Can Not Happen (remember, NOTFOUND is handled)

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

@@ -25,7 +25,7 @@ namespace isc {
 namespace dns {
 class Name;
 class RRClass;
-};
+}
 
 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 += message_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)
 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
 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/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 \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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
 # NOTE: test_session.py is to be run manually, so not automated.
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += sendcmd.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
 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 \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_socket.sock \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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
     # 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

+ 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 += module_spec_test.py
 EXTRA_DIST = $(PYTESTS)
 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
 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 \
 	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/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

+ 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)
 
     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(False, self.validate_command_params("spec27.spec", "data22_2.data",'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_3.data", 'cmd1'))
@@ -321,6 +324,7 @@ class TestModuleSpec(unittest.TestCase):
                }]
 
         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 }, 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
 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
 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/python/isc/log \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,12 +1,16 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = log_test.py
 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
 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/python/isc/log \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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
 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
 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) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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

@@ -1,3 +1,4 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = notify_out_test.py
 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)
 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
 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 \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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
 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
 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) $(abs_srcdir)/$$pytest || exit ; \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	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 += queryBadEDNS_fromWire.wire shortanswer_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
 # so is included in tarball
@@ -18,6 +19,8 @@ EXTRA_DIST += shortquestion_fromWire
 EXTRA_DIST += shortresponse_fromWire
 EXTRA_DIST += simplequery_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.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