Browse Source

sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac292@3255 e5f2f494-b856-4b98-b285-d166d9295462
Jelte Jansen 14 years ago
parent
commit
3c9e7b7662
100 changed files with 3479 additions and 684 deletions
  1. 20 0
      src/bin/auth/benchmarks/Makefile.am
  2. 180 0
      src/bin/auth/benchmarks/query_bench.cc
  3. 26 0
      src/bin/auth/tests/testdata/Makefile.am
  4. 0 13
      src/bin/auth/tests/testdata/badExampleQuery_fromWire
  5. 0 13
      src/bin/auth/tests/testdata/examplequery_fromWire
  6. 0 13
      src/bin/auth/tests/testdata/iqueryresponse_fromWire
  7. 0 17
      src/bin/auth/tests/testdata/multiquestion_fromWire
  8. 0 19
      src/bin/auth/tests/testdata/queryBadEDNS_fromWire
  9. 0 13
      src/bin/auth/tests/testdata/shortanswer_fromWire
  10. 0 13
      src/bin/auth/tests/testdata/simplequery_fromWire
  11. 0 13
      src/bin/auth/tests/testdata/simpleresponse_fromWire
  12. 37 0
      src/bin/stats/Makefile.am
  13. 68 0
      src/bin/stats/b10-stats.8
  14. 124 0
      src/bin/stats/b10-stats.xml
  15. 30 0
      src/bin/stats/run_b10-stats.sh.in
  16. 30 0
      src/bin/stats/run_b10-stats_stub.sh.in
  17. 416 0
      src/bin/stats/stats.py.in
  18. 140 0
      src/bin/stats/stats.spec.pre.in
  19. 155 0
      src/bin/stats/stats_stub.py.in
  20. 0 151
      src/bin/stats/statsd.py
  21. 0 55
      src/bin/stats/statsd.txt
  22. 0 6
      src/bin/stats/test/shutdown.py
  23. 0 178
      src/bin/stats/test/test_agent.py
  24. 0 54
      src/bin/stats/test_total.py
  25. 15 0
      src/bin/stats/tests/Makefile.am
  26. 116 0
      src/bin/stats/tests/b10-stats_stub_test.py
  27. 646 0
      src/bin/stats/tests/b10-stats_test.py
  28. 48 0
      src/bin/stats/tests/fake_time.py
  29. 3 0
      src/bin/stats/tests/isc/Makefile.am
  30. 0 0
      src/bin/stats/tests/isc/__init__.py
  31. 2 0
      src/bin/stats/tests/isc/cc/Makefile.am
  32. 1 0
      src/bin/stats/tests/isc/cc/__init__.py
  33. 127 0
      src/bin/stats/tests/isc/cc/session.py
  34. 2 0
      src/bin/stats/tests/isc/config/Makefile.am
  35. 1 0
      src/bin/stats/tests/isc/config/__init__.py
  36. 114 0
      src/bin/stats/tests/isc/config/ccsession.py
  37. 2 0
      src/bin/stats/tests/isc/utils/Makefile.am
  38. 0 0
      src/bin/stats/tests/isc/utils/__init__.py
  39. 20 0
      src/bin/stats/tests/isc/utils/process.py
  40. 31 0
      src/bin/stats/tests/stats_test.in
  41. 1 0
      src/bin/stats/tests/testdata/Makefile.am
  42. 19 0
      src/bin/stats/tests/testdata/stats_test.spec
  43. 13 0
      src/bin/tests/Makefile.am
  44. 3 0
      src/bin/tests/README
  45. 61 0
      src/bin/tests/process_rename_test.py.in
  46. 0 6
      src/bin/xfrin/xfrin.spec.pre.in
  47. 91 0
      src/bin/zonemgr/b10-zonemgr.8
  48. 205 0
      src/bin/zonemgr/b10-zonemgr.xml
  49. 1 0
      src/lib/config/ccsession.cc
  50. 18 11
      src/lib/config/ccsession.h
  51. 13 11
      src/lib/config/config_data.h
  52. 1 0
      src/lib/config/module_spec.cc
  53. 26 19
      src/lib/config/module_spec.h
  54. 0 8
      src/lib/config/testdata/Makefile.am
  55. 50 0
      src/lib/config/tests/testdata/Makefile.am
  56. 0 0
      src/lib/config/tests/testdata/b10-config-bad1.db
  57. 0 0
      src/lib/config/tests/testdata/b10-config-bad2.db
  58. 0 0
      src/lib/config/tests/testdata/b10-config-bad3.db
  59. 0 0
      src/lib/config/tests/testdata/b10-config-bad4.db
  60. 0 0
      src/lib/config/tests/testdata/b10-config.db.master
  61. 0 0
      src/lib/config/tests/testdata/data22_1.data
  62. 0 0
      src/lib/config/tests/testdata/data22_2.data
  63. 0 0
      src/lib/config/tests/testdata/data22_3.data
  64. 0 0
      src/lib/config/tests/testdata/data22_4.data
  65. 0 0
      src/lib/config/tests/testdata/data22_5.data
  66. 0 0
      src/lib/config/tests/testdata/data22_6.data
  67. 0 0
      src/lib/config/tests/testdata/data22_7.data
  68. 0 0
      src/lib/config/tests/testdata/data22_8.data
  69. 0 0
      src/lib/config/tests/testdata/spec1.spec
  70. 0 0
      src/lib/config/tests/testdata/spec10.spec
  71. 0 0
      src/lib/config/tests/testdata/spec11.spec
  72. 0 0
      src/lib/config/tests/testdata/spec12.spec
  73. 0 0
      src/lib/config/tests/testdata/spec13.spec
  74. 0 0
      src/lib/config/tests/testdata/spec14.spec
  75. 0 0
      src/lib/config/tests/testdata/spec15.spec
  76. 0 0
      src/lib/config/tests/testdata/spec16.spec
  77. 0 0
      src/lib/config/tests/testdata/spec17.spec
  78. 0 0
      src/lib/config/tests/testdata/spec18.spec
  79. 0 0
      src/lib/config/tests/testdata/spec19.spec
  80. 0 0
      src/lib/config/tests/testdata/spec2.spec
  81. 0 0
      src/lib/config/tests/testdata/spec20.spec
  82. 0 0
      src/lib/config/tests/testdata/spec21.spec
  83. 0 0
      src/lib/config/tests/testdata/spec22.spec
  84. 0 0
      src/lib/config/tests/testdata/spec23.spec
  85. 0 0
      src/lib/config/tests/testdata/spec24.spec
  86. 0 0
      src/lib/config/tests/testdata/spec25.spec
  87. 0 0
      src/lib/config/tests/testdata/spec26.spec
  88. 0 0
      src/lib/config/tests/testdata/spec27.spec
  89. 0 0
      src/lib/config/tests/testdata/spec28.spec
  90. 0 0
      src/lib/config/tests/testdata/spec3.spec
  91. 0 0
      src/lib/config/tests/testdata/spec4.spec
  92. 0 0
      src/lib/config/tests/testdata/spec5.spec
  93. 0 0
      src/lib/config/tests/testdata/spec6.spec
  94. 0 0
      src/lib/config/tests/testdata/spec7.spec
  95. 0 0
      src/lib/config/tests/testdata/spec8.spec
  96. 0 0
      src/lib/config/tests/testdata/spec9.spec
  97. 174 0
      src/lib/dns/edns.cc
  98. 449 0
      src/lib/dns/edns.h
  99. 0 71
      src/lib/dns/message_test.py
  100. 0 0
      src/lib/dns/opcode.cc

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

@@ -0,0 +1,20 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = query_bench
+query_bench_SOURCES = query_bench.cc
+query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
+
+query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
+query_bench_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+query_bench_LDADD += $(top_builddir)/src/lib/bench/libbench.la
+query_bench_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+query_bench_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+query_bench_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+query_bench_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+query_bench_LDADD += $(top_builddir)/src/bin/auth/libasio_link.a
+query_bench_LDADD += $(SQLITE_LIBS)

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

@@ -0,0 +1,180 @@
+// 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 <stdlib.h>
+
+#include <iostream>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <bench/benchmark.h>
+#include <bench/benchmark_util.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrclass.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <auth/auth_srv.h>
+#include <auth/asio_link.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::xfr;
+using namespace isc::bench;
+using namespace asio_link;
+
+namespace {
+// Commonly used constant:
+XfroutClient xfrout_client("dummy_path"); // path doesn't matter
+
+class QueryBenchMark {
+private:
+    // Maintain dynamically generated objects via shared pointers because
+    // QueryBenchMark objects will be copied.
+    typedef boost::shared_ptr<AuthSrv> AuthSrvPtr;
+    typedef boost::shared_ptr<const IOEndpoint> IOEndpointPtr;
+public:
+    QueryBenchMark(const int cache_slots, const char* const datasrc_file,
+                   const BenchQueries& queries, Message& query_message,
+                   MessageRenderer& renderer) :
+        server_(new AuthSrv(cache_slots >= 0 ? true : false, xfrout_client)),
+        queries_(queries),
+        query_message_(query_message),
+        renderer_(renderer),
+        dummy_socket(IOSocket::getDummyUDPSocket()),
+        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) + "\"}"));
+    }
+    unsigned int run() {
+        BenchQueries::const_iterator query;
+        const BenchQueries::const_iterator query_end = queries_.end();
+        for (query = queries_.begin(); query != query_end; ++query) {
+            IOMessage io_message(&(*query)[0], (*query).size(), dummy_socket,
+                                 *dummy_endpoint);
+            query_message_.clear(Message::PARSE);
+            renderer_.clear();
+            server_->processMessage(io_message, query_message_, renderer_);
+        }
+
+        return (queries_.size());
+    }
+private:
+    AuthSrvPtr server_;
+    const BenchQueries& queries_;
+    Message& query_message_;
+    MessageRenderer& renderer_;
+    IOSocket& dummy_socket;
+    IOEndpointPtr dummy_endpoint;
+};
+}
+
+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;
+}
+}
+}
+
+namespace {
+void
+usage() {
+    cerr << "Usage: query_bench [-n iterations] datasrc_file query_datafile"
+         << endl;
+    exit (1);
+}
+}
+
+int
+main(int argc, char* argv[]) {
+    int ch;
+    int iteration = 1;
+    while ((ch = getopt(argc, argv, "n:")) != -1) {
+        switch (ch) {
+        case 'n':
+            iteration = atoi(optarg);
+            break;
+        case '?':
+        default:
+            usage();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+    if (argc < 2) {
+        usage();
+    }
+    const char* const datasrc_file = argv[0];
+    const char* const query_data_file = argv[1];
+
+    BenchQueries queries;
+    loadQueryData(query_data_file, queries, RRClass::IN());
+    OutputBuffer buffer(4096);
+    MessageRenderer renderer(buffer);
+    Message message(Message::PARSE);
+
+    cout << "Parameters:" << endl;
+    cout << "  Iterations: " << iteration << endl;
+    cout << "  Data Source: " << datasrc_file << 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,
+                                             renderer));
+
+    cout << "Benchmark enabling Hot Spot Cache with 10*#queries slots "
+         << endl;
+    BenchMark<QueryBenchMark>(iteration,
+                              QueryBenchMark(10 * queries.size(), datasrc_file,
+                                             queries, message, renderer));
+
+    cout << "Benchmark enabling Hot Spot Cache with #queries/2 slots "
+         << endl;
+    BenchMark<QueryBenchMark>(iteration,
+                              QueryBenchMark(queries.size() / 2, datasrc_file,
+                                             queries, message, renderer));
+
+    cout << "Benchmark disabling Hot Spot Cache" << endl;
+    BenchMark<QueryBenchMark>(iteration,
+                              QueryBenchMark(-1, datasrc_file, queries,
+                                             message, renderer));    
+
+    return (0);
+}

+ 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 $@ $<

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from badExampleQuery_fromWire.spec
-###
-
-# 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=broken.example.com QTYPE=AAAA(28) QCLASS=IN(1)
-0662726f6b656e076578616d706c6503636f6d00 001c 0001

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from examplequery_fromWire.spec
-###
-
-# 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=ns.example.com QTYPE=A(1) QCLASS=IN(1)
-026e73076578616d706c6503636f6d00 0001 0001

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from iqueryresponse_fromWire.spec
-###
-
-# Header Section
-# ID=4149 QR=Response Opcode=IQUERY(1) Rcode=NOERROR(0)
-1035 c000
-# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=0
-0001 0000 0000 0000
-
-# Question Section
-# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
-076578616d706c6503636f6d00 0001 0001

+ 0 - 17
src/bin/auth/tests/testdata/multiquestion_fromWire

@@ -1,17 +0,0 @@
-###
-### This data file was auto-generated from multiquestion_fromWire.spec
-###
-
-# Header Section
-# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
-1035 0000
-# QDCNT=2, ANCNT=0, NSCNT=0, ARCNT=0
-0002 0000 0000 0000
-
-# Question Section
-# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
-076578616d706c6503636f6d00 0001 0001
-
-# Question Section
-# QNAME=example.com. QTYPE=AAAA(28) QCLASS=IN(1)
-076578616d706c6503636f6d00 001c 0001

+ 0 - 19
src/bin/auth/tests/testdata/queryBadEDNS_fromWire

@@ -1,19 +0,0 @@
-###
-### This data file was auto-generated from queryBadEDNS_fromWire.spec
-###
-
-# Header Section
-# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
-1035 0000
-# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
-0001 0000 0000 0001
-
-# Question Section
-# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
-076578616d706c6503636f6d00 0001 0001
-
-# EDNS OPT RR
-# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=1 DO=1
-00 0029 1000 0001 8000
-# RDLEN=0
-0000

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from shortanswer_fromWire.spec
-###
-
-# Header Section
-# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
-1035 0000
-# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0
-0001 0001 0000 0000
-
-# Question Section
-# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
-076578616d706c6503636f6d00 0001 0001

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from simplequery_fromWire.spec
-###
-
-# 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=IN(1)
-076578616d706c6503636f6d00 0001 0001

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

@@ -1,13 +0,0 @@
-###
-### This data file was auto-generated from simpleresponse_fromWire.spec
-###
-
-# 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=IN(1)
-076578616d706c6503636f6d00 0001 0001

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

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

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

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

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

@@ -0,0 +1,124 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<!-- $Id$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>Oct 15, 2010</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-stats</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-stats</refname>
+    <refpurpose>BIND 10 statistics module</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-stats</command>
+      <arg><option>-v</option></arg>
+      <arg><option>--verbose</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>
+      The <command>b10-stats</command> is a daemon forked by
+      <command>bind10</command>. Stats module collects statistics data
+      from each module and reports statistics information
+      via <command>bindctl</command>.  It communicates by using the
+      Command Channel by <command>b10-msgq</command> with other
+      modules
+      like <command>bind10</command>, <command>b10-auth</command> and
+      so on. It waits for coming data from other modules, then other
+      modules send data to stats module periodically. Other modules
+      send stats data to stats module independently from
+      implementation of stats module, so the frequency of sending data
+      may not be constant. Stats module collects data and aggregates
+      it.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>OPTIONS</title>
+    <para>The arguments are as follows:</para>
+    <variablelist>
+      <varlistentry>
+        <term><option>-v</option>, <option>--verbose</option></term>
+        <listitem>
+	  <para>
+          This <command>b10-stats</command> switches to verbose
+          mode. It sends verbose messages to STDOUT.
+	  </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+      &mdash; This is a spec file for <command>b10-stats</command>. It
+      contains definitions of statistics items of BIND 10 and commands
+      received vi bindctl.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-stats</command> daemon was initially designed
+      and implemented by Naoki Kambe of JPRS in Oct 2010.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

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

@@ -0,0 +1,30 @@
+#! /bin/sh
+
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+STATS_PATH=@abs_top_builddir@/src/bin/stats
+
+cd ${STATS_PATH}
+exec ${PYTHON_EXEC} -O b10-stats $*

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

@@ -0,0 +1,30 @@
+#! /bin/sh
+
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_srcdir@
+export B10_FROM_BUILD
+
+STATS_PATH=@abs_top_builddir@/src/bin/stats
+
+cd ${STATS_PATH}
+exec ${PYTHON_EXEC} -O b10-stats_stub $*

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

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

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

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

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

@@ -0,0 +1,155 @@
+#!@PYTHON@
+
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+__version__ = "$Revision$"
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+import time
+from optparse import OptionParser, OptionValueError
+from isc.config.ccsession import ModuleCCSession, create_command, parse_answer, parse_command, create_answer
+from isc.cc import Session, SessionError
+from stats import get_datetime
+
+# for setproctitle
+import isc.utils.process
+isc.utils.process.rename()
+
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+    SPECFILE_LOCATION = os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+
+class CCSessionStub:
+    """
+    This class is intended to behaves as a sender to Stats module. It
+    creates MoudleCCSession object and send specified command.
+    """
+    def __init__(self, session=None, verbose=False):
+        # create ModuleCCSession object
+        self.verbose = verbose
+        self.cc_session = ModuleCCSession(SPECFILE_LOCATION,
+                                          self.__dummy, self.__dummy, session)
+        self.module_name = self.cc_session._module_name
+        self.session = self.cc_session._session
+
+    def __dummy(self, *args):
+        pass
+
+    def send_command(self, command, args):
+        """
+        send command to stats module with args
+        """
+        cmd = create_command(command, args)
+        if self.verbose:
+            sys.stdout.write("[b10-stats_stub] send command : " + str(cmd) + "\n")
+        seq = self.session.group_sendmsg(cmd, self.module_name)
+        msg, env = self.session.group_recvmsg(False, seq) # non-blocking is False
+        if self.verbose:
+            sys.stdout.write("[b10-stats_stub] received env : " + str(env) + "\n")
+            sys.stdout.write("[b10-stats_stub] received message : " + str(msg) + "\n")
+        (ret, arg) = (None, None)
+        if 'result' in msg:
+            ret, arg = parse_answer(msg)
+        elif 'command' in msg:
+            ret, arg = parse_command(msg)
+        self.session.group_reply(env, create_answer(0))
+        return ret, arg, env
+        
+class BossModuleStub:
+    """
+    This class is customized from CCSessionStub and is intended to behaves
+    as a virtual Boss module to send to Stats Module.
+    """
+    def __init__(self, session=None, verbose=False):
+        self.stub = CCSessionStub(session=session, verbose=verbose)
+    
+    def send_boottime(self):
+        return self.stub.send_command("set", {"stats_data": {"bind10.boot_time": get_datetime()}})
+
+class AuthModuleStub:
+    """
+    This class is customized CCSessionStub and is intended to behaves
+    as a virtual Auth module to send to Stats Module.
+    """
+    def __init__(self, session=None, verbose=False):
+        self.stub = CCSessionStub(session=session, verbose=verbose)
+        self.count = { "udp": 0, "tcp": 0 }
+    
+    def send_udp_query_count(self, cmd="set", cnt=0):
+        """
+        count up udp query count
+        """
+        prt = "udp"
+        self.count[prt] = 1
+        if cnt > 0:
+            self.count[prt] = cnt
+        return self.stub.send_command(cmd,
+                                      {"stats_data":
+                                           {"auth.queries."+prt: self.count[prt]}
+                                       })
+
+    def send_tcp_query_count(self, cmd="set", cnt=0):
+        """
+        set udp query count
+        """
+        prt = "tcp"
+        self.count[prt] = self.count[prt] + 1
+        if cnt > 0:
+            self.count[prt] = cnt
+        return self.stub.send_command(cmd,
+                                      {"stats_data":
+                                           {"auth.queries."+prt: self.count[prt]}
+                                       })
+
+def main(session=None):
+    try:
+        parser=OptionParser()
+        parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      help="display more about what is going on")
+        (options, args) = parser.parse_args()
+        stub = CCSessionStub(session=session, verbose=options.verbose)
+        boss = BossModuleStub(session=stub.session, verbose=options.verbose)
+        auth = AuthModuleStub(session=stub.session, verbose=options.verbose)
+        stub.send_command("status", None)
+        boss.send_boottime()
+        t_cnt=0
+        u_cnt=81120
+        auth.send_udp_query_count(cnt=u_cnt) # This is an example.
+        while True:
+            u_cnt = u_cnt + 1
+            t_cnt = t_cnt + 1
+            auth.send_udp_query_count(cnt=u_cnt)
+            auth.send_tcp_query_count(cnt=t_cnt)
+            time.sleep(1)
+
+    except OptionValueError:
+        sys.stderr.write("[b10-stats_stub] Error parsing options\n")
+    except SessionError as se:
+        sys.stderr.write("[b10-stats_stub] Error creating Stats module, "
+              + "is the command channel daemon running?\n")
+    except KeyboardInterrupt as kie:
+        sys.stderr.write("[b10-stats_stub] Interrupted, exiting\n")
+
+if __name__ == "__main__":
+    main()

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,116 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+__version__ = "$Revision$"
+
+#
+# Tests for the stats stub module
+#
+import unittest
+import time
+import os
+import imp
+import stats_stub
+from isc.cc.session import Session
+from stats_stub import CCSessionStub, BossModuleStub, AuthModuleStub
+from stats import get_datetime
+
+class TestStats(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session()
+        self.stub = CCSessionStub(session=self.session, verbose=True)
+        self.boss = BossModuleStub(session=self.session, verbose=True)
+        self.auth = AuthModuleStub(session=self.session, verbose=True)
+        self.env = {'from': self.session.lname, 'group': 'Stats',
+                    'instance': '*', 'to':'*',
+                    'type':'send','seq':0}
+        self.result_ok = {'result': [0]}
+
+    def tearDown(self):
+        self.session.close()
+
+    def test_stub(self):
+        """
+        Test for send_command of CCSessionStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(('status', None, env),
+                         self.stub.send_command('status', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('shutdown', None, env),
+                         self.stub.send_command('shutdown', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('show', None, env),
+                         self.stub.send_command('show', None))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(('set', {'atest': 100.0}, env),
+                         self.stub.send_command('set', {'atest': 100.0}))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_boss_stub(self):
+        """
+        Test for send_command of BossModuleStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(('set', {"stats_data":
+                                      {"bind10.boot_time": get_datetime()}
+                                  }, env), self.boss.send_boottime())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_auth_stub(self):
+        """
+        Test for send_command of AuthModuleStub object
+        """
+        env = self.env
+        result_ok = self.result_ok
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.udp": 1}}, env),
+            self.auth.send_udp_query_count())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.tcp": 1}}, env),
+            self.auth.send_tcp_query_count())
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.udp": 100}}, env),
+            self.auth.send_udp_query_count(cmd='set', cnt=100))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+        self.assertEqual(
+            ('set', {"stats_data": {"auth.queries.tcp": 99}}, env),
+            self.auth.send_tcp_query_count(cmd='set', cnt=99))
+        self.assertEqual(result_ok, self.session.get_message("Stats", None))
+
+    def test_func_main(self):
+        # explicitly make failed
+        self.session.close()
+        stats_stub.main(session=self.session)
+
+    def test_osenv(self):
+        """
+        test for not having environ "B10_FROM_BUILD"
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            path = os.environ["B10_FROM_BUILD"]
+            os.environ.pop("B10_FROM_BUILD")
+            imp.reload(stats_stub)
+            os.environ["B10_FROM_BUILD"] = path
+            imp.reload(stats_stub)
+
+if __name__ == "__main__":
+    unittest.main()

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

@@ -0,0 +1,646 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+__version__ = "$Revision$"
+
+#
+# Tests for the stats module
+#
+import os
+import sys
+import time
+import unittest
+import imp
+from isc.cc.session import Session, SessionError
+from isc.config.ccsession import ModuleCCSession, ModuleCCSessionError
+import stats
+from stats import SessionSubject, CCSessionListener, get_timestamp, get_datetime
+from fake_time import _TEST_TIME_SECS, _TEST_TIME_STRF
+
+# setting Constant
+if sys.path[0] == '':
+    TEST_SPECFILE_LOCATION = "./testdata/stats_test.spec"
+else:
+    TEST_SPECFILE_LOCATION = sys.path[0] + "/testdata/stats_test.spec"
+
+class TestStats(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session()
+        self.subject = SessionSubject(session=self.session, verbose=True)
+        self.listener = CCSessionListener(self.subject, verbose=True)
+        self.stats_spec = self.listener.cc_session.get_module_spec().get_config_spec()
+        self.module_name = self.listener.cc_session.get_module_spec().get_module_name()
+        self.stats_data = {
+                'report_time' : get_datetime(),
+                'bind10.boot_time' : "1970-01-01T00:00:00Z",
+                'stats.timestamp' : get_timestamp(),
+                'stats.lname' : self.session.lname,
+                'auth.queries.tcp': 0,
+                'auth.queries.udp': 0,
+                "stats.boot_time": get_datetime(),
+                "stats.start_time": get_datetime(),
+                "stats.last_update_time": get_datetime()
+                }
+        # check starting
+        self.assertFalse(self.subject.running)
+        self.subject.start()
+        self.assertTrue(self.subject.running)
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.module_name, 'Stats')
+
+    def tearDown(self):
+        # check closing
+        self.subject.stop()
+        self.assertFalse(self.subject.running)
+        self.subject.detach(self.listener)
+        self.listener.stop()
+        self.session.close()
+
+    def test_local_func(self):
+        """
+        Test for local function
+        
+        """
+        # test for result_ok
+        self.assertEqual(type(result_ok()), dict)
+        self.assertEqual(result_ok(), {'result': [0]})
+        self.assertEqual(result_ok(1), {'result': [1]})
+        self.assertEqual(result_ok(0,'OK'), {'result': [0, 'OK']})
+        self.assertEqual(result_ok(1,'Not good'), {'result': [1, 'Not good']})
+        self.assertEqual(result_ok(None,"It's None"), {'result': [None, "It's None"]})
+        self.assertNotEqual(result_ok(), {'RESULT': [0]})
+
+        # test for get_timestamp
+        self.assertEqual(get_timestamp(), _TEST_TIME_SECS)
+
+        # test for get_datetime
+        self.assertEqual(get_datetime(), _TEST_TIME_STRF)
+
+    def test_show_command(self):
+        """
+        Test for show command
+        
+        """
+        # test show command without arg
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        # ignore under 0.9 seconds
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command with arg
+        self.session.group_sendmsg({"command": [ "show", {"stats_item_name": "stats.lname"}]}, "Stats")
+        self.assertEqual(len(self.subject.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.subject.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'stats.lname': self.stats_data['stats.lname']}),
+                         result_data)
+        self.assertEqual(len(self.subject.session.message_queue), 0)
+
+        # test show command with arg which has wrong name
+        self.session.group_sendmsg({"command": [ "show", {"stats_item_name": "stats.dummy"}]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        # ignore under 0.9 seconds
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_set_command(self):
+        """
+        Test for set command
+        
+        """
+        # test set command
+        self.stats_data['auth.queries.udp'] = 54321
+        self.assertEqual(self.stats_data['auth.queries.udp'], 54321)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 0)
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'auth.queries.udp': 54321 }
+                                      } ] },
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 2
+        self.stats_data['auth.queries.udp'] = 0
+        self.assertEqual(self.stats_data['auth.queries.udp'], 0)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 0)
+        self.session.group_sendmsg({ "command": [ "set", {'stats_data': {'auth.queries.udp': 0}} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command 2
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 3
+        self.stats_data['auth.queries.tcp'] = 54322
+        self.assertEqual(self.stats_data['auth.queries.udp'], 0)
+        self.assertEqual(self.stats_data['auth.queries.tcp'], 54322)
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'auth.queries.tcp': 54322 }
+                                      } ] },
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command 3
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_remove_command(self):
+        """
+        Test for remove command
+        
+        """
+        self.session.group_sendmsg({"command":
+                                   [ "remove", {"stats_item_name": 'bind10.boot_time' }]},
+                              "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.stats_data.pop('bind10.boot_time'), "1970-01-01T00:00:00Z")
+        self.assertFalse('bind10.boot_time' in self.stats_data)
+
+        # test show command with arg
+        self.session.group_sendmsg({"command":
+                                    [ "show", {"stats_item_name": 'bind10.boot_time'}]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertFalse('bind10.boot_time' in result_data['result'][1])
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_reset_command(self):
+        """
+        Test for reset command
+        
+        """
+        self.session.group_sendmsg({"command": [ "reset" ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command
+        self.session.group_sendmsg({"command": [ "show" ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_status_command(self):
+        """
+        Test for status command
+        
+        """
+        self.session.group_sendmsg({"command": [ "status" ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(0, "I'm alive."),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_unknown_command(self):
+        """
+        Test for unknown command
+        
+        """
+        self.session.group_sendmsg({"command": [ "hoge", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(1, "Unknown command: 'hoge'"),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_shutdown_command(self):
+        """
+        Test for shutdown command
+        
+        """
+        self.session.group_sendmsg({"command": [ "shutdown", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.assertTrue(self.subject.running)
+        self.subject.check()
+        self.assertFalse(self.subject.running)
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+
+    def test_some_commands(self):
+        """
+        Test for some commands in a row
+        
+        """
+        # test set command
+        self.stats_data['bind10.boot_time'] = '2010-08-02T14:47:56Z'
+        self.assertEqual(self.stats_data['bind10.boot_time'], '2010-08-02T14:47:56Z')
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'bind10.boot_time': '2010-08-02T14:47:56Z' }
+                                      }]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'bind10.boot_time' }
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'bind10.boot_time': '2010-08-02T14:47:56Z'}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'bind10.boot_time': self.stats_data['bind10.boot_time']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 2nd
+        self.stats_data['auth.queries.udp'] = 98765
+        self.assertEqual(self.stats_data['auth.queries.udp'], 98765)
+        self.session.group_sendmsg({ "command": [
+                                      "set", { 'stats_data': {
+                                            'auth.queries.udp':
+                                              self.stats_data['auth.queries.udp']
+                                            } } 
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({"command": [
+				      "show", {'stats_item_name': 'auth.queries.udp'}
+                                    ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': 98765}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': self.stats_data['auth.queries.udp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 3
+        self.stats_data['auth.queries.tcp'] = 4321
+        self.session.group_sendmsg({"command": [
+                                      "set",
+                                      {'stats_data': {'auth.queries.tcp': 4321 }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check value
+        self.session.group_sendmsg({"command": [ "show", {'stats_item_name': 'auth.queries.tcp'} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.tcp': 4321}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.tcp': self.stats_data['auth.queries.tcp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        self.session.group_sendmsg({"command": [ "show", {'stats_item_name': 'auth.queries.udp'} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': 98765}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'auth.queries.udp': self.stats_data['auth.queries.udp']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set command 4
+        self.stats_data['auth.queries.tcp'] = 67890
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'auth.queries.tcp': 67890 }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test show command for all values
+        self.session.group_sendmsg({"command": [ "show", None ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, self.stats_data), result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_some_commands2(self):
+        """
+        Test for some commands in a row using list-type value
+        
+        """
+        self.stats_data['listtype'] = [1, 2, 3]
+        self.assertEqual(self.stats_data['listtype'], [1, 2, 3])
+        self.session.group_sendmsg({ "command": [
+                                      "set", {'stats_data': {'listtype': [1, 2, 3] }}
+                                      ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'listtype'}
+                                     ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'listtype': [1, 2, 3]}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'listtype': self.stats_data['listtype']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set list-type value
+        self.assertEqual(self.stats_data['listtype'], [1, 2, 3])
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'listtype': [3, 2, 1, 0] }}
+                                    ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [
+                                      "show", { 'stats_item_name': 'listtype' }
+                                     ] }, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'listtype': [3, 2, 1, 0]}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_some_commands3(self):
+        """
+        Test for some commands in a row using dictionary-type value
+        
+        """
+        self.stats_data['dicttype'] = {"a": 1, "b": 2, "c": 3}
+        self.assertEqual(self.stats_data['dicttype'], {"a": 1, "b": 2, "c": 3})
+        self.session.group_sendmsg({ "command": [
+                                      "set", {
+                                          'stats_data': {'dicttype': {"a": 1, "b": 2, "c": 3} }
+                                      }]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [ "show", { 'stats_item_name': 'dicttype' } ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'dicttype': {"a": 1, "b": 2, "c": 3}}),
+                         result_data)
+        self.assertEqual(result_ok(0, {'dicttype': self.stats_data['dicttype']}),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # test set list-type value
+        self.assertEqual(self.stats_data['dicttype'], {"a": 1, "b": 2, "c": 3})
+        self.session.group_sendmsg({"command": [
+                                      "set", {'stats_data': {'dicttype': {"a": 3, "b": 2, "c": 1, "d": 0} }} ]},
+                                   "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+        self.assertEqual(len(self.session.message_queue), 0)
+
+        # check its value
+        self.session.group_sendmsg({ "command": [ "show", { 'stats_item_name': 'dicttype' }]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        result_data = self.session.get_message("Stats", None)
+        self.assertEqual(result_ok(0, {'dicttype': {"a": 3, "b": 2, "c": 1, "d": 0} }),
+                         result_data)
+        self.assertEqual(len(self.session.message_queue), 0)
+
+    def test_config_update(self):
+        """
+        Test for config update
+        
+        """
+        # test show command without arg
+        self.session.group_sendmsg({"command": [ "config_update", {"x-version":999} ]}, "Stats")
+        self.assertEqual(len(self.session.message_queue), 1)
+        self.subject.check()
+        self.assertEqual(result_ok(),
+                         self.session.get_message("Stats", None))
+
+class TestStats2(unittest.TestCase):
+
+    def setUp(self):
+        self.session = Session(verbose=True)
+        self.subject = SessionSubject(session=self.session, verbose=True)
+        self.listener = CCSessionListener(self.subject, verbose=True)
+        self.module_name = self.listener.cc_session.get_module_spec().get_module_name()
+        # check starting
+        self.assertFalse(self.subject.running)
+        self.subject.start()
+        self.assertTrue(self.subject.running)
+        self.assertEqual(len(self.session.message_queue), 0)
+        self.assertEqual(self.module_name, 'Stats')
+
+    def tearDown(self):
+        # check closing
+        self.subject.stop()
+        self.assertFalse(self.subject.running)
+        self.subject.detach(self.listener)
+        self.listener.stop()
+
+    def test_specfile(self):
+        """
+        Test for specfile
+        
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            self.assertEqual(stats.SPECFILE_LOCATION,
+                             os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec")
+        imp.reload(stats)
+        # change path of SPECFILE_LOCATION
+        stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
+        self.assertEqual(stats.SPECFILE_LOCATION, TEST_SPECFILE_LOCATION)
+        self.subject = stats.SessionSubject(session=self.session, verbose=True)
+        self.session = self.subject.session
+        self.listener = stats.CCSessionListener(self.subject, verbose=True)
+
+        self.assertEqual(self.listener.stats_spec, [])
+        self.assertEqual(self.listener.stats_data, {})
+
+        self.assertEqual(self.listener.commands_spec, [
+                {
+                    "command_name": "status",
+                    "command_description": "identify whether stats module is alive or not",
+                    "command_args": []
+                },
+                {
+                    "command_name": "the_dummy",
+                    "command_description": "this is for testing",
+                    "command_args": []
+                }])
+
+    def test_func_initialize_data(self):
+        """
+        Test for initialize_data function 
+        
+        """
+        # prepare for sample data set
+        stats_spec = [
+            {
+                "item_name": "none_sample",
+                "item_type": "null",
+                "item_default": "None"
+            },
+            {
+                "item_name": "boolean_sample",
+                "item_type": "boolean",
+                "item_default": True
+            },
+            {
+                "item_name": "string_sample",
+                "item_type": "string",
+                "item_default": "A something"
+            },
+            {
+                "item_name": "int_sample",
+                "item_type": "integer",
+                "item_default": 9999999
+            },
+            {
+                "item_name": "real_sample",
+                "item_type": "real",
+                "item_default": 0.0009
+            },
+            {
+                "item_name": "list_sample",
+                "item_type": "list",
+                "item_default": [0, 1, 2, 3, 4],
+                "list_item_spec": []
+            },
+            {
+                "item_name": "map_sample",
+                "item_type": "map",
+                "item_default": {'name':'value'},
+                "map_item_spec": []
+            },
+            {
+                "item_name": "other_sample",
+                "item_type": "__unknown__",
+                "item_default": "__unknown__"
+            }
+        ]
+        # data for comparison
+        stats_data = {
+            'none_sample': None,
+            'boolean_sample': True,
+            'string_sample': 'A something',
+            'int_sample': 9999999,
+            'real_sample': 0.0009,
+            'list_sample': [0, 1, 2, 3, 4],
+            'map_sample': {'name':'value'},
+            'other_sample': '__unknown__'
+        }
+        self.assertEqual(self.listener.initialize_data(stats_spec), stats_data)
+
+    def test_func_main(self):
+        # explicitly make failed
+        self.session.close()
+        stats.main(session=self.session)
+
+    def test_osenv(self):
+        """
+        test for not having environ "B10_FROM_BUILD"
+        """
+        if "B10_FROM_BUILD" in os.environ:
+            path = os.environ["B10_FROM_BUILD"]
+            os.environ.pop("B10_FROM_BUILD")
+            imp.reload(stats)
+            os.environ["B10_FROM_BUILD"] = path
+            imp.reload(stats)
+
+def result_ok(*args):
+    if args:
+        return { 'result': list(args) }
+    else:
+        return { 'result': [ 0 ] }
+
+if __name__ == "__main__":
+    unittest.main()

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

@@ -0,0 +1,48 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+__version__ = "$Revision$"
+
+# This is a dummy time class against a Python standard time class.
+# It is just testing use only.
+# Other methods which time class has is not implemented.
+# (This class isn't orderloaded for time class.)
+
+# These variables are constant. These are example.
+_TEST_TIME_SECS = 1283364938.229088
+_TEST_TIME_STRF = '2010-09-01T18:15:38Z'
+
+def time():
+    """
+    This is a dummy time() method against time.time()
+    """
+    # return float constant value
+    return _TEST_TIME_SECS
+
+def gmtime():
+    """
+    This is a dummy gmtime() method against time.gmtime()
+    """
+    # always return nothing
+    return None
+
+def strftime(*arg):
+    """
+    This is a dummy gmtime() method against time.gmtime()
+    """
+    return _TEST_TIME_STRF
+
+

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

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

src/lib/config/testdata/b10-config-bad3.db → src/bin/stats/tests/isc/__init__.py


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

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

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

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

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

@@ -0,0 +1,127 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+# This module is a mock-up class of isc.cc.session
+
+__version__ = "$Revision$"
+
+import sys
+
+# set a dummy lname
+_TEST_LNAME = '123abc@xxxx'
+
+class Queue():
+    def __init__(self, msg=None, env={}):
+        self.msg = msg
+        self.env = env
+
+    def dump(self):
+        return { 'msg': self.msg, 'env': self.env }
+               
+class SessionError(Exception):
+    pass
+
+class Session:
+    def __init__(self, socket_file=None, verbose=False):
+        self._lname = _TEST_LNAME
+        self.message_queue = []
+        self.old_message_queue = []
+        self._socket = True
+        self.verbose = verbose
+
+    @property
+    def lname(self):
+        return self._lname
+
+    def close(self):
+        self._socket = False
+
+    def _next_sequence(self, que=None):
+        return len(self.message_queue)
+
+    def enqueue(self, msg=None, env={}):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        seq = self._next_sequence()
+        env.update({"seq": 0}) # fixed here
+        que = Queue(msg=msg, env=env)
+        self.message_queue.append(que)
+        if self.verbose:
+            sys.stdout.write("[Session] enqueue: " + str(que.dump()) + "\n")
+        return seq
+
+    def dequeue(self, seq=0):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        que = None
+        try:
+            que = self.message_queue.pop(seq)
+            self.old_message_queue.append(que)
+        except IndexError:
+            que = Queue()
+        if self.verbose:
+            sys.stdout.write("[Session] dequeue: " + str(que.dump()) + "\n")
+        return que
+
+    def get_queue(self, seq=None):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        if seq is None:
+            seq = len(self.message_queue) - 1
+        que = None
+        try:
+            que = self.message_queue[seq]
+        except IndexError:
+            raise IndexError
+            que = Queue()
+        if self.verbose:
+            sys.stdout.write("[Session] get_queue: " + str(que.dump()) + "\n")
+        return que
+
+    def group_sendmsg(self, msg, group, instance="*", to="*"):
+        return self.enqueue(msg=msg, env={
+                "type": "send",
+                "from": self._lname,
+                "to": to,
+                "group": group,
+                "instance": instance })
+
+    def group_recvmsg(self, nonblock=True, seq=0):
+        que = self.dequeue(seq)
+        return que.msg, que.env
+        
+    def group_reply(self, routing, msg):
+        return self.enqueue(msg=msg, env={
+                "type": "send",
+                "from": self._lname,
+                "to": routing["from"],
+                "group": routing["group"],
+                "instance": routing["instance"],
+                "reply": routing["seq"] })
+
+    def get_message(self, group, to='*'):
+        if not self._socket:
+            raise SessionError("Session has been closed.")
+        que = Queue()
+        for q in self.message_queue:
+            if q.env['group'] == group:
+                self.message_queue.remove(q)
+                self.old_message_queue.append(q)
+                que = q
+        if self.verbose:
+            sys.stdout.write("[Session] get_message: " + str(que.dump()) + "\n")
+        return q.msg
+

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

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

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

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

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

@@ -0,0 +1,114 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+# This module is a mock-up class of isc.cc.session
+
+__version__ = "$Revision$"
+
+import json
+from isc.cc.session import Session
+
+COMMAND_CONFIG_UPDATE = "config_update"
+
+def parse_answer(msg):
+    try:
+        return msg['result'][0], msg['result'][1]
+    except IndexError:
+        return msg['result'][0], None
+
+def create_answer(rcode, arg = None):
+    if arg is None:
+        return { 'result': [ rcode ] }
+    else:
+        return { 'result': [ rcode, arg ] }
+
+def parse_command(msg):
+    try:
+        return msg['command'][0], msg['command'][1]
+    except IndexError:
+        return msg['command'][0], None
+
+def create_command(command_name, params = None):
+    if params is None:
+        return {"command": [command_name]}
+    else:
+        return {"command": [command_name, params]}
+
+def module_spec_from_file(spec_file, check = True):
+    file = open(spec_file)
+    module_spec = json.loads(file.read())
+    return ModuleSpec(module_spec['module_spec'], check)
+
+class ModuleSpec:
+    def __init__(self, module_spec, check = True):
+        self._module_spec = module_spec
+
+    def get_config_spec(self):
+        return self._module_spec['config_data']
+
+    def get_commands_spec(self):
+        return self._module_spec['commands']
+
+    def get_module_name(self):
+        return self._module_spec['module_name']
+
+class ModuleCCSessionError(Exception):
+    pass
+
+class ConfigData:
+    def __init__(self, specification):
+        self.specification = specification
+
+class ModuleCCSession(ConfigData):
+    def __init__(self, spec_file_name, config_handler, command_handler, cc_session = None):
+        module_spec = module_spec_from_file(spec_file_name)
+        ConfigData.__init__(self, module_spec)
+        self._module_name = module_spec.get_module_name()
+        self.set_config_handler(config_handler)
+        self.set_command_handler(command_handler)
+        if not cc_session:
+            self._session = Session(verbose=True)
+        else:
+            self._session = cc_session
+
+    def start(self):
+        pass
+
+    def close(self):
+        self._session.close()
+
+    def check_command(self):
+        msg, env = self._session.group_recvmsg(False)
+        if not msg or 'result' in msg:
+            return
+        cmd, arg = parse_command(msg)
+        answer = None
+        if cmd == COMMAND_CONFIG_UPDATE and self._config_handler:
+            answer = self._config_handler(arg)
+        elif env['group'] == self._module_name and self._command_handler:
+            answer = self._command_handler(cmd, arg)
+        if answer:
+            self._session.group_reply(env, answer)
+
+    def set_config_handler(self, config_handler):
+        self._config_handler = config_handler
+        # should we run this right now since we've changed the handler?
+
+    def set_command_handler(self, command_handler):
+        self._command_handler = command_handler
+
+    def get_module_spec(self):
+        return self.specification

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

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

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


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

@@ -0,0 +1,20 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+
+# A dummy function of isc.utils.process.rename()
+def rename(name=None):
+    pass

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

@@ -0,0 +1,31 @@
+#! /bin/sh
+
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/bin/stats:@abs_top_srcdir@/src/bin/stats/tests
+export PYTHONPATH
+
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+TEST_PATH=@abs_top_srcdir@/src/bin/stats/tests
+
+cd ${TEST_PATH}
+${PYTHON_EXEC} -O b10-stats_test.py $*
+${PYTHON_EXEC} -O b10-stats_stub_test.py $*

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

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

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

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

+ 13 - 0
src/bin/tests/Makefile.am

@@ -0,0 +1,13 @@
+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:
+	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 ; \
+	done

+ 3 - 0
src/bin/tests/README

@@ -0,0 +1,3 @@
+This directory does not contain any code a user might want to run. It contains
+"global" tests -- tests that are not specific to a single library or program.
+Do not expect to find b10-tests here, there's no such program.

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

@@ -0,0 +1,61 @@
+# 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 to see if every python process renames itself."""
+import unittest
+import os
+import os.path
+import isc.utils.process
+import re
+
+class TestRename(unittest.TestCase):
+    """Test scanning all python scripts if they rename themself."""
+    def __scan(self, directory, script, fun):
+        # Scan one script if it contains call to the renaming function
+        filename = os.path.join(directory, script)
+        data = ''.join(open(filename).readlines())
+        prettyname = 'src' + filename[filename.rfind('../') + 2:]
+        self.assertTrue(fun.search(data),
+            "Didn't find a call to isc.utils.process.rename in " + prettyname)
+
+    def test_calls(self):
+        """
+        Test if every script renames itself.
+
+        Scan all Makefile and look for scripts.
+        Then scan them by looking at the source text
+        (without actually running them)
+        """
+        # Regexp to find all the *_SCRIPTS = something lines,
+        # including line continuations (backslash and newline)
+        lines = re.compile(r'^\w+_SCRIPTS\s*=\s*((.|\\\n)*)$',
+            re.MULTILINE)
+        # Script name regular expression
+        scripts = re.compile(r'((\w|[-.0-9])+)')
+        # Line with the call
+        fun = re.compile(r'^\s*isc\.utils\.process\.rename\s*\(.*\)\s*(|#.*)$',
+            re.MULTILINE)
+
+        # Find all Makefile and extract names of scripts
+        for (d, _, fs) in os.walk('@top_builddir@'):
+            if 'Makefile' in fs:
+                makefile = ''.join(open(os.path.join(d,
+                    "Makefile")).readlines())
+                for (var, _) in lines.findall(makefile):
+                    for (script, _) in scripts.findall(var):
+                        self.__scan(d, script, fun)
+
+if __name__ == "__main__":
+    unittest.main()

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

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

+ 91 - 0
src/bin/zonemgr/b10-zonemgr.8

@@ -0,0 +1,91 @@
+'\" t
+.\"     Title: b10-zonemgr
+.\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\"      Date: September 8, 2010
+.\"    Manual: BIND10
+.\"    Source: BIND10
+.\"  Language: English
+.\"
+.TH "B10\-ZONEMGR" "8" "September 8, 2010" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-zonemgr \- BIND 10 Secondary zone manager
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-zonemgr\fR\ 'u
+\fBb10\-zonemgr\fR [\fB\-v\fR] [\fB\-\-verbose\fR]
+.SH "DESCRIPTION"
+.PP
+The
+\fBb10\-zonemgr\fR
+daemon, also known as the BIND 10 secondary manager, keeps track of timers and other information necessary for BIND 10 to act as a DNS slave\&. Normally it is started by the
+\fBbind10\fR(8)
+boss process\&.
+.PP
+This daemon communicates with BIND 10 over a
+\fBb10-msgq\fR(8)
+C\-Channel connection\&. If this connection is not established,
+\fBb10\-zonemgr\fR
+will exit\&.
+.PP
+
+\fBb10\-zonemgr\fR
+receives its configurations from
+\fBb10-cfgmgr\fR(8)\&.
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configuration commands are:
+.PP
+
+\fBnotify\fR
+(sent by
+\fBb10-auth\fR(8)) tells
+\fBb10\-zonemgr\fR
+the zone name and class, and the IP address for the master (source of the NOTIFY message)\&. This will set the zone\'s refresh time to now\&.
+This is an internal command and not exposed to the administrator\&.
+.PP
+
+\fBshutdown\fR
+exits
+\fBb10\-zonemgr\fR\&. (Note that the BIND 10 boss process will restart this service\&.)
+.PP
+
+\fBzone_new_data_ready\fR
+is sent from
+\fBb10-xfrin\fR(8)
+to indicate that the zone transferred in successfully\&. This is an internal command and not exposed to the administrator\&.
+.PP
+
+\fBzone_xfrin_failed\fR
+is sent from
+\fBb10-xfrin\fR(8)
+to indicate a failure (such as a transfer\-in was incomplete)\&. The refresh timer for the zone is reset\&.
+This is an internal command and not exposed to the administrator\&.
+.SH "SEE ALSO"
+.PP
+
+\fBb10-auth\fR(8),
+\fBb10-cfgmgr\fR(8),
+\fBb10-msgq\fR(8),
+\fBb10-xfrin\fR(8),
+\fBb10-xfrout\fR(8),
+\fBbind10\fR(8),
+BIND 10 Guide\&.
+.SH "HISTORY"
+.PP
+The
+\fBb10\-zonemgr\fR
+daemon was designed in July 2010 by CNNIC for the ISC BIND 10 project\&.
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+.br

+ 205 - 0
src/bin/zonemgr/b10-zonemgr.xml

@@ -0,0 +1,205 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<!-- $Id$ -->
+<refentry>
+
+  <refentryinfo>
+    <date>September 8, 2010</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-zonemgr</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-zonemgr</refname>
+    <refpurpose>BIND 10 Secondary zone manager</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-zonemgr</command>
+      <arg><option>-v</option></arg>
+      <arg><option>--verbose</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-zonemgr</command> daemon, also known
+      as the BIND 10 secondary manager, keeps track of timers
+      and other information necessary for BIND 10 to act as a DNS slave.
+      Normally it is started by the
+      <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      boss process.
+    </para>
+
+    <para>
+      This daemon communicates with BIND 10 over a
+      <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      C-Channel connection.  If this connection is not established,
+      <command>b10-zonemgr</command> will exit.
+<!-- TODO what if connection closes later, will b10-zonemgr exit? -->
+    </para>
+
+    <para>
+     <command>b10-zonemgr</command> receives its configurations from
+<citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+    </para>
+
+<!--
+
+            self._send_command(XFRIN_MODULE_NAME, ZONE_NOTIFY_COMMAND, param) 
+            self._clear_zone_notifier_master(zone_name_class)
+        # Send refresh command to xfrin module
+        else:
+            param = {"zone_name" : zone_name_class[0],
+                     "zone_class" : zone_name_class[1]
+                    }
+            self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
+
+-->
+
+  </refsect1>
+
+  <refsect1>
+    <title>CONFIGURATION AND COMMANDS</title>
+<!--
+    <para>
+      The configurable settings are:
+    </para>
+-->
+
+<!-- TODO: formating -->
+    <para>
+      The configuration commands are:
+    </para>
+    <para>
+      <command>notify</command> (sent by
+      <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
+      tells <command>b10-zonemgr</command>
+      the zone name and class, and the IP address for the master
+      (source of the NOTIFY message).
+      This will set the zone's refresh time to now.
+<!-- TODO reword this -->
+      This is an internal command and not exposed to the administrator.
+<!-- not defined in spec -->
+    </para>
+
+    <para>
+      <command>shutdown</command> exits <command>b10-zonemgr</command>.
+      (Note that the BIND 10 boss process will restart this service.)
+    </para>
+
+    <para>
+      <command>zone_new_data_ready</command> is sent from
+      <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      to indicate that the zone transferred in successfully.
+      This is an internal command and not exposed to the administrator.
+<!-- not defined in spec -->
+    </para>
+
+    <para>
+      <command>zone_xfrin_failed</command> is sent from
+      <citerefentry><refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      to indicate a failure (such as a transfer-in was incomplete).
+      The refresh timer for the zone is reset.
+<!--
+        """Set zone next refresh time after zone refresh fail.
+           now + retry*3/4 <= next_refresh_time <= now + retry
+-->
+      This is an internal command and not exposed to the administrator.
+<!-- not defined in spec -->
+    </para>
+
+  </refsect1>
+
+<!--
+  <refsect1>
+    <title>OPTIONS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option></option></term>
+        <listitem><para>
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+-->
+<!--
+  <refsect1>
+    <title>FILES</title>
+    <para>
+    <filename>/tmp/auth_xfrout_conn</filename>
+    </para>
+  </refsect1>
+-->
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-zonemgr</command> daemon was designed in July 2010
+      by CNNIC for the ISC BIND 10 project.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 1 - 0
src/lib/config/ccsession.cc

@@ -45,6 +45,7 @@
 using namespace std;
 
 using isc::data::Element;
+using isc::data::ConstElementPtr;
 using isc::data::ElementPtr;
 using isc::data::JSONError;
 

+ 18 - 11
src/lib/config/ccsession.h

@@ -31,7 +31,7 @@ namespace config {
 /// \brief Creates a standard config/command level success answer message
 ///        (i.e. of the form { "result": [ 0 ] }
 /// \return Standard command/config success answer message
-ConstElementPtr createAnswer();
+isc::data::ConstElementPtr createAnswer();
 
 ///
 /// \brief Creates a standard config/command level answer message
@@ -43,7 +43,8 @@ ConstElementPtr createAnswer();
 ///            Element type. For rcode == 1, this argument is mandatory,
 ///            and must be a StringElement containing an error description
 /// \return Standard command/config answer message
-ConstElementPtr createAnswer(const int rcode, ConstElementPtr arg);
+isc::data::ConstElementPtr createAnswer(const int rcode,
+                                        isc::data::ConstElementPtr arg);
 
 ///
 /// \brief Creates a standard config/command level answer message
@@ -52,7 +53,8 @@ ConstElementPtr createAnswer(const int rcode, ConstElementPtr arg);
 /// \param rcode The return code (0 for success)
 /// \param arg A string to put into the StringElement argument
 /// \return Standard command/config answer message
-ConstElementPtr createAnswer(const int rcode, const std::string& arg);
+isc::data::ConstElementPtr createAnswer(const int rcode,
+                                        const std::string& arg);
 
 ///
 /// Parses a standard config/command level answer message
@@ -63,7 +65,8 @@ ConstElementPtr createAnswer(const int rcode, const std::string& arg);
 /// \return The optional argument in the message, or an empty ElementPtr
 ///         if there was no argument. If rcode != 0, this contains a
 ///         StringElement with the error description.
-ConstElementPtr parseAnswer(int &rcode, ConstElementPtr msg);
+isc::data::ConstElementPtr parseAnswer(int &rcode,
+                                       isc::data::ConstElementPtr msg);
 
 ///
 /// \brief Creates a standard config/command command message with no
@@ -71,7 +74,7 @@ ConstElementPtr parseAnswer(int &rcode, ConstElementPtr msg);
 /// 
 /// \param command The command string
 /// \return The created message
-ConstElementPtr createCommand(const std::string& command);
+isc::data::ConstElementPtr createCommand(const std::string& command);
 
 ///
 /// \brief Creates a standard config/command command message with the
@@ -81,7 +84,8 @@ ConstElementPtr createCommand(const std::string& command);
 /// \param arg The optional argument for the command. This can be of 
 ///        any Element type, but it should conform to the .spec file.
 /// \return The created message
-ConstElementPtr createCommand(const std::string& command, ConstElementPtr arg);
+isc::data::ConstElementPtr createCommand(const std::string& command,
+                                         isc::data::ConstElementPtr arg);
 
 ///
 /// \brief Parses the given command into a string containing the actual
@@ -92,7 +96,8 @@ ConstElementPtr createCommand(const std::string& command, ConstElementPtr arg);
 /// \param command The command message containing the command (as made
 ///        by createCommand()
 /// \return The command string
-std::string parseCommand(ConstElementPtr& arg, ConstElementPtr command);
+std::string parseCommand(isc::data::ConstElementPtr& arg,
+                         isc::data::ConstElementPtr command);
 
 
 ///
@@ -225,8 +230,9 @@ public:
      * \param identifier The identifier of the config value
      * \return The configuration setting at the given identifier
      */
-    ConstElementPtr getRemoteConfigValue(const std::string& module_name,
-                                         const std::string& identifier) const;
+    isc::data::ConstElementPtr getRemoteConfigValue(
+        const std::string& module_name,
+        const std::string& identifier) const;
     
 private:
     ModuleSpec readModuleSpecification(const std::string& filename);
@@ -235,7 +241,8 @@ private:
     std::string module_name_;
     isc::cc::AbstractSession& session_;
     ModuleSpec module_specification_;
-    ConstElementPtr handleConfigUpdate(ConstElementPtr new_config);
+    isc::data::ConstElementPtr handleConfigUpdate(
+        isc::data::ConstElementPtr new_config);
 
     isc::data::ConstElementPtr(*config_handler_)(
         isc::data::ConstElementPtr new_config);
@@ -245,7 +252,7 @@ private:
 
     std::map<std::string, ConfigData> remote_module_configs_;
     void updateRemoteConfig(const std::string& module_name,
-                            ConstElementPtr new_config);
+                            isc::data::ConstElementPtr new_config);
 };
 
 }

+ 13 - 11
src/lib/config/config_data.h

@@ -39,12 +39,14 @@ class ConfigData {
 public:
     /// Constructs a ConfigData option with no specification and an
     /// empty configuration.
-    ConfigData() { _config = Element::createMap(); };
+    ConfigData() { _config = isc::data::Element::createMap(); };
 
     /// Constructs a ConfigData option with the given specification
     /// and an empty configuration.
     /// \param module_spec A ModuleSpec for the relevant module
-    ConfigData(const ModuleSpec& module_spec) : _module_spec(module_spec) { _config = Element::createMap(); }
+    ConfigData(const ModuleSpec& module_spec) : _module_spec(module_spec) {
+        _config = isc::data::Element::createMap();
+    }
 
     virtual ~ConfigData() {};
 
@@ -55,7 +57,7 @@ public:
     /// Raises a DataNotFoundError if the identifier is bad.
     /// \param identifier The identifier pointing to the configuration
     ///        value that is to be returned
-    ConstElementPtr getValue(const std::string& identifier) const;
+    isc::data::ConstElementPtr getValue(const std::string& identifier) const;
 
     /// Returns the value currently set for the given identifier
     /// If no value is set, the default value (as specified by the
@@ -67,8 +69,8 @@ public:
     ///                   false otherwise
     /// \param identifier The identifier pointing to the configuration
     ///        value that is to be returned
-    ConstElementPtr getValue(bool& is_default,
-                             const std::string& identifier) const;
+    isc::data::ConstElementPtr getValue(bool& is_default,
+                                        const std::string& identifier) const;
 
     /// Returns the ModuleSpec associated with this ConfigData object
     const ModuleSpec& getModuleSpec() const { return (_module_spec); }
@@ -80,12 +82,12 @@ public:
     /// \param config An ElementPtr pointing to a MapElement containing
     ///        *all* non-default configuration values. Existing values
     ///        will be removed.
-    void setLocalConfig(ElementPtr config) { _config = config; }
+    void setLocalConfig(isc::data::ElementPtr config) { _config = config; }
 
     /// Returns the local (i.e. non-default) configuration.
     /// \returns An ElementPtr pointing to a MapElement containing all
     ///          non-default configuration options.
-    ElementPtr getLocalConfig() { return (_config); }
+    isc::data::ElementPtr getLocalConfig() { return (_config); }
 
     /// Returns a list of all possible configuration options as specified
     ///         by the ModuleSpec.
@@ -97,18 +99,18 @@ public:
     ///         StringElements that specify the identifiers at the given
     ///         location (or all possible identifiers if identifier==""
     ///         and recurse==false)
-    ConstElementPtr getItemList(const std::string& identifier = "",
-                                bool recurse = false) const;
+    isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
+                                           bool recurse = false) const;
 
     /// Returns all current configuration settings (both non-default and default).
     /// \return An ElementPtr pointing to a MapElement containing
     ///         string->value elements, where the string is the
     ///         full identifier of the configuration option and the
     ///         value is an ElementPtr with the value.
-    ConstElementPtr getFullConfig() const;
+    isc::data::ConstElementPtr getFullConfig() const;
 
 private:
-    ElementPtr _config;
+    isc::data::ElementPtr _config;
     ModuleSpec _module_spec;
 };
 

+ 1 - 0
src/lib/config/module_spec.cc

@@ -24,6 +24,7 @@
 
 // todo: add more context to thrown ModuleSpecErrors?
 
+using namespace isc::data;
 using namespace isc::config;
 
 namespace {

+ 26 - 19
src/lib/config/module_spec.h

@@ -20,8 +20,6 @@
 
 #include <sstream>
 
-using namespace isc::data;
-
 namespace isc { namespace config {
 
     ///
@@ -55,24 +53,27 @@ namespace isc { namespace config {
         /// Create a \c ModuleSpec instance with the given data as
         /// the specification
         /// \param e The Element containing the data specification
-        explicit ModuleSpec(ConstElementPtr e, const bool check = true)
+        explicit ModuleSpec(isc::data::ConstElementPtr e,
+                            const bool check = true)
             throw(ModuleSpecError);
 
         /// Returns the commands part of the specification as an
         /// ElementPtr, returns an empty ElementPtr if there is none
         /// \return ElementPtr Shared pointer to the commands
         ///                    part of the specification
-        ConstElementPtr getCommandsSpec() const;
+        isc::data::ConstElementPtr getCommandsSpec() const;
 
         /// Returns the configuration part of the specification as an
         /// ElementPtr
         /// \return ElementPtr Shared pointer to the configuration
         ///                    part of the specification
-        ConstElementPtr getConfigSpec() const;
+        isc::data::ConstElementPtr getConfigSpec() const;
 
         /// Returns the full module specification as an ElementPtr
         /// \return ElementPtr Shared pointer to the specification
-        ConstElementPtr getFullSpec() const { return module_specification; }
+        isc::data::ConstElementPtr getFullSpec() const {
+            return module_specification;
+        }
 
         /// Returns the module name as specified by the specification
         const std::string getModuleName() const;
@@ -87,22 +88,28 @@ namespace isc { namespace config {
         /// \param data The base \c Element of the data to check
         /// \return true if the data conforms to the specification,
         /// false otherwise.
-        bool validate_config(ConstElementPtr data,
+        bool validate_config(isc::data::ConstElementPtr data,
                              const bool full = false) const;
 
         /// errors must be of type ListElement
-        bool validate_config(ConstElementPtr data, const bool full,
-                             ElementPtr errors) const;
+        bool validate_config(isc::data::ConstElementPtr data, const bool full,
+                             isc::data::ElementPtr errors) const;
 
     private:
-        bool validate_item(ConstElementPtr spec, ConstElementPtr data,
-                           const bool full, ElementPtr errors) const;
-        bool validate_spec(ConstElementPtr spec, ConstElementPtr data,
-                           const bool full, ElementPtr errors) const;
-        bool validate_spec_list(ConstElementPtr spec, ConstElementPtr data,
-                                const bool full, ElementPtr errors) const;
-
-        ConstElementPtr module_specification;
+        bool validate_item(isc::data::ConstElementPtr spec,
+                           isc::data::ConstElementPtr data,
+                           const bool full,
+                           isc::data::ElementPtr errors) const;
+        bool validate_spec(isc::data::ConstElementPtr spec,
+                           isc::data::ConstElementPtr data,
+                           const bool full,
+                           isc::data::ElementPtr errors) const;
+        bool validate_spec_list(isc::data::ConstElementPtr spec,
+                                isc::data::ConstElementPtr data,
+                                const bool full,
+                                isc::data::ElementPtr errors) const;
+
+        isc::data::ConstElementPtr module_specification;
     };
 
     /// Creates a \c ModuleSpec instance from the contents
@@ -115,7 +122,7 @@ namespace isc { namespace config {
     /// is checked to be of the correct form
     ModuleSpec
     moduleSpecFromFile(const std::string& file_name, const bool check = true)
-                       throw(JSONError, ModuleSpecError);
+        throw(isc::data::JSONError, ModuleSpecError);
 
     /// Creates a \c ModuleSpec instance from the given input
     /// stream that contains the contents of a .spec file.
@@ -127,7 +134,7 @@ namespace isc { namespace config {
     /// to be of the correct form
     ModuleSpec
     moduleSpecFromFile(std::ifstream& in, const bool check = true)
-                       throw(JSONError, ModuleSpecError);
+                       throw(isc::data::JSONError, ModuleSpecError);
 } }
 
 #endif // _DATA_DEF_H

+ 0 - 8
src/lib/config/testdata/Makefile.am

@@ -1,8 +0,0 @@
-CLEANFILES = b10-config.db
-
-BUILT_SOURCES = b10-config.db
-
-# cfgmgr_test (under lib/python) will override b10-config.db, so we make a
-# writable copy in the builddir.
-b10-config.db: b10-config.db.master
-	cp $(srcdir)/b10-config.db.master $@

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

@@ -0,0 +1,50 @@
+CLEANFILES = b10-config.db
+
+BUILT_SOURCES = b10-config.db
+
+# cfgmgr_test (under lib/python) will override b10-config.db, so we make a
+# writable copy in the builddir.
+b10-config.db: b10-config.db.master
+	cp $(srcdir)/b10-config.db.master $@
+
+EXTRA_DIST =  b10-config-bad1.db
+EXTRA_DIST += b10-config-bad2.db
+EXTRA_DIST += b10-config-bad3.db
+EXTRA_DIST += b10-config-bad4.db
+EXTRA_DIST += b10-config.db.master #.db will be auto-generated
+EXTRA_DIST += data22_1.data
+EXTRA_DIST += data22_2.data
+EXTRA_DIST += data22_3.data
+EXTRA_DIST += data22_4.data
+EXTRA_DIST += data22_5.data
+EXTRA_DIST += data22_6.data
+EXTRA_DIST += data22_7.data
+EXTRA_DIST += data22_8.data
+EXTRA_DIST += spec1.spec
+EXTRA_DIST += spec2.spec
+EXTRA_DIST += spec3.spec
+EXTRA_DIST += spec4.spec
+EXTRA_DIST += spec5.spec
+EXTRA_DIST += spec6.spec
+EXTRA_DIST += spec7.spec
+EXTRA_DIST += spec8.spec
+EXTRA_DIST += spec9.spec
+EXTRA_DIST += spec10.spec
+EXTRA_DIST += spec11.spec
+EXTRA_DIST += spec12.spec
+EXTRA_DIST += spec13.spec
+EXTRA_DIST += spec14.spec
+EXTRA_DIST += spec15.spec
+EXTRA_DIST += spec16.spec
+EXTRA_DIST += spec17.spec
+EXTRA_DIST += spec18.spec
+EXTRA_DIST += spec19.spec
+EXTRA_DIST += spec20.spec
+EXTRA_DIST += spec21.spec
+EXTRA_DIST += spec22.spec
+EXTRA_DIST += spec23.spec
+EXTRA_DIST += spec24.spec
+EXTRA_DIST += spec25.spec
+EXTRA_DIST += spec26.spec
+EXTRA_DIST += spec27.spec
+EXTRA_DIST += spec28.spec

src/lib/config/testdata/b10-config-bad1.db → src/lib/config/tests/testdata/b10-config-bad1.db


src/lib/config/testdata/b10-config-bad2.db → src/lib/config/tests/testdata/b10-config-bad2.db


+ 0 - 0
src/lib/config/tests/testdata/b10-config-bad3.db


src/lib/config/testdata/b10-config-bad4.db → src/lib/config/tests/testdata/b10-config-bad4.db


src/lib/config/testdata/b10-config.db.master → src/lib/config/tests/testdata/b10-config.db.master


src/lib/config/testdata/data22_1.data → src/lib/config/tests/testdata/data22_1.data


src/lib/config/testdata/data22_2.data → src/lib/config/tests/testdata/data22_2.data


src/lib/config/testdata/data22_3.data → src/lib/config/tests/testdata/data22_3.data


src/lib/config/testdata/data22_4.data → src/lib/config/tests/testdata/data22_4.data


src/lib/config/testdata/data22_5.data → src/lib/config/tests/testdata/data22_5.data


src/lib/config/testdata/data22_6.data → src/lib/config/tests/testdata/data22_6.data


src/lib/config/testdata/data22_7.data → src/lib/config/tests/testdata/data22_7.data


src/lib/config/testdata/data22_8.data → src/lib/config/tests/testdata/data22_8.data


src/lib/config/testdata/spec1.spec → src/lib/config/tests/testdata/spec1.spec


src/lib/config/testdata/spec10.spec → src/lib/config/tests/testdata/spec10.spec


src/lib/config/testdata/spec11.spec → src/lib/config/tests/testdata/spec11.spec


src/lib/config/testdata/spec12.spec → src/lib/config/tests/testdata/spec12.spec


src/lib/config/testdata/spec13.spec → src/lib/config/tests/testdata/spec13.spec


src/lib/config/testdata/spec14.spec → src/lib/config/tests/testdata/spec14.spec


src/lib/config/testdata/spec15.spec → src/lib/config/tests/testdata/spec15.spec


src/lib/config/testdata/spec16.spec → src/lib/config/tests/testdata/spec16.spec


src/lib/config/testdata/spec17.spec → src/lib/config/tests/testdata/spec17.spec


src/lib/config/testdata/spec18.spec → src/lib/config/tests/testdata/spec18.spec


src/lib/config/testdata/spec19.spec → src/lib/config/tests/testdata/spec19.spec


src/lib/config/testdata/spec2.spec → src/lib/config/tests/testdata/spec2.spec


src/lib/config/testdata/spec20.spec → src/lib/config/tests/testdata/spec20.spec


src/lib/config/testdata/spec21.spec → src/lib/config/tests/testdata/spec21.spec


src/lib/config/testdata/spec22.spec → src/lib/config/tests/testdata/spec22.spec


src/lib/config/testdata/spec23.spec → src/lib/config/tests/testdata/spec23.spec


src/lib/config/testdata/spec24.spec → src/lib/config/tests/testdata/spec24.spec


src/lib/config/testdata/spec25.spec → src/lib/config/tests/testdata/spec25.spec


src/lib/config/testdata/spec26.spec → src/lib/config/tests/testdata/spec26.spec


src/lib/config/testdata/spec27.spec → src/lib/config/tests/testdata/spec27.spec


src/lib/config/testdata/spec28.spec → src/lib/config/tests/testdata/spec28.spec


src/lib/config/testdata/spec3.spec → src/lib/config/tests/testdata/spec3.spec


src/lib/config/testdata/spec4.spec → src/lib/config/tests/testdata/spec4.spec


src/lib/config/testdata/spec5.spec → src/lib/config/tests/testdata/spec5.spec


src/lib/config/testdata/spec6.spec → src/lib/config/tests/testdata/spec6.spec


src/lib/config/testdata/spec7.spec → src/lib/config/tests/testdata/spec7.spec


src/lib/config/testdata/spec8.spec → src/lib/config/tests/testdata/spec8.spec


src/lib/config/testdata/spec9.spec → src/lib/config/tests/testdata/spec9.spec


+ 174 - 0
src/lib/dns/edns.cc

@@ -0,0 +1,174 @@
+// 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 <stdint.h>
+
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+
+namespace {
+// This diagram shows the wire-format representation of the TTL field of
+// OPT RR and its relationship with implementation specific parameters.
+//
+//           0             7               15                              31
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//          | EXTENDED-RCODE|    VERSION     |D|               Z             |
+//          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//                                          <= VERSION_SHIFT (16 bits)
+//                          <= EXTRCODE_SHIFT (24 bits)
+//EXTFLAG_DO:0 0 0 ....................... 0 1 0 0 0 0.....................0
+//VER_MASK:  0 0 0 ........0 1 1 1 1 1 1 1 1 0 0 ..........................0
+
+const unsigned int VERSION_SHIFT = 16;
+const unsigned int EXTRCODE_SHIFT = 24;
+const uint32_t VERSION_MASK = 0x00ff0000;
+const uint32_t EXTFLAG_DO = 0x00008000;
+}
+
+EDNS::EDNS(const uint8_t version) :
+    version_(version),
+    udp_size_(Message::DEFAULT_MAX_UDPSIZE),
+    dnssec_aware_(false)
+{
+    if (version_ > SUPPORTED_VERSION) {
+        isc_throw(isc::InvalidParameter,
+                  "failed to construct EDNS: unsupported version: " <<
+                  static_cast<unsigned int>(version_));
+    }
+}
+
+EDNS::EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+           const RRTTL& ttl, const Rdata& rdata UNUSED_PARAM) :
+    version_((ttl.getValue() & VERSION_MASK) >> VERSION_SHIFT)
+{
+    if (rrtype != RRType::OPT()) {
+        isc_throw(isc::InvalidParameter,
+                  "EDNS is being created with incompatible RR type: "
+                  << rrtype);
+    }
+    
+    if (version_ > EDNS::SUPPORTED_VERSION) {
+        isc_throw(DNSMessageBADVERS, "unsupported EDNS version: " <<
+                  static_cast<unsigned int>(version_));
+    }
+
+    if (name != Name::ROOT_NAME()) {
+        isc_throw(DNSMessageFORMERR, "invalid owner name for EDNS OPT RR: " <<
+                  name);
+    }
+
+    dnssec_aware_ = ((ttl.getValue() & EXTFLAG_DO) != 0);
+    udp_size_ = rrclass.getCode();
+}
+
+string
+EDNS::toText() const {
+    string ret = "; EDNS: version: ";
+
+    ret += lexical_cast<string>(static_cast<int>(getVersion()));
+    ret += ", flags:";
+    if (getDNSSECAwareness()) {
+        ret += " do";
+    }
+    ret += "; udp: " + lexical_cast<string>(getUDPSize()) + "\n";
+
+    return (ret);
+}
+
+template <typename Output>
+int
+EDNS::toWire(Output& output, const uint8_t extended_rcode) const {
+    // Render EDNS OPT RR
+    uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT;
+    extrcode_flags |= (version_ << VERSION_SHIFT) & VERSION_MASK;
+    if (dnssec_aware_) {
+        extrcode_flags |= EXTFLAG_DO;
+    }
+
+    // Construct an RRset corresponding to the EDNS.
+    // We don't support any options for now, so the OPT RR can be empty.
+    RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size_),
+                                  RRType::OPT(), RRTTL(extrcode_flags)));
+    edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
+
+    edns_rrset->toWire(output);
+
+    return (1);
+}
+
+unsigned int
+EDNS::toWire(MessageRenderer& renderer, const uint8_t extended_rcode) const {
+    // If adding the OPT RR would exceed the size limit, don't do it.
+    // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
+    // (RDATA is empty in this simple implementation)
+    if (renderer.getLength() + 11 > renderer.getLengthLimit()) {
+        return (0);
+    }
+
+    return (toWire<MessageRenderer>(renderer, extended_rcode));
+}
+
+unsigned int
+EDNS::toWire(OutputBuffer& buffer, const uint8_t extended_rcode) const {
+    return (toWire<OutputBuffer>(buffer, extended_rcode));
+}
+
+EDNS*
+createEDNSFromRR(const Name& name, const RRClass& rrclass,
+                 const RRType& rrtype, const RRTTL& ttl,
+                 const Rdata& rdata UNUSED_PARAM,
+                 uint8_t& extended_rcode)
+{
+    // Create a new EDNS object first for exception guarantee.
+    EDNS* edns = new EDNS(name, rrclass, rrtype, ttl, rdata);
+
+    // At this point we can update extended_rcode safely.
+    extended_rcode = ttl.getValue() >> EXTRCODE_SHIFT;
+
+    return (edns);
+}
+
+ostream&
+operator<<(std::ostream& os, const EDNS& edns) {
+    os << edns.toText();
+    return (os);
+}
+
+} // end of namespace dns
+} // end of namespace isc

+ 449 - 0
src/lib/dns/edns.h

@@ -0,0 +1,449 @@
+// 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 __EDNS_H
+#define __EDNS_H 1
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <ostream>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MessageRenderer;
+class RRClass;
+class RRTTL;
+class RRType;
+class Rcode;
+
+/// \brief A pointer-like type pointing to an \c EDNS object.
+typedef boost::shared_ptr<EDNS> EDNSPtr;
+
+/// \brief A pointer-like type pointing to an immutable \c EDNS object.
+typedef boost::shared_ptr<const EDNS> ConstEDNSPtr;
+
+/// The \c EDNS class represents the %EDNS OPT RR defined in RFC2671.
+///
+/// This class encapsulates various optional features of %EDNS such as
+/// the UDP payload size or the DNSSEC DO bit, and provides interfaces
+/// to manage these features.  It is also responsible for conversion
+/// to and from wire-format OPT RR.
+/// One important exception is about the extended RCODE:
+/// The \c EDNS class is only responsible for extracting the 8-bit part
+/// of the 12-bit extended RCODE from the OPT RR's TTL field of an
+/// incoming message, and for setting the 8-bit part into the OPT RR TTL
+/// of an outgoing message.  It's not supposed to know how to construct the
+/// complete RCODE, much less maintain the RCODE in it.
+/// It is the caller's responsibility (typically the \c Message class).
+///
+/// When converting wire-format OPT RR into an \c EDNS object, it normalizes
+/// the information, i.e., unknown flags will be ignored on construction.
+///
+/// This class is also supposed to support %EDNS options such as NSID,
+/// but the initial implementation does not include it.  This is a near term
+/// TODO item.
+///
+/// <b>Notes to developers</b>
+///
+/// The rest of the description is for developers who need to or want to
+/// understand the design of this API.
+///
+/// Representing %EDNS is tricky.  An OPT RR is no different from other RRs
+/// in terms of the wire format syntax, and in that sense we could use the
+/// generic \c RRset class to represent an OPT RR (BIND 9 adopts this
+/// approach).  But the resulting interface would be inconvenient for
+/// developers.  For example, the developer would need to know that the
+/// UDP size is encoded in the RR Class field.  It's better to provide
+/// a more abstract interface along with the special semantics of OPT RR.
+///
+/// Another approach would be to realize each optional feature of EDNS
+/// as an attribute of the DNS message.
+/// NLnet Labs' ldns takes this approach.
+/// This way an operation for specifying the UDP size would be written
+/// like this:
+/// \code message->setUDPSize(4096); \endcode
+/// which should be more intuitive.
+/// A drawback of this approach is that OPT RR is itself optional and the
+/// separate parameters may not necessarily indicate whether to include an
+/// OPT RR per se.
+/// For example, consider what should be done with this code:
+/// \code message->setUDPSize(512); \endcode
+/// Since the payload size of 512 is the default, it may mean the OPT RR
+/// should be skipped.  But it might also mean the caller intentionally
+/// (for some reason) wants to insert an OPT RR specifying the default UDP
+/// size explicitly.
+///
+/// So, we use a separate class that encapsulates the EDNS semantics and
+/// knows the mapping between the semantics and the wire format representation.
+/// This way the interface can be semantics-based and is intuitive:
+/// \code edns->setUDPSize(4096); \endcode
+/// while we can explicitly specify whether to include an OPT RR by setting
+/// (or not setting) an \c EDNS object in a message:
+/// \code message->setEDNS(edns); // unless we do this OPT RR is skipped
+/// \endcode
+///
+/// There is still a non trivial point: How to manage extended RCODEs.
+/// An OPT RR encodes the upper 8 bits of extended 12-bit RCODE.
+/// In general, it would be better to provide a unified interface to get
+/// access to RCODEs whether or not they are traditional 4 bit codes or
+/// extended ones that have non 0 upper bits.
+/// However, since an OPT RR may not appear in a message the RCODE cannot be
+/// maintained in the \c EDNS class.
+/// But it would not be desirable to maintain the extended RCODEs completely
+/// in the \c Message class, either, because we wanted to hide the mapping
+/// between %EDNS semantics and its wire format representation within the
+///  \c EDNS class; if we moved the responsibility about RCODEs to the
+/// \c Message class, it would have to parse and render the upper 8 bits of
+/// the RCODEs, dealing with wire representation of OPT RR.
+/// This is suboptimal in the sense of encapsulation.
+///
+/// As a compromise, our decision is to separate the knowledge about the
+/// relationship with RCODE from the knowledge about the wire format as
+/// noted in the beginning of this description.
+///
+/// This decoupling is based on the observation that the extended RCODE
+/// is a very special case where %EDNS only has partial information.
+/// If a future version of the %EDNS protocol introduces further relationship
+/// between the message and the %EDNS, we might reconsider the interface,
+/// probably with higher abstraction.
+class EDNS {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// We use the default copy constructor, default copy assignment operator,
+    /// and default destructors intentionally.
+    ///
+    /// Note about copyability: This version of this class is copyable,
+    /// but we may want to change it once we support EDNS options, when
+    /// we want to revise this class using the pimpl idiom.
+    /// But we should be careful about that: the python binding currently
+    /// assumes this class is copyable.
+    //@{
+    /// Constructor with the EDNS version.
+    /// An application would use this constructor to specify EDNS parameters
+    /// and/or options for outgoing DNS messages.
+    ///
+    /// All other parameters than the version number will be initialized to
+    /// reasonable defaults.
+    /// Specifically, the UDP payload size is set to
+    /// \c Message::DEFAULT_MAX_UDPSIZE, and DNSSEC is assumed to be not
+    /// supported.
+    /// These parameters can be altered via setter methods of this class.
+    /// Note, however, that the version number cannot be changed once
+    /// constructed.
+    ///
+    /// The version number parameter can be omitted, in which case the highest
+    /// supported version in this implementation will be assumed.
+    /// When specified, if it is larger than the highest supported version,
+    /// an exception of class \c isc::InvalidParameter will be thrown.
+    ///
+    /// This constructor throws no other exception.
+    ///
+    /// \param version The version number of the EDNS to be constructed.
+    explicit EDNS(const uint8_t version = SUPPORTED_VERSION);
+
+    /// \brief Constructor from resource record (RR) parameters.
+    ///
+    /// This constructor is intended to be used to construct an EDNS object
+    /// from an OPT RR contained in an incoming DNS message.
+    ///
+    /// Unlike many other constructors for this purpose, this constructor
+    /// does not take the bare wire-format %data in the form of an
+    /// \c InputBuffer object.  This is because parsing incoming EDNS is
+    /// highly context dependent and it's not feasible to handle it in a
+    /// completely polymorphic way.  For example, a DNS message parser would
+    /// have to check an OPT RR appears at most once in the message, and if
+    /// it appears it should be in the additional section.  So, the parser
+    /// needs to have an explicit check to see if an RR is of type OPT, and
+    /// then (if other conditions are met) construct a corresponding \c EDNS
+    /// object.  At that point the parser would have already converted the
+    /// wire %data into corresponding objects of \c Name, \c RRClass,
+    /// \c RRType, etc, and it makes more sense to pass them directly to the
+    /// constructor.
+    ///
+    /// In practice, top level applications rarely need to use this
+    /// constructor directly.  It should normally suffice to have a higher
+    /// level class such as \c Message do that job.
+    ///
+    /// This constructor checks the passed parameters to see if they are
+    /// valid in terms of the EDNS protocol specification.
+    /// \c name must be the root name ("."); otherwise, an exception of
+    /// class \c DNSMessageFORMERR will be thrown.
+    /// \c rrtype must specify the OPT RR type; otherwise, an exception of
+    /// class \c isc::InvalidParameter will be thrown.
+    /// The ENDS version number is extracted from \c rrttl.  If it is larger
+    /// than the higher supported version, an exception of class
+    /// \c DNSMessageBADVERS will be thrown.  Note that this is different from
+    /// the case of the same error in the other constructor.
+    /// This is intentional, so that the application can transparently convert
+    /// the exception to a response RCODE according to the protocol
+    /// specification.
+    ///
+    /// This initial implementation does not support EDNS options at all,
+    /// and \c rdata is simply ignored.  Future versions will support
+    /// options, and may throw exceptions while validating the given parameter.
+    ///
+    /// \b Note: since no other type than OPT for \c rrtype is allowed, this
+    /// parameter could actually have been omitted.  But it is intentionally
+    /// included as a parameter so that invalid usage of the construction
+    /// can be detected.  As noted above the caller should normally have
+    /// the corresponding \c RRType object at the time of call to this
+    /// constructor, so the overhead of having the additional parameter
+    /// should be marginal.
+    ///
+    /// \param name The owner name of the OPT RR.  This must be the root name.
+    /// \param rrclass The RR class of the OPT RR.
+    /// \param rrtype This must specify the OPT RR type.
+    /// \param rrttl The TTL of the OPT RR.
+    /// \param rdata The RDATA of the OPT RR.
+    EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+         const RRTTL& ttl, const rdata::Rdata& rdata);
+    //@}
+
+    ///
+    /// \name Getter and Setter Methods
+    ///
+    //@{
+    /// \brief Returns the version of EDNS.
+    ///
+    /// This method never throws an exception.
+    uint8_t getVersion() const { return (version_); }
+
+    /// \brief Returns the maximum payload size of UDP messages for the sender
+    /// of the message containing this \c EDNS.
+    ///
+    /// This method never throws an exception.
+    uint16_t getUDPSize() const { return (udp_size_); }
+
+    /// \brief Specify the maximum payload size of UDP messages that use
+    /// this EDNS.
+    ///
+    /// Unless explicitly specified, \c DEFAULT_MAX_UDPSIZE will be assumed
+    /// for the maximum payload size, regardless of whether EDNS OPT RR is
+    /// included or not.  This means if an application wants to send a message
+    /// with an EDNS OPT RR for specifying a larger UDP size, it must
+    /// explicitly specify the value using this method.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param udp_size The maximum payload size of UDP messages for the sender
+    /// of the message containing this \c EDNS.
+    void setUDPSize(const uint16_t udp_size) { udp_size_ = udp_size; }
+
+    /// \brief Returns whether the message sender is DNSSEC aware.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return true if DNSSEC is supported; otherwise false.
+    bool getDNSSECAwareness() const { return (dnssec_aware_); }
+
+    /// \brief Specifies whether the sender of the message containing this
+    /// \c EDNS is DNSSEC aware.
+    ///
+    /// If the parameter is true, a subsequent call to \c toWire() will
+    /// set the DNSSEC DO bit on for the corresponding OPT RR.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param is_aware \c true if DNSSEC is supported; \c false otherwise.
+    void setDNSSECAwareness(const bool is_aware) { dnssec_aware_ = is_aware; }
+    //@}
+
+    ///
+    /// \name Converter Methods
+    ///
+    //@{
+    /// \brief Render the \c EDNS in the wire format.
+    ///
+    /// This method renders the \c EDNS object as a form of DNS OPT RR
+    /// via \c renderer, which encapsulates output buffer and other rendering
+    /// contexts.
+    /// Since the \c EDNS object does not maintain the extended RCODE
+    /// information, a separate parameter \c extended_rcode must be passed to
+    /// this method.
+    ///
+    /// If by adding the OPT RR the message size would exceed the limit
+    /// maintained in \c renderer, this method skips rendering the RR
+    /// and returns 0; otherwise it returns 1, which is the number of RR
+    /// rendered.
+    ///
+    /// In the current implementation the return value is either 0 or 1, but
+    /// the return type is <code>unsigned int</code> to be consistent with
+    /// \c RRset::toWire().  In any case the caller shouldn't assume these are
+    /// only possible return values from this method.
+    ///
+    /// This method is mostly exception free, but it requires memory
+    /// allocation and if it fails a corresponding standard exception will be
+    /// thrown.
+    ///
+    /// In practice, top level applications rarely need to use this
+    /// method directly.  It should normally suffice to have a higher
+    /// level class such as \c Message do that job.
+    ///
+    /// <b>Note to developer:</b> the current implementation constructs an
+    /// \c RRset object for the OPT RR and calls its \c toWire() method,
+    /// which is inefficient.  In future, we may want to optimize this method
+    /// by caching the rendered image and having the application reuse the
+    /// same \c EDNS object when possible.
+    ///
+    /// \param renderer DNS message rendering context that encapsulates the
+    /// output buffer and name compression information.
+    /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
+    /// part of the EDNS OPT RR.
+    /// \return 1 if the OPT RR fits in the message size limit; otherwise 0.
+    unsigned int toWire(MessageRenderer& renderer,
+                        const uint8_t extended_rcode) const;
+
+    /// \brief Render the \c EDNS in the wire format.
+    ///
+    /// This method is same as \c toWire(MessageRenderer&,uint8_t)const
+    /// except it renders the OPT RR in an \c OutputBuffer and therefore
+    /// does not care about message size limit.
+    /// As a consequence it always returns 1.
+    unsigned int toWire(OutputBuffer& buffer,
+                        const uint8_t extended_rcode) const;
+
+    /// \brief Convert the EDNS to a string.
+    ///
+    /// The format of the resulting string is as follows:
+    /// \code ; EDNS: version: <version>, flags: <edns flags>; udp: <udp size>
+    /// \endcode
+    /// where
+    ///  - \em version is the EDNS version number (integer).
+    ///  - <em>edns flags</em> is a sequence of EDNS flag bits.  The only
+    ///    possible flag is the "DNSSEC OK", which is represented as "do".
+    ///  - <em>udp size</em> is sender's UDP payload size in bytes.
+    ///
+    /// The string will be terminated with a trailing newline character.
+    ///
+    /// When EDNS options are supported the output of this method will be
+    /// extended.
+    ///
+    /// This method is mostly exception free, but it may require memory
+    /// allocation and if it fails a corresponding standard exception will be
+    /// thrown.
+    ///
+    /// \return A string representation of \c EDNS.  See above for the format.
+    std::string toText() const;
+    //@}
+
+    // TBD: This method is currently not implemented.  We'll eventually need
+    // something like this.
+    //void addOption();
+
+private:
+    /// Helper method to define unified implementation for the public versions
+    /// of toWire().
+    template <typename Output>
+    int toWire(Output& output, const uint8_t extended_rcode) const;
+
+public:
+    /// \brief The highest EDNS version this implementation supports.
+    static const uint8_t SUPPORTED_VERSION = 0;
+private:
+    // We may eventually want to migrate to pimpl, especially when we support
+    // EDNS options.  In this initial implementation, we keep it simple.
+    const uint8_t version_;
+    uint16_t udp_size_;
+    bool dnssec_aware_;
+};
+
+/// \brief Create a new \c EDNS object from a set of RR parameters, also
+/// providing the extended RCODE value.
+///
+/// This function is similar to the EDNS class constructor
+/// \c EDNS::EDNS(const Name&, const RRClass&, const RRType&, const RRTTL&, const rdata::Rdata&)
+/// but is different in that
+/// - It dynamically creates a new object
+/// - It returns (via a reference argument) the topmost 8 bits of the extended
+/// RCODE encoded in the \c ttl.
+///
+/// On success, \c extended_rcode will be updated with the 8-bit part of
+/// the extended RCODE encoded in the TTL of the OPT RR.
+///
+/// The intended usage of this function is to parse an OPT RR of an incoming
+/// DNS message, while updating the RCODE of the message.
+/// One common usage patter is as follows:
+///
+/// \code Message msg;
+/// ...
+/// uint8_t extended_rcode;
+/// ConstEDNSPtr edns = ConstEDNSPtr(createEDNSFromRR(..., extended_rcode));
+/// rcode = Rcode(msg.getRcode().getCode(), extended_rcode);
+/// \endcode
+/// (although, like the \c EDNS constructor, normal applications wouldn't have
+/// to use this function directly).
+///
+/// This function provides the strong exception guarantee: Unless an
+/// exception is thrown \c extended_code won't be modified.
+///
+/// This function validates the given parameters and throws exceptions on
+/// failure in the same way as the \c EDNS class constructor.
+/// In addition, if memory allocation for the new object fails it throws the
+/// corresponding standard exception.
+///
+/// Note that this function returns a bare pointer to the newly allocated
+/// object, not a shared pointer object enclosing the pointer.
+/// The caller is responsible for deleting the object after the use of it
+/// (typically, the caller would immediately encapsulate the returned pointer
+/// in a shared pointer object, \c EDNSPtr or \c ConstEDNSPtr).
+/// It returns a bare pointer so that it can be used where the use of a shared
+/// pointer is impossible or not desirable.
+///
+/// Note to developers: there is no strong technical reason why this function
+/// cannot be a constructor of the \c EDNS class or even integrated into the
+/// constructor.  But we decided to make it a separate free function so that
+/// constructors will be free from side effects (which is in itself a matter
+/// of preference).
+///
+/// \param name The owner name of the OPT RR.  This must be the root name.
+/// \param rrclass The RR class of the OPT RR.
+/// \param rrtype This must specify the OPT RR type.
+/// \param rrttl The TTL of the OPT RR.
+/// \param rdata The RDATA of the OPT RR.
+/// \param extended_rcode A placeholder to store the topmost 8 bits of the
+/// extended Rcode.
+/// \return A pointer to the created \c EDNS object.
+EDNS* createEDNSFromRR(const Name& name, const RRClass& rrclass,
+                       const RRType& rrtype, const RRTTL& ttl,
+                       const rdata::Rdata& rdata, uint8_t& extended_rcode);
+
+/// \brief Insert the \c EDNS as a string into stream.
+///
+/// This method convert \c edns into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param edns A reference to an \c EDNS object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const EDNS& edns);
+}
+}
+#endif  // __EDNS_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 0 - 71
src/lib/dns/message_test.py

@@ -1,71 +0,0 @@
-from bind10_dns import *
-from struct import *
-
-id = ["10","35"]
-flags = ["85", "00"]
-sections = ["00","01","00","02","00","00","00","00"]
-query_raw_name = ["04","74","65","73","74","07","65","78","61","6d","70","6c","65","03","63","6f","6d","00"]
-query_type_class = ["00","01","00","01"]
-answer_compress = ["c0","0c"]
-answer1_ttl_type_class_rdlen_rdata = ["00","01","00","01","00","00","0e","10","00","04","c0","00","02","01"]
-answer2_ttl_type_class_rdlen_rdata = ["00","01","00","01","00","00","1c","20","00","04","c0","00","02","02"]
-
-bytes = pack("B" * len(query_raw_name), *[int(i,16) for i in query_raw_name])
-query_name = name(input_buffer(bytes))
-print("query name is ", query_name.to_text())
-
-message_raw_data = id + flags + sections + query_raw_name + query_type_class + answer_compress + answer1_ttl_type_class_rdlen_rdata + answer_compress + \
-        answer2_ttl_type_class_rdlen_rdata
-
-m = message(message_mode.PARSE)
-message_bytes = pack("B" * len(message_raw_data), *[int(i,16) for i in message_raw_data])
-m.from_wire(input_buffer(message_bytes))
-if m.get_qid() == int("1035", 16):
-    print("id is correct")
-
-if m.get_opcode() == op_code.QUERY():
-    print("opcode is correct")
-
-if m.get_rcode() == rcode.NOERROR():
-    print("rcode is correct")
-
-if m.get_header_flag(message_flag.QR()):
-    print("qr is correct")
-
-if m.get_header_flag(message_flag.RD()):
-    print("rd is correct")
-
-if m.get_header_flag(message_flag.AA()):
-    print("aa is correct")
-
-if m.get_rr_count(section.QUESTION()) == 1:
-    print("qustion rr count is correct")
-
-if m.get_rr_count(section.ANSWER()) == 2:
-    print("answer rr count is ok")
-
-if m.get_rr_count(section.AUTHORITY()) == 0 and m.get_rr_count(section.ADDITIONAL()) == 0:
-    print("authority and additional rr count is ok")
-
-question_iter = question_iter(m)
-question = question_iter.get_question()
-print("question name is ", question.get_name().to_text())
-if question.get_type() == rr_type.A():
-    print("question rr type is A")
-if question.get_class() == rr_class.IN():
-    print("question rr class is IN")
-
-answer_rrset_iter = section_iter(m, section.ANSWER())
-answer = answer_rrset_iter.get_rrset()
-print("answer part name is ", answer.get_name().to_text())
-if answer.get_type() == rr_type.A() and answer.get_class() == rr_class.IN():
-    print("answer part is A record and class IN")
-
-rdata_iter = answer.get_rdata_iterator()
-rdata_iter.first()
-print("first part of rdata is ", rdata_iter.get_current().to_text())
-rdata_iter.next()
-print("second part of rdata is", rdata_iter.get_current().to_text())
-rdata_iter.next()
-if rdata_iter.is_last():
-    print("answer part has two rr")

+ 0 - 0
src/lib/dns/opcode.cc


Some files were not shown because too many files changed in this diff