BIND 10 master, updated. 241c11e7b06b233585b52df53a9273ab8e0ad405 Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Apr 26 08:59:23 UTC 2011
The branch, master has been updated
via 241c11e7b06b233585b52df53a9273ab8e0ad405 (commit)
via dfddb7ba4637530a542b833ee14083421eeba826 (commit)
via 1cbd51919237a6e65983be46e4f5a63d1877b1d3 (commit)
via 5a6f9f8c21674d8f70fe33de2b7d7e23c105e888 (commit)
via 5573b0bc953d1a9589d2e0f06b8da58dadb64bc3 (commit)
via b1cb4c4a1421bb512d093690c95d27425b33e1e5 (commit)
via 521c140da800fdb7a7c87e53e01270e02ae4bce2 (commit)
via 7b1677584cd0cc94825741792f9ed66f16029938 (commit)
via dcd582cc0a7d02d69c3c7c28916980f71034f9b2 (commit)
via c6947d22cd80d5934ad53c91b74c640c8a64e57c (commit)
via 36b32903369f9a3afda93e2537d369d2402a3bfc (commit)
via bb5efb65cb0114b5bf985fd1588e8b645d4c45aa (commit)
via 7ae7577211c77f31a9f30be122d245add72d893d (commit)
via 0eea320309f86b8f1f95bed053078bdcf4c82071 (commit)
via 8eb1d7a04312f7fa70ee3c986e66b6444a1dfde4 (commit)
via 99c44efcd64b8262614354faaef2b5abb6fb644e (commit)
via af9f4f7fdaf88b906022bb808f44c6e586e702fc (commit)
via 1ea4834d960f3d5f14ff8cee312448baab72e075 (commit)
via f1ac03b8147047bee020fbc019d4969f16c75064 (commit)
via 4e8ed21d6a2e80e81393e64d808bb07655f42cbf (commit)
via 5012fabe00f1ebcbd2d67564df325fc69a529b5c (commit)
via 160b06212023cfce40c1ff6f13c5307c4d4f6e50 (commit)
via 8541053e6fffaf84127fddd2c0a25d0dcd5c635b (commit)
via 5e89e6afd429bf91d66f2f70435342fa1de388a4 (commit)
via a37c25cdb88ceb49b518d27a324ad7523f936fb2 (commit)
via b1610a09098d7019c6701daf77c2b8b98eb03b75 (commit)
via 04330565df2cc183a92f196772e03b05482046eb (commit)
via 3437b4fa8efb83ce5395ec159e85b38951e23504 (commit)
via 4b65b6bdb511b88979d0b1c6f44161c223e1dc9d (commit)
via 3f6620c5ba918a2a411aca9430fd8ac347e1aeca (commit)
via 2018011dab54aaff10385ae4905c1a5706997062 (commit)
via b751b7e0cc08dca691029169909a54a0bd1e6b45 (commit)
via d7cf894e555a8aa8508f695fabac2061822d454d (commit)
via 41361697fe474af2b511e922c18dc0b211f1344c (commit)
via 0dc2e2582d63c42847400701a7c3c6b0fb2d6d9b (commit)
via f8a702716291db0b289aa211e5adac6fb2790cb7 (commit)
via 390af5c81d043a9f4cc8e908365e7f0734bddb8f (commit)
via 2df69541deceaa8a3bbbcc04d2087e94aa063d11 (commit)
via ed3c8e2f7a386b05b696d3ff79fa285ae7a050e9 (commit)
via 1f16df990ddee676893ae701b8553fd42d6992a7 (commit)
via 342926bcc0d317101321ff9afe6cf61967472a6a (commit)
via 7a7299fb09d2a02023041d3fe7a92e22d17ab549 (commit)
from d743478f2b76e0f37381f64d5f4e90417260d67c (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 241c11e7b06b233585b52df53a9273ab8e0ad405
Merge: dfddb7ba4637530a542b833ee14083421eeba826 d743478f2b76e0f37381f64d5f4e90417260d67c
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Apr 26 17:55:16 2011 +0900
Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10
commit dfddb7ba4637530a542b833ee14083421eeba826
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Apr 26 17:39:09 2011 +0900
[master] add git id for trac #547
commit 1cbd51919237a6e65983be46e4f5a63d1877b1d3
Merge: bc436f1f6715aabcc62c468fa6cfd7d3bf694f29 5a6f9f8c21674d8f70fe33de2b7d7e23c105e888
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Apr 26 17:34:13 2011 +0900
[master] Merge branch 'trac547-summarized'
Conflicts:
ChangeLog
src/bin/stats/tests/b10-stats_test.py
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 11 +
configure.ac | 13 +-
src/bin/bind10/bind10.py.in | 5 +
src/bin/bind10/tests/bind10_test.py.in | 21 +-
src/bin/stats/Makefile.am | 27 +-
src/bin/stats/b10-stats-httpd.8 | 132 ++++++
src/bin/stats/b10-stats-httpd.xml | 215 +++++++++
...-stats_stub.sh.in => run_b10-stats-httpd.sh.in} | 10 +-
src/bin/stats/run_b10-stats.sh.in | 6 +-
src/bin/stats/stats-httpd-xml.tpl.in | 24 +
src/bin/stats/stats-httpd-xsd.tpl.in | 38 ++
src/bin/stats/stats-httpd-xsl.tpl.in | 56 +++
src/bin/stats/stats-httpd.spec.in | 54 +++
src/bin/stats/stats.py.in | 9 +-
src/bin/stats/{stats.spec.pre.in => stats.spec.in} | 2 +-
src/bin/stats/stats_httpd.py.in | 484 ++++++++++++++++++++
src/bin/stats/stats_stub.py.in | 154 -------
src/bin/stats/tests/Makefile.am | 10 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 444 ++++++++++++++++++
src/bin/stats/tests/b10-stats_stub_test.py | 115 -----
src/bin/stats/tests/b10-stats_test.py | 16 +-
.../stats/tests/fake_select.py} | 37 +-
src/bin/stats/tests/fake_socket.py | 70 +++
src/bin/stats/tests/http/Makefile.am | 2 +
.../stats/tests/{isc/utils => http}/__init__.py | 0
src/bin/stats/tests/http/server.py | 96 ++++
src/bin/stats/tests/isc/cc/session.py | 46 ++-
src/bin/stats/tests/isc/config/ccsession.py | 59 +++-
src/bin/stats/tests/isc/util/process.py | 5 +-
src/bin/stats/tests/isc/utils/Makefile.am | 2 -
src/bin/stats/tests/isc/utils/process.py | 18 -
src/bin/stats/tests/stats_test.in | 8 +-
32 files changed, 1818 insertions(+), 371 deletions(-)
create mode 100644 src/bin/stats/b10-stats-httpd.8
create mode 100644 src/bin/stats/b10-stats-httpd.xml
rename src/bin/stats/{run_b10-stats_stub.sh.in => run_b10-stats-httpd.sh.in} (86%)
create mode 100644 src/bin/stats/stats-httpd-xml.tpl.in
create mode 100644 src/bin/stats/stats-httpd-xsd.tpl.in
create mode 100644 src/bin/stats/stats-httpd-xsl.tpl.in
create mode 100644 src/bin/stats/stats-httpd.spec.in
mode change 100755 => 100644 src/bin/stats/stats.py.in
rename src/bin/stats/{stats.spec.pre.in => stats.spec.in} (99%)
create mode 100644 src/bin/stats/stats_httpd.py.in
delete mode 100755 src/bin/stats/stats_stub.py.in
create mode 100644 src/bin/stats/tests/b10-stats-httpd_test.py
delete mode 100644 src/bin/stats/tests/b10-stats_stub_test.py
copy src/{lib/python/isc/testutils/parse_args.py => bin/stats/tests/fake_select.py} (50%)
create mode 100644 src/bin/stats/tests/fake_socket.py
create mode 100644 src/bin/stats/tests/http/Makefile.am
rename src/bin/stats/tests/{isc/utils => http}/__init__.py (100%)
create mode 100644 src/bin/stats/tests/http/server.py
delete mode 100644 src/bin/stats/tests/isc/utils/Makefile.am
delete mode 100644 src/bin/stats/tests/isc/utils/process.py
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index ba87f20..87f6b15 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+225. [func] naokikambe
+ Added the HTTP/XML interface(b10-stats-httpd) to the statistics feature
+ in BIND 10. b10-stats-httpd is a standalone HTTP server and it requests
+ statistics data to the stats daemon(b10-stats) and sends it to HTTP
+ clients in XML format. Items of the data collected via b10-stats-httpd
+ are almost equivalent to ones which are collected via bindctl. Since it
+ also can send XSL(Extensible Stylessheet Language) document and XSD(XML
+ Schema definition) document, XML document is human-friendly to view
+ through web browsers and its data types are strictly defined.
+ (Trac #547, git 1cbd51919237a6e65983be46e4f5a63d1877b1d3)
+
224. [bug] jinmei
b10-auth, src/lib/datasrc: inconsistency between the hot spot
cache and actual data source could cause a crash while query
diff --git a/configure.ac b/configure.ac
index 81df476..82bd3d7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -640,6 +640,7 @@ AC_CONFIG_FILES([Makefile
src/bin/stats/tests/isc/config/Makefile
src/bin/stats/tests/isc/util/Makefile
src/bin/stats/tests/testdata/Makefile
+ src/bin/stats/tests/http/Makefile
src/bin/usermgr/Makefile
src/bin/tests/Makefile
src/lib/Makefile
@@ -729,10 +730,14 @@ AC_OUTPUT([doc/version.ent
src/bin/zonemgr/tests/zonemgr_test
src/bin/zonemgr/run_b10-zonemgr.sh
src/bin/stats/stats.py
- src/bin/stats/stats_stub.py
- src/bin/stats/stats.spec.pre
+ src/bin/stats/stats_httpd.py
+ src/bin/stats/stats.spec
+ src/bin/stats/stats-httpd.spec
+ src/bin/stats/stats-httpd-xml.tpl
+ src/bin/stats/stats-httpd-xsd.tpl
+ src/bin/stats/stats-httpd-xsl.tpl
src/bin/stats/run_b10-stats.sh
- src/bin/stats/run_b10-stats_stub.sh
+ src/bin/stats/run_b10-stats-httpd.sh
src/bin/stats/tests/stats_test
src/bin/bind10/bind10.py
src/bin/bind10/run_bind10.sh
@@ -774,7 +779,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
chmod +x src/bin/stats/tests/stats_test
chmod +x src/bin/stats/run_b10-stats.sh
- chmod +x src/bin/stats/run_b10-stats_stub.sh
+ chmod +x src/bin/stats/run_b10-stats-httpd.sh
chmod +x src/bin/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/tests/cmdctl_test
chmod +x src/bin/xfrin/tests/xfrin_test
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
index e6504e1..648d085 100755
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -546,6 +546,9 @@ class BoB:
def start_stats(self, c_channel_env):
self.start_simple("b10-stats", c_channel_env)
+ def start_stats_httpd(self, c_channel_env):
+ self.start_simple("b10-stats-httpd", c_channel_env)
+
def start_cmdctl(self, c_channel_env):
"""
Starts the command control process
@@ -595,6 +598,7 @@ class BoB:
# ... and finally start the remaining processes
self.start_stats(c_channel_env)
+ self.start_stats_httpd(c_channel_env)
self.start_cmdctl(c_channel_env)
def startup(self):
@@ -644,6 +648,7 @@ class BoB:
self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
+ self.cc_session.group_sendmsg(cmd, "StatsHttpd", "StatsHttpd")
def stop_process(self, process, recipient):
"""
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 8432c89..2ffe2b4 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -183,6 +183,7 @@ class MockBob(BoB):
self.xfrin = False
self.zonemgr = False
self.stats = False
+ self.stats_httpd = False
self.cmdctl = False
self.c_channel_env = {}
self.processes = { }
@@ -236,10 +237,15 @@ class MockBob(BoB):
self.processes[10] = ProcessInfo('b10-stats', ['/bin/false'])
self.processes[10].pid = 10
+ def start_stats_httpd(self, c_channel_env):
+ self.stats_httpd = True
+ self.processes[11] = ProcessInfo('b10-stats-httpd', ['/bin/false'])
+ self.processes[11].pid = 11
+
def start_cmdctl(self, c_channel_env):
self.cmdctl = True
- self.processes[11] = ProcessInfo('b10-cmdctl', ['/bin/false'])
- self.processes[11].pid = 11
+ self.processes[12] = ProcessInfo('b10-cmdctl', ['/bin/false'])
+ self.processes[12].pid = 12
# We don't really use all of these stop_ methods. But it might turn out
# someone would add some stop_ method to BoB and we want that one overriden
@@ -289,9 +295,14 @@ class MockBob(BoB):
del self.processes[10]
self.stats = False
+ def stop_stats_httpd(self):
+ if self.stats_httpd:
+ del self.processes[11]
+ self.stats_httpd = False
+
def stop_cmdctl(self):
if self.cmdctl:
- del self.processes[11]
+ del self.processes[12]
self.cmdctl = False
class TestStartStopProcessesBob(unittest.TestCase):
@@ -316,6 +327,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
self.assertEqual(bob.xfrin, auth)
self.assertEqual(bob.zonemgr, auth)
self.assertEqual(bob.stats, core)
+ self.assertEqual(bob.stats_httpd, core)
self.assertEqual(bob.cmdctl, core)
def check_preconditions(self, bob):
@@ -544,7 +556,8 @@ class TestBossCmd(unittest.TestCase):
[8, 'b10-xfrin'],
[9, 'b10-zonemgr'],
[10, 'b10-stats'],
- [11, 'b10-cmdctl']]
+ [11, 'b10-stats-httpd'],
+ [12, 'b10-cmdctl']]
self.assertEqual(answer, {'result': [0, processes]})
class TestParseArgs(unittest.TestCase):
diff --git a/src/bin/stats/Makefile.am b/src/bin/stats/Makefile.am
index b173813..485bc05 100644
--- a/src/bin/stats/Makefile.am
+++ b/src/bin/stats/Makefile.am
@@ -2,35 +2,36 @@ SUBDIRS = tests
pkglibexecdir = $(libexecdir)/@PACKAGE@
-pkglibexec_SCRIPTS = b10-stats
-noinst_SCRIPTS = b10-stats_stub
+pkglibexec_SCRIPTS = b10-stats b10-stats-httpd
b10_statsdir = $(pkgdatadir)
-b10_stats_DATA = stats.spec
+b10_stats_DATA = stats.spec stats-httpd.spec
+b10_stats_DATA += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
-CLEANFILES = stats.spec b10-stats stats.pyc stats.pyo b10-stats_stub stats_stub.pyc stats_stub.pyo
+CLEANFILES = b10-stats stats.pyc
+CLEANFILES += b10-stats-httpd stats_httpd.pyc
-man_MANS = b10-stats.8
-EXTRA_DIST = $(man_MANS) b10-stats.xml
+man_MANS = b10-stats.8 b10-stats-httpd.8
+EXTRA_DIST = $(man_MANS) b10-stats.xml b10-stats-httpd.xml
+EXTRA_DIST += stats.spec stats-httpd.spec
+EXTRA_DIST += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
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
+b10-stats-httpd.8: b10-stats-httpd.xml
+ xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-stats-httpd.xml
-stats.spec: stats.spec.pre
- $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" stats.spec.pre >$@
+endif
# 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 >$@
+b10-stats-httpd: stats_httpd.py
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" stats_httpd.py >$@
chmod a+x $@
diff --git a/src/bin/stats/b10-stats-httpd.8 b/src/bin/stats/b10-stats-httpd.8
new file mode 100644
index 0000000..c066f91
--- /dev/null
+++ b/src/bin/stats/b10-stats-httpd.8
@@ -0,0 +1,132 @@
+'\" t
+.\" Title: b10-stats-httpd
+.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
+.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
+.\" Date: Mar 8, 2011
+.\" Manual: BIND10
+.\" Source: BIND10
+.\" Language: English
+.\"
+.TH "B10\-STATS\-HTTPD" "8" "Mar 8, 2011" "BIND10" "BIND10"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+b10-stats-httpd \- BIND 10 HTTP server for HTTP/XML interface of statistics
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-stats\-httpd\fR\ 'u
+\fBb10\-stats\-httpd\fR [\fB\-v\fR]| [\fB\-\-verbose\fR]
+.SH "DESCRIPTION"
+.PP
+
+\fBb10\-stats\-httpd\fR
+is a standalone HTTP server\&. It is intended for HTTP/XML interface for statistics module\&. This server process runs as a process separated from the process of the BIND 10 Stats daemon (\fBb10\-stats\fR)\&. The server is initially executed by the BIND 10 boss process (\fBbind10\fR) and eventually exited by it\&. The server is intended to be server requests by HTTP clients like web browsers and third\-party modules\&. When the server is asked, it requests BIND 10 statistics data from
+\fBb10\-stats\fR, and it sends the data back in Python dictionary format and the server converts it into XML format\&. The server sends it to the HTTP client\&. The server can send three types of document, which are XML (Extensible Markup Language), XSD (XML Schema definition) and XSL (Extensible Stylesheet Language)\&. The XML document is the statistics data of BIND 10, The XSD document is the data schema of it, and The XSL document is the style sheet to be showed for the web browsers\&. There is different URL for each document\&. But please note that you would be redirected to the URL of XML document if you request the URL of the root document\&. For example, you would be redirected to http://127\&.0\&.0\&.1:8000/bind10/statistics/xml if you request http://127\&.0\&.0\&.1:8000/\&. Please see the manual and the spec file of
+\fBb10\-stats\fR
+for more details about the items of BIND 10 statistics\&. The server uses CC session in communication with
+\fBb10\-stats\fR\&. CC session is provided by
+\fBb10\-msgq\fR
+which is started by
+\fBbind10\fR
+in advance\&. The server is implemented by HTTP\-server libraries included in Python 3\&. The server obtains the configuration from the config manager (\fBb10\-cfgmgr\fR) in runtime\&. Please see below for more details about this spec file and configuration of the server\&.
+.SH "OPTIONS"
+.PP
+The argument is as follow:
+.PP
+\fB\-v\fR, \fB\-\-verbose\fR
+.RS 4
+
+\fBb10\-stats\-httpd\fR
+switches to verbose mode and sends verbose messages to STDOUT\&.
+.RE
+.SH "FILES"
+.PP
+
+/usr/local/share/bind10\-devel/stats\-httpd\&.spec
+\(em the spec file of
+\fBb10\-stats\-httpd\fR\&. This file contains configurable settings of
+\fBb10\-stats\-httpd\fR\&. This setting can be configured in runtime via
+bindctl(1)\&. Please see the manual of
+bindctl(1)
+about how to configure the settings\&.
+.PP
+
+/usr/local/share/bind10\-devel/stats\-httpd\-xml\&.tpl
+\(em the template file of XML document\&.
+.PP
+
+/usr/local/share/bind10\-devel/stats\-httpd\-xsd\&.tpl
+\(em the template file of XSD document\&.
+.PP
+
+/usr/local/share/bind10\-devel/stats\-httpd\-xsl\&.tpl
+\(em the template file of XSL document\&.
+.SH "CONFIGURATION AND COMMANDS"
+.PP
+The configurable setting in
+stats\-httpd\&.spec
+is:
+.PP
+\fIlisten_on\fR
+.RS 4
+a list of pairs of address and port for
+\fBb10\-stats\-httpd\fR
+to listen HTTP requests on\&. The pair consists of the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. The default setting is the list of address 127\&.0\&.0\&.1 port 8000\&. If the server is started by the default setting being left, for example, the URL for XML document is http://127\&.0\&.0\&.1:8000/bind10/statistics/xml\&. And also IPv6 addresses can be configured and they works in the runtime environment for dual stack\&. You can change the settings through
+bindctl(8)\&.
+.RE
+.PP
+The commands in
+stats\-httpd\&.spec
+are:
+.PP
+\fBstatus\fR
+.RS 4
+shows the status of
+\fBb10\-stats\-httpd\fR
+with its PID\&.
+.RE
+.PP
+\fBshutdown\fR
+.RS 4
+exits the
+\fBb10\-stats\-httpd\fR
+process\&. (Note that the BIND 10 boss process will restart this service\&.)
+.RE
+.SH "SEE ALSO"
+.PP
+
+\fBb10-stats\fR(8),
+\fBb10-msgq\fR(8),
+\fBb10-cfgmgr\fR(8),
+\fBbind10\fR(8),
+\fBbindctl\fR(1),
+BIND 10 Guide\&.
+.SH "HISTORY"
+.PP
+
+\fBb10\-stats\-httpd\fR
+was designed and implemented by Naoki Kambe of JPRS in Mar 2011\&.
+.SH "COPYRIGHT"
+.br
+Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC")
+.br
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
new file mode 100644
index 0000000..5cf3b4b
--- /dev/null
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -0,0 +1,215 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2011 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.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>Mar 8, 2011</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-stats-httpd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-stats-httpd</refname>
+ <refpurpose>BIND 10 HTTP server for HTTP/XML interface of statistics</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2011</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis> <command>b10-stats-httpd</command>
+ <arg><option>-v</option></arg>|<arg><option>--verbose</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ <command>b10-stats-httpd</command> is a standalone HTTP server. It is
+ intended for HTTP/XML interface for statistics module. This server
+ process runs as a process separated from the process of the BIND 10 Stats
+ daemon (<command>b10-stats</command>). The server is initially executed
+ by the BIND 10 boss process (<command>bind10</command>) and eventually
+ exited by it. The server is intended to be server requests by HTTP
+ clients like web browsers and third-party modules. When the server is
+ asked, it requests BIND 10 statistics data from
+ <command>b10-stats</command>, and it sends the data back in Python
+ dictionary format and the server converts it into XML format. The server
+ sends it to the HTTP client. The server can send three types of document,
+ which are XML (Extensible Markup Language), XSD (XML Schema definition)
+ and XSL (Extensible Stylesheet Language). The XML document is the
+ statistics data of BIND 10, The XSD document is the data schema of it,
+ and The XSL document is the style sheet to be showed for the web
+ browsers. There is different URL for each document. But please note that
+ you would be redirected to the URL of XML document if you request the URL
+ of the root document. For example, you would be redirected to
+ http://127.0.0.1:8000/bind10/statistics/xml if you request
+ http://127.0.0.1:8000/. Please see the manual and the spec file
+ of <command>b10-stats</command> for more details about the items of BIND
+ 10 statistics. The server uses CC session in communication
+ with <command>b10-stats</command>. CC session is provided
+ by <command>b10-msgq</command> which is started
+ by <command>bind10</command> in advance. The server is implemented by
+ HTTP-server libraries included in Python 3. The server obtains the
+ configuration from the config manager (<command>b10-cfgmgr</command>) in
+ runtime. Please see below for more details about this spec file and
+ configuration of the server.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+ <para>The argument is as follow:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-v</option>, <option>--verbose</option></term>
+ <listitem>
+ <para>
+ <command>b10-stats-httpd</command> switches to verbose mode and sends
+ verbose messages to STDOUT.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>FILES</title>
+ <para>
+ <filename>/usr/local/share/bind10-devel/stats-httpd.spec</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — the spec file of <command>b10-stats-httpd</command>. This file
+ contains configurable settings
+ of <command>b10-stats-httpd</command>. This setting can be configured in
+ runtime via
+ <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>. Please
+ see the manual
+ of <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum> about
+ how to configure the settings.
+ </para>
+ <para>
+ <filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — the template file of XML document.
+ </para>
+ <para>
+ <filename>/usr/local/share/bind10-devel/stats-httpd-xsd.tpl</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — the template file of XSD document.
+ </para>
+ <para>
+ <filename>/usr/local/share/bind10-devel/stats-httpd-xsl.tpl</filename>
+ <!--TODO: The filename should be computed from prefix-->
+ — the template file of XSL document.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable setting in
+ <filename>stats-httpd.spec</filename> is:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><varname>listen_on</varname></term>
+ <listitem>
+ <para>
+ a list of pairs of address and port for
+ <command>b10-stats-httpd</command> to listen HTTP requests on. The
+ pair consists of the <varname>address</varname> string
+ and <varname>port</varname> number. The default setting is the list
+ of address 127.0.0.1 port 8000. If the server is started by the
+ default setting being left, for example, the URL for XML document
+ is http://127.0.0.1:8000/bind10/statistics/xml. And also IPv6
+ addresses can be configured and they works in the runtime
+ environment for dual stack. You can change the settings
+ through <refentrytitle>bindctl</refentrytitle><manvolnum>8</manvolnum>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ The commands in <filename>stats-httpd.spec</filename> are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><command>status</command></term>
+ <listitem>
+ <para>
+ shows the status of <command>b10-stats-httpd</command> with its
+ PID.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>shutdown</command></term>
+ <listitem>
+ <para>
+ exits the <command>b10-stats-httpd</command> process. (Note that
+ the BIND 10 boss process will restart this service.)
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ <command>b10-stats-httpd</command> was designed and implemented by Naoki
+ Kambe of JPRS in Mar 2011.
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/stats/run_b10-stats-httpd.sh.in b/src/bin/stats/run_b10-stats-httpd.sh.in
new file mode 100644
index 0000000..6a20412
--- /dev/null
+++ b/src/bin/stats/run_b10-stats-httpd.sh.in
@@ -0,0 +1,33 @@
+#! /bin/sh
+
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
+export PYTHON_EXEC
+
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
+STATS_PATH=@abs_top_builddir@/src/bin/stats
+
+B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+export B10_FROM_SOURCE
+
+cd ${STATS_PATH}
+exec ${PYTHON_EXEC} -O b10-stats-httpd "$@"
diff --git a/src/bin/stats/run_b10-stats.sh.in b/src/bin/stats/run_b10-stats.sh.in
index 65b9737..5623082 100644
--- a/src/bin/stats/run_b10-stats.sh.in
+++ b/src/bin/stats/run_b10-stats.sh.in
@@ -1,6 +1,6 @@
#! /bin/sh
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010, 2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -24,8 +24,8 @@ export PYTHONPATH
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
-B10_FROM_BUILD=@abs_top_builddir@
-export B10_FROM_BUILD
+B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+export B10_FROM_SOURCE
STATS_PATH=@abs_top_builddir@/src/bin/stats
diff --git a/src/bin/stats/run_b10-stats_stub.sh.in b/src/bin/stats/run_b10-stats_stub.sh.in
deleted file mode 100644
index 19ade5c..0000000
--- a/src/bin/stats/run_b10-stats_stub.sh.in
+++ /dev/null
@@ -1,33 +0,0 @@
-#! /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:- at PYTHON@}
-export PYTHON_EXEC
-
-PYTHONPATH=@abs_top_builddir@/src/lib/python
-export PYTHONPATH
-
-B10_FROM_BUILD=@abs_top_srcdir@
-export B10_FROM_BUILD
-
-BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
-export BIND10_MSGQ_SOCKET_FILE
-
-STATS_PATH=@abs_top_builddir@/src/bin/stats
-
-cd ${STATS_PATH}
-exec ${PYTHON_EXEC} -O b10-stats_stub "$@"
diff --git a/src/bin/stats/stats-httpd-xml.tpl.in b/src/bin/stats/stats-httpd-xml.tpl.in
new file mode 100644
index 0000000..d5846ad
--- /dev/null
+++ b/src/bin/stats/stats-httpd-xml.tpl.in
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="$xsl_url_path"?>
+<!--
+ - Copyright (C) 2011 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.
+-->
+
+<stats:stats_data version="1.0"
+ xmlns:stats="$xsd_namespace"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="$xsd_namespace $xsd_url_path">
+ $xml_string
+</stats:stats_data>
diff --git a/src/bin/stats/stats-httpd-xsd.tpl.in b/src/bin/stats/stats-httpd-xsd.tpl.in
new file mode 100644
index 0000000..6ad1280
--- /dev/null
+++ b/src/bin/stats/stats-httpd-xsd.tpl.in
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright (C) 2011 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.
+-->
+
+<schema targetNamespace="$xsd_namespace"
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:stats="$xsd_namespace">
+ <annotation>
+ <documentation xml:lang="en">XML schema of the statistics
+ data in BIND 10</documentation>
+ </annotation>
+ <element name="stats_data">
+ <annotation>
+ <documentation>A set of statistics data</documentation>
+ </annotation>
+ <complexType>
+ $xsd_string
+ <attribute name="version" type="token" use="optional" default="1.0">
+ <annotation>
+ <documentation>Version number of syntax</documentation>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+</schema>
diff --git a/src/bin/stats/stats-httpd-xsl.tpl.in b/src/bin/stats/stats-httpd-xsl.tpl.in
new file mode 100644
index 0000000..01ffdc6
--- /dev/null
+++ b/src/bin/stats/stats-httpd-xsl.tpl.in
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright (C) 2011 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.
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:stats="$xsd_namespace">
+ <xsl:output method="html" encoding="UTF-8"
+ doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
+ doctype-system=" http://www.w3.org/TR/html4/loose.dtd " />
+ <xsl:template match="/">
+ <html lang="en">
+ <head>
+ <title>BIND 10 Statistics</title>
+ <style type="text/css"><![CDATA[
+table {
+ border: 1px #000000 solid;
+ border-collapse: collapse;
+}
+td, th {
+ padding: 3px 20px;
+ border: 1px #000000 solid;
+}
+td.title {
+ text-decoration:underline;
+}
+]]>
+ </style>
+ </head>
+ <body>
+ <h1>BIND 10 Statistics</h1>
+ <table>
+ <tr>
+ <th>Title</th>
+ <th>Value</th>
+ </tr>
+ <xsl:apply-templates />
+ </table>
+ </body>
+ </html>
+ </xsl:template>
+ $xsl_string
+</xsl:stylesheet>
diff --git a/src/bin/stats/stats-httpd.spec.in b/src/bin/stats/stats-httpd.spec.in
new file mode 100644
index 0000000..6307135
--- /dev/null
+++ b/src/bin/stats/stats-httpd.spec.in
@@ -0,0 +1,54 @@
+{
+ "module_spec": {
+ "module_name": "StatsHttpd",
+ "module_description": "Stats HTTP daemon",
+ "config_data": [
+ {
+ "item_name": "listen_on",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [
+ {
+ "address": "127.0.0.1",
+ "port": 8000
+ }
+ ],
+ "list_item_spec": {
+ "item_name": "address",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "127.0.0.1",
+ "item_description": "listen-on address for HTTP"
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 8000,
+ "item_description": "listen-on port for HTTP"
+ }
+ ]
+ },
+ "item_description": "http listen-on address and port"
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "status",
+ "command_description": "Status of the stats httpd",
+ "command_args": []
+ },
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down the stats httpd",
+ "command_args": []
+ }
+ ]
+ }
+}
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
old mode 100755
new mode 100644
index eeddd92..dac8796
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -35,15 +35,16 @@ if __name__ == 'stats': #@@REMOVED@@
import isc.util.process
isc.util.process.rename()
-# If B10_FROM_BUILD is set in the environment, we use data files
+# If B10_FROM_SOURCE 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"
+if "B10_FROM_SOURCE" in os.environ:
+ SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + "stats.spec"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@/@PACKAGE@/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
+ SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
class Singleton(type):
"""
diff --git a/src/bin/stats/stats.spec.in b/src/bin/stats/stats.spec.in
new file mode 100644
index 0000000..4d42ebf
--- /dev/null
+++ b/src/bin/stats/stats.spec.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": "bind10.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": []
+ }
+ ]
+ }
+}
diff --git a/src/bin/stats/stats.spec.pre.in b/src/bin/stats/stats.spec.pre.in
deleted file mode 100644
index 6970250..0000000
--- a/src/bin/stats/stats.spec.pre.in
+++ /dev/null
@@ -1,140 +0,0 @@
-{
- "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": []
- }
- ]
- }
-}
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
new file mode 100644
index 0000000..20c3442
--- /dev/null
+++ b/src/bin/stats/stats_httpd.py.in
@@ -0,0 +1,484 @@
+#!@PYTHON@
+
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+A standalone HTTP server for HTTP/XML interface of statistics in BIND 10
+
+"""
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+import time
+import errno
+import select
+from optparse import OptionParser, OptionValueError
+import http.server
+import socket
+import string
+import xml.etree.ElementTree
+
+import isc.cc
+import isc.config
+import isc.util.process
+
+# If B10_FROM_SOURCE 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_SOURCE" in os.environ:
+ BASE_LOCATION = os.environ["B10_FROM_SOURCE"]
+else:
+ PREFIX = "@prefix@"
+ DATAROOTDIR = "@datarootdir@"
+ BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
+ BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd.spec"
+STATS_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
+XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
+XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
+XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
+
+# These variables are paths part of URL.
+# eg. "http://${address}" + XXX_URL_PATH
+XML_URL_PATH = '/bind10/statistics/xml'
+XSD_URL_PATH = '/bind10/statistics/xsd'
+XSL_URL_PATH = '/bind10/statistics/xsl'
+# TODO: This should be considered later.
+XSD_NAMESPACE = 'http://bind10.isc.org' + XSD_URL_PATH
+DEFAULT_CONFIG = dict(listen_on=[('127.0.0.1', 8000)])
+
+# Assign this process name
+isc.util.process.rename()
+
+class HttpHandler(http.server.BaseHTTPRequestHandler):
+ """HTTP handler class for HttpServer class. The class inhrits the super
+ class http.server.BaseHTTPRequestHandler. It implemets do_GET()
+ and do_HEAD() and orverrides log_message()"""
+ def do_GET(self):
+ body = self.send_head()
+ if body is not None:
+ self.wfile.write(body.encode())
+
+ def do_HEAD(self):
+ self.send_head()
+
+ def send_head(self):
+ try:
+ if self.path == XML_URL_PATH:
+ body = self.server.xml_handler()
+ elif self.path == XSD_URL_PATH:
+ body = self.server.xsd_handler()
+ elif self.path == XSL_URL_PATH:
+ body = self.server.xsl_handler()
+ else:
+ if self.path == '/' and 'Host' in self.headers.keys():
+ # redirect to XML URL only when requested with '/'
+ self.send_response(302)
+ self.send_header(
+ "Location",
+ "http://" + self.headers.get('Host') + XML_URL_PATH)
+ self.end_headers()
+ return None
+ else:
+ # Couldn't find HOST
+ self.send_error(404)
+ return None
+ except StatsHttpdError as err:
+ self.send_error(500)
+ if self.server.verbose:
+ self.server.log_writer(
+ "[b10-stats-httpd] %s\n" % err)
+ return None
+ else:
+ self.send_response(200)
+ self.send_header("Content-type", "text/xml")
+ self.send_header("Content-Length", len(body))
+ self.end_headers()
+ return body
+
+ def log_message(self, format, *args):
+ """Change the default log format"""
+ if self.server.verbose:
+ self.server.log_writer(
+ "[b10-stats-httpd] %s - - [%s] %s\n" %
+ (self.address_string(),
+ self.log_date_time_string(),
+ format%args))
+
+class HttpServerError(Exception):
+ """Exception class for HttpServer class. It is intended to be
+ passed from the HttpServer object to the StatsHttpd object."""
+ pass
+
+class HttpServer(http.server.HTTPServer):
+ """HTTP Server class. The class inherits the super
+ http.server.HTTPServer. Some parameters are specified as
+ arguments, which are xml_handler, xsd_handler, xsl_handler, and
+ log_writer. These all are parameters which the StatsHttpd object
+ has. The handler parameters are references of functions which
+ return body of each document. The last parameter log_writer is
+ reference of writer function to just write to
+ sys.stderr.write. They are intended to be referred by HttpHandler
+ object."""
+ def __init__(self, server_address, handler,
+ xml_handler, xsd_handler, xsl_handler, log_writer, verbose=False):
+ self.server_address = server_address
+ self.xml_handler = xml_handler
+ self.xsd_handler = xsd_handler
+ self.xsl_handler = xsl_handler
+ self.log_writer = log_writer
+ self.verbose = verbose
+ http.server.HTTPServer.__init__(self, server_address, handler)
+
+class StatsHttpdError(Exception):
+ """Exception class for StatsHttpd class. It is intended to be
+ thrown from the the StatsHttpd object to the HttpHandler object or
+ main routine."""
+ pass
+
+class StatsHttpd:
+ """The main class of HTTP server of HTTP/XML interface for
+ statistics module. It handles HTTP requests, and command channel
+ and config channel CC session. It uses select.select function
+ while waiting for clients requests."""
+ def __init__(self, verbose=False):
+ self.verbose = verbose
+ self.running = False
+ self.poll_intval = 0.5
+ self.write_log = sys.stderr.write
+ self.mccs = None
+ self.httpd = []
+ self.open_mccs()
+ self.load_config()
+ self.load_templates()
+ self.open_httpd()
+
+ def open_mccs(self):
+ """Opens a ModuleCCSession object"""
+ # create ModuleCCSession
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Starting CC Session\n")
+ self.mccs = isc.config.ModuleCCSession(
+ SPECFILE_LOCATION, self.config_handler, self.command_handler)
+ self.cc_session = self.mccs._session
+ # read spec file of stats module and subscribe 'Stats'
+ self.stats_module_spec = isc.config.module_spec_from_file(STATS_SPECFILE_LOCATION)
+ self.stats_config_spec = self.stats_module_spec.get_config_spec()
+ self.stats_module_name = self.stats_module_spec.get_module_name()
+
+ def close_mccs(self):
+ """Closes a ModuleCCSession object"""
+ if self.mccs is None:
+ return
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Closing CC Session\n")
+ self.mccs.close()
+ self.mccs = None
+
+ def load_config(self, new_config={}):
+ """Loads configuration from spec file or new configuration
+ from the config manager"""
+ # load config
+ if len(new_config) > 0:
+ self.config.update(new_config)
+ else:
+ self.config = DEFAULT_CONFIG
+ self.config.update(
+ dict([
+ (itm['item_name'], self.mccs.get_value(itm['item_name'])[0])
+ for itm in self.mccs.get_module_spec().get_config_spec()
+ ])
+ )
+ # set addresses and ports for HTTP
+ self.http_addrs = [ (cf['address'], cf['port']) for cf in self.config['listen_on'] ]
+
+ def open_httpd(self):
+ """Opens sockets for HTTP. Iterating each HTTP address to be
+ configured in spec file"""
+ for addr in self.http_addrs:
+ self.httpd.append(self._open_httpd(addr))
+
+ def _open_httpd(self, server_address, address_family=None):
+ try:
+ # try IPv6 at first
+ if address_family is not None:
+ HttpServer.address_family = address_family
+ elif socket.has_ipv6:
+ HttpServer.address_family = socket.AF_INET6
+ httpd = HttpServer(
+ server_address, HttpHandler,
+ self.xml_handler, self.xsd_handler, self.xsl_handler,
+ self.write_log, self.verbose)
+ except (socket.gaierror, socket.error,
+ OverflowError, TypeError) as err:
+ # try IPv4 next
+ if HttpServer.address_family == socket.AF_INET6:
+ httpd = self._open_httpd(server_address, socket.AF_INET)
+ else:
+ raise HttpServerError(
+ "Invalid address %s, port %s: %s: %s" %
+ (server_address[0], server_address[1],
+ err.__class__.__name__, err))
+ else:
+ if self.verbose:
+ self.write_log(
+ "[b10-stats-httpd] Started on address %s, port %s\n" %
+ server_address)
+ return httpd
+
+ def close_httpd(self):
+ """Closes sockets for HTTP"""
+ if len(self.httpd) == 0:
+ return
+ for ht in self.httpd:
+ if self.verbose:
+ self.write_log(
+ "[b10-stats-httpd] Closing address %s, port %s\n" %
+ (ht.server_address[0], ht.server_address[1])
+ )
+ ht.server_close()
+ self.httpd = []
+
+ def start(self):
+ """Starts StatsHttpd objects to run. Waiting for client
+ requests by using select.select functions"""
+ self.mccs.start()
+ self.running = True
+ while self.running:
+ try:
+ (rfd, wfd, xfd) = select.select(
+ self.get_sockets(), [], [], self.poll_intval)
+ except select.error as err:
+ # select.error exception is caught only in the case of
+ # EINTR, or in other cases it is just thrown.
+ if err.args[0] == errno.EINTR:
+ (rfd, wfd, xfd) = ([], [], [])
+ else:
+ raise
+ # FIXME: This module can handle only one request at a
+ # time. If someone sends only part of the request, we block
+ # waiting for it until we time out.
+ # But it isn't so big issue for administration purposes.
+ for fd in rfd + xfd:
+ if fd == self.mccs.get_socket():
+ self.mccs.check_command(nonblock=False)
+ continue
+ for ht in self.httpd:
+ if fd == ht.socket:
+ ht.handle_request()
+ break
+ self.stop()
+
+ def stop(self):
+ """Stops the running StatsHttpd objects. Closes CC session and
+ HTTP handling sockets"""
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Shutting down\n")
+ self.close_httpd()
+ self.close_mccs()
+
+ def get_sockets(self):
+ """Returns sockets to select.select"""
+ sockets = []
+ if self.mccs is not None:
+ sockets.append(self.mccs.get_socket())
+ if len(self.httpd) > 0:
+ for ht in self.httpd:
+ sockets.append(ht.socket)
+ return sockets
+
+ def config_handler(self, new_config):
+ """Config handler for the ModuleCCSession object. It resets
+ addresses and ports to listen HTTP requests on."""
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Loading config : %s\n" % str(new_config))
+ for key in new_config.keys():
+ if key not in DEFAULT_CONFIG:
+ if self.verbose:
+ self.write_log(
+ "[b10-stats-httpd] Unknown known config: %s" % key)
+ return isc.config.ccsession.create_answer(
+ 1, "Unknown known config: %s" % key)
+ # backup old config
+ old_config = self.config.copy()
+ self.close_httpd()
+ self.load_config(new_config)
+ try:
+ self.open_httpd()
+ except HttpServerError as err:
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] %s\n" % err)
+ self.write_log("[b10-stats-httpd] Restoring old config\n")
+ # restore old config
+ self.config_handler(old_config)
+ return isc.config.ccsession.create_answer(
+ 1, "[b10-stats-httpd] %s" % err)
+ else:
+ return isc.config.ccsession.create_answer(0)
+
+ def command_handler(self, command, args):
+ """Command handler for the ModuleCCSesson object. It handles
+ "status" and "shutdown" commands."""
+ if command == "status":
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Received 'status' command\n")
+ return isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")")
+ elif command == "shutdown":
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Received 'shutdown' command\n")
+ self.running = False
+ return isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is shutting down.")
+ else:
+ if self.verbose:
+ self.write_log("[b10-stats-httpd] Received unknown command\n")
+ return isc.config.ccsession.create_answer(
+ 1, "Unknown command: " + str(command))
+
+ def get_stats_data(self):
+ """Requests statistics data to the Stats daemon and returns
+ the data which obtains from it"""
+ try:
+ seq = self.cc_session.group_sendmsg(
+ isc.config.ccsession.create_command('show'),
+ self.stats_module_name)
+ (answer, env) = self.cc_session.group_recvmsg(False, seq)
+ if answer:
+ (rcode, value) = isc.config.ccsession.parse_answer(answer)
+ except (isc.cc.session.SessionTimeout,
+ isc.cc.session.SessionError) as err:
+ raise StatsHttpdError("%s: %s" %
+ (err.__class__.__name__, err))
+ else:
+ if rcode == 0:
+ return value
+ else:
+ raise StatsHttpdError("Stats module: %s" % str(value))
+
+ def get_stats_spec(self):
+ """Just returns spec data"""
+ return self.stats_config_spec
+
+ def load_templates(self):
+ """Setup the bodies of XSD and XSL documents to be responds to
+ HTTP clients. Before that it also creates XML tag structures by
+ using xml.etree.ElementTree.Element class and substitutes
+ concrete strings with parameters embed in the string.Template
+ object."""
+ # for XSD
+ xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
+ for item in self.get_stats_spec():
+ element = xml.etree.ElementTree.Element(
+ "element",
+ dict( name=item["item_name"],
+ type=item["item_type"] if item["item_type"].lower() != 'real' else 'float',
+ minOccurs="1",
+ maxOccurs="1" ),
+ )
+ annotation = xml.etree.ElementTree.Element("annotation")
+ appinfo = xml.etree.ElementTree.Element("appinfo")
+ documentation = xml.etree.ElementTree.Element("documentation")
+ appinfo.text = item["item_title"]
+ documentation.text = item["item_description"]
+ annotation.append(appinfo)
+ annotation.append(documentation)
+ element.append(annotation)
+ xsd_root.append(element)
+ xsd_string = xml.etree.ElementTree.tostring(xsd_root)
+ self.xsd_body = self.open_template(XSD_TEMPLATE_LOCATION).substitute(
+ xsd_string=xsd_string,
+ xsd_namespace=XSD_NAMESPACE
+ )
+ assert self.xsd_body is not None
+
+ # for XSL
+ xsd_root = xml.etree.ElementTree.Element(
+ "xsl:template",
+ dict(match="*")) # started with xml:template tag
+ for item in self.get_stats_spec():
+ tr = xml.etree.ElementTree.Element("tr")
+ td1 = xml.etree.ElementTree.Element(
+ "td", { "class" : "title",
+ "title" : item["item_description"] })
+ td1.text = item["item_title"]
+ td2 = xml.etree.ElementTree.Element("td")
+ xsl_valueof = xml.etree.ElementTree.Element(
+ "xsl:value-of",
+ dict(select=item["item_name"]))
+ td2.append(xsl_valueof)
+ tr.append(td1)
+ tr.append(td2)
+ xsd_root.append(tr)
+ xsl_string = xml.etree.ElementTree.tostring(xsd_root)
+ self.xsl_body = self.open_template(XSL_TEMPLATE_LOCATION).substitute(
+ xsl_string=xsl_string,
+ xsd_namespace=XSD_NAMESPACE)
+ assert self.xsl_body is not None
+
+ def xml_handler(self):
+ """Handler which requests to Stats daemon to obtain statistics
+ data and returns the body of XML document"""
+ xml_list=[]
+ for (k, v) in self.get_stats_data().items():
+ (k, v) = (str(k), str(v))
+ elem = xml.etree.ElementTree.Element(k)
+ elem.text = v
+ xml_list.append(
+ xml.etree.ElementTree.tostring(elem))
+ xml_string = "".join(xml_list)
+ self.xml_body = self.open_template(XML_TEMPLATE_LOCATION).substitute(
+ xml_string=xml_string,
+ xsd_namespace=XSD_NAMESPACE,
+ xsd_url_path=XSD_URL_PATH,
+ xsl_url_path=XSL_URL_PATH)
+ assert self.xml_body is not None
+ return self.xml_body
+
+ def xsd_handler(self):
+ """Handler which just returns the body of XSD document"""
+ return self.xsd_body
+
+ def xsl_handler(self):
+ """Handler which just returns the body of XSL document"""
+ return self.xsl_body
+
+ def open_template(self, file_name):
+ """It opens a template file, and it loads all lines to a
+ string variable and returns string. Template object includes
+ the variable. Limitation of a file size isn't needed there."""
+ lines = "".join(
+ open(file_name, 'r').readlines())
+ assert lines is not None
+ return string.Template(lines)
+
+if __name__ == "__main__":
+ 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()
+ stats_httpd = StatsHttpd(verbose=options.verbose)
+ stats_httpd.start()
+ except OptionValueError:
+ sys.exit("[b10-stats-httpd] Error parsing options")
+ except isc.cc.session.SessionError as se:
+ sys.exit("[b10-stats-httpd] Error creating module, "
+ + "is the command channel daemon running?")
+ except HttpServerError as hse:
+ sys.exit("[b10-stats-httpd] %s" % hse)
+ except KeyboardInterrupt as kie:
+ sys.exit("[b10-stats-httpd] Interrupted, exiting")
diff --git a/src/bin/stats/stats_stub.py.in b/src/bin/stats/stats_stub.py.in
deleted file mode 100755
index 4a4a641..0000000
--- a/src/bin/stats/stats_stub.py.in
+++ /dev/null
@@ -1,154 +0,0 @@
-#!@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.
-
-__version__ = "$Revision$"
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-import os
-import time
-from optparse import OptionParser, OptionValueError
-from isc.config.ccsession import ModuleCCSession, create_command, parse_answer, parse_command, create_answer
-from isc.cc import Session, SessionError
-from stats import get_datetime
-
-# for setproctitle
-import isc.util.process
-isc.util.process.rename()
-
-# If B10_FROM_BUILD is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
-if "B10_FROM_BUILD" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec"
-else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@/@PACKAGE@/stats.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-
-class CCSessionStub:
- """
- This class is intended to behaves as a sender to Stats module. It
- creates MoudleCCSession object and send specified command.
- """
- def __init__(self, session=None, verbose=False):
- # create ModuleCCSession object
- self.verbose = verbose
- self.cc_session = ModuleCCSession(SPECFILE_LOCATION,
- self.__dummy, self.__dummy, session)
- self.module_name = self.cc_session._module_name
- self.session = self.cc_session._session
-
- def __dummy(self, *args):
- pass
-
- def send_command(self, command, args):
- """
- send command to stats module with args
- """
- cmd = create_command(command, args)
- if self.verbose:
- sys.stdout.write("[b10-stats_stub] send command : " + str(cmd) + "\n")
- seq = self.session.group_sendmsg(cmd, self.module_name)
- msg, env = self.session.group_recvmsg(False, seq) # non-blocking is False
- if self.verbose:
- sys.stdout.write("[b10-stats_stub] received env : " + str(env) + "\n")
- sys.stdout.write("[b10-stats_stub] received message : " + str(msg) + "\n")
- (ret, arg) = (None, None)
- if 'result' in msg:
- ret, arg = parse_answer(msg)
- elif 'command' in msg:
- ret, arg = parse_command(msg)
- self.session.group_reply(env, create_answer(0))
- return ret, arg, env
-
-class BossModuleStub:
- """
- This class is customized from CCSessionStub and is intended to behaves
- as a virtual Boss module to send to Stats Module.
- """
- def __init__(self, session=None, verbose=False):
- self.stub = CCSessionStub(session=session, verbose=verbose)
-
- def send_boottime(self):
- return self.stub.send_command("set", {"stats_data": {"bind10.boot_time": get_datetime()}})
-
-class AuthModuleStub:
- """
- This class is customized CCSessionStub and is intended to behaves
- as a virtual Auth module to send to Stats Module.
- """
- def __init__(self, session=None, verbose=False):
- self.stub = CCSessionStub(session=session, verbose=verbose)
- self.count = { "udp": 0, "tcp": 0 }
-
- def send_udp_query_count(self, cmd="set", cnt=0):
- """
- count up udp query count
- """
- prt = "udp"
- self.count[prt] = 1
- if cnt > 0:
- self.count[prt] = cnt
- return self.stub.send_command(cmd,
- {"stats_data":
- {"auth.queries."+prt: self.count[prt]}
- })
-
- def send_tcp_query_count(self, cmd="set", cnt=0):
- """
- set udp query count
- """
- prt = "tcp"
- self.count[prt] = self.count[prt] + 1
- if cnt > 0:
- self.count[prt] = cnt
- return self.stub.send_command(cmd,
- {"stats_data":
- {"auth.queries."+prt: self.count[prt]}
- })
-
-def main(session=None):
- try:
- parser=OptionParser()
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- (options, args) = parser.parse_args()
- stub = CCSessionStub(session=session, verbose=options.verbose)
- boss = BossModuleStub(session=stub.session, verbose=options.verbose)
- auth = AuthModuleStub(session=stub.session, verbose=options.verbose)
- stub.send_command("status", None)
- boss.send_boottime()
- t_cnt=0
- u_cnt=81120
- auth.send_udp_query_count(cnt=u_cnt) # This is an example.
- while True:
- u_cnt = u_cnt + 1
- t_cnt = t_cnt + 1
- auth.send_udp_query_count(cnt=u_cnt)
- auth.send_tcp_query_count(cnt=t_cnt)
- time.sleep(1)
-
- except OptionValueError:
- sys.stderr.write("[b10-stats_stub] Error parsing options\n")
- except SessionError as se:
- sys.stderr.write("[b10-stats_stub] Error creating Stats module, "
- + "is the command channel daemon running?\n")
- except KeyboardInterrupt as kie:
- sys.stderr.write("[b10-stats_stub] Interrupted, exiting\n")
-
-if __name__ == "__main__":
- main()
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 9e94050..1fbb0cc 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,8 +1,8 @@
-SUBDIRS = isc testdata
+SUBDIRS = isc http testdata
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = b10-stats_test.py b10-stats_stub_test.py
-EXTRA_DIST = $(PYTESTS) fake_time.py
-CLEANFILES = fake_time.pyc
+PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+EXTRA_DIST = $(PYTESTS) fake_time.py fake_socket.py fake_select.py
+CLEANFILES = fake_time.pyc fake_socket.pyc fake_select.pyc
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
@@ -14,6 +14,6 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests \
- B10_FROM_BUILD=$(abs_top_builddir) \
+ B10_FROM_SOURCE=$(abs_top_srcdir)/src/bin/stats \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
new file mode 100644
index 0000000..07999ea
--- /dev/null
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -0,0 +1,444 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import os
+import http.server
+import string
+import fake_select
+import imp
+import sys
+import fake_socket
+
+import isc.cc
+
+import stats_httpd
+stats_httpd.socket = fake_socket
+stats_httpd.select = fake_select
+
+DUMMY_DATA = {
+ "auth.queries.tcp": 10000,
+ "auth.queries.udp": 12000,
+ "bind10.boot_time": "2011-03-04T11:59:05Z",
+ "report_time": "2011-03-04T11:59:19Z",
+ "stats.boot_time": "2011-03-04T11:59:06Z",
+ "stats.last_update_time": "2011-03-04T11:59:07Z",
+ "stats.lname": "4d70d40a_c at host",
+ "stats.start_time": "2011-03-04T11:59:06Z",
+ "stats.timestamp": 1299239959.560846
+ }
+
+def push_answer(stats_httpd):
+ stats_httpd.cc_session.group_sendmsg(
+ { 'result':
+ [ 0, DUMMY_DATA ] }, "Stats")
+
+def pull_query(stats_httpd):
+ (msg, env) = stats_httpd.cc_session.group_recvmsg()
+ if 'result' in msg:
+ (ret, arg) = isc.config.ccsession.parse_answer(msg)
+ else:
+ (ret, arg) = isc.config.ccsession.parse_command(msg)
+ return (ret, arg, env)
+
+class TestHttpHandler(unittest.TestCase):
+ """Tests for HttpHandler class"""
+
+ def setUp(self):
+ self.verbose = True
+ self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+ self.assertTrue(type(self.stats_httpd.httpd) is list)
+ self.httpd = self.stats_httpd.httpd
+ for ht in self.httpd:
+ self.assertTrue(ht.verbose)
+ self.stats_httpd.cc_session.verbose = False
+
+ def test_do_GET(self):
+ for ht in self.httpd:
+ self._test_do_GET(ht._handler)
+
+ def _test_do_GET(self, handler):
+
+ # URL is '/bind10/statistics/xml'
+ handler.path = stats_httpd.XML_URL_PATH
+ push_answer(self.stats_httpd)
+ handler.do_GET()
+ (ret, arg, env) = pull_query(self.stats_httpd)
+ self.assertEqual(ret, "show")
+ self.assertIsNone(arg)
+ self.assertTrue('group' in env)
+ self.assertEqual(env['group'], 'Stats')
+ self.assertEqual(handler.response.code, 200)
+ self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+ self.assertTrue(handler.response.headers["Content-Length"] > 0)
+ self.assertTrue(handler.response.wrote_headers)
+ self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
+ self.assertTrue(handler.response.body.find(stats_httpd.XSD_URL_PATH)>0)
+ for (k, v) in DUMMY_DATA.items():
+ self.assertTrue(handler.response.body.find(str(k))>0)
+ self.assertTrue(handler.response.body.find(str(v))>0)
+
+ # URL is '/bind10/statitics/xsd'
+ handler.path = stats_httpd.XSD_URL_PATH
+ handler.do_GET()
+ self.assertEqual(handler.response.code, 200)
+ self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+ self.assertTrue(handler.response.headers["Content-Length"] > 0)
+ self.assertTrue(handler.response.wrote_headers)
+ self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
+ for (k, v) in DUMMY_DATA.items():
+ self.assertTrue(handler.response.body.find(str(k))>0)
+
+ # URL is '/bind10/statitics/xsl'
+ handler.path = stats_httpd.XSL_URL_PATH
+ handler.do_GET()
+ self.assertEqual(handler.response.code, 200)
+ self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+ self.assertTrue(handler.response.headers["Content-Length"] > 0)
+ self.assertTrue(handler.response.wrote_headers)
+ self.assertTrue(handler.response.body.find(stats_httpd.XSD_NAMESPACE)>0)
+ for (k, v) in DUMMY_DATA.items():
+ self.assertTrue(handler.response.body.find(str(k))>0)
+
+ # 302 redirect
+ handler.path = '/'
+ handler.headers = {'Host': 'my.host.domain'}
+ handler.do_GET()
+ self.assertEqual(handler.response.code, 302)
+ self.assertEqual(handler.response.headers["Location"],
+ "http://my.host.domain%s" % stats_httpd.XML_URL_PATH)
+
+ # 404 NotFound
+ handler.path = '/path/to/foo/bar'
+ handler.headers = {}
+ handler.do_GET()
+ self.assertEqual(handler.response.code, 404)
+
+ # failure case(connection with Stats is down)
+ handler.path = stats_httpd.XML_URL_PATH
+ push_answer(self.stats_httpd)
+ self.assertFalse(self.stats_httpd.cc_session._socket._closed)
+ self.stats_httpd.cc_session._socket._closed = True
+ handler.do_GET()
+ self.stats_httpd.cc_session._socket._closed = False
+ self.assertEqual(handler.response.code, 500)
+ self.stats_httpd.cc_session._clear_queues()
+
+ # failure case(Stats module returns err)
+ handler.path = stats_httpd.XML_URL_PATH
+ self.stats_httpd.cc_session.group_sendmsg(
+ { 'result': [ 1, "I have an error." ] }, "Stats")
+ self.assertFalse(self.stats_httpd.cc_session._socket._closed)
+ self.stats_httpd.cc_session._socket._closed = False
+ handler.do_GET()
+ self.assertEqual(handler.response.code, 500)
+ self.stats_httpd.cc_session._clear_queues()
+
+ def test_do_HEAD(self):
+ for ht in self.httpd:
+ self._test_do_HEAD(ht._handler)
+
+ def _test_do_HEAD(self, handler):
+ handler.path = '/path/to/foo/bar'
+ handler.do_HEAD()
+ self.assertEqual(handler.response.code, 404)
+
+ def test_log_message(self):
+ for ht in self.httpd:
+ self._test_log_message(ht._handler)
+
+ def _test_log_message(self, handler):
+ # switch write_log function
+ handler.server.log_writer = handler.response._write_log
+ log_message = 'ABCDEFG'
+ handler.log_message("%s", log_message)
+ self.assertEqual(handler.response.log,
+ "[b10-stats-httpd] %s - - [%s] %s\n" %
+ (handler.address_string(),
+ handler.log_date_time_string(),
+ log_message))
+
+class TestHttpServerError(unittest.TestCase):
+ """Tests for HttpServerError exception"""
+
+ def test_raises(self):
+ try:
+ raise stats_httpd.HttpServerError('Nothing')
+ except stats_httpd.HttpServerError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestHttpServer(unittest.TestCase):
+ """Tests for HttpServer class"""
+
+ def test_httpserver(self):
+ self.verbose = True
+ self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+ self.stats_httpd.cc_session.verbose = False
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(ht.server_address in self.stats_httpd.http_addrs)
+ self.assertEqual(ht.verbose, self.verbose)
+ self.assertEqual(ht.xml_handler, self.stats_httpd.xml_handler)
+ self.assertEqual(ht.xsd_handler, self.stats_httpd.xsd_handler)
+ self.assertEqual(ht.xsl_handler, self.stats_httpd.xsl_handler)
+ self.assertEqual(ht.log_writer, self.stats_httpd.write_log)
+ self.assertTrue(isinstance(ht._handler, stats_httpd.HttpHandler))
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+
+class TestStatsHttpdError(unittest.TestCase):
+ """Tests for StatsHttpdError exception"""
+
+ def test_raises(self):
+ try:
+ raise stats_httpd.StatsHttpdError('Nothing')
+ except stats_httpd.StatsHttpdError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestStatsHttpd(unittest.TestCase):
+ """Tests for StatsHttpd class"""
+
+ def setUp(self):
+ self.verbose = True
+ fake_socket._CLOSED = False
+ fake_socket.has_ipv6 = True
+ self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+ self.stats_httpd.cc_session.verbose = False
+
+ def tearDown(self):
+ self.stats_httpd.stop()
+
+ def test_init(self):
+ self.assertTrue(self.stats_httpd.verbose)
+ self.assertFalse(self.stats_httpd.mccs.get_socket()._closed)
+ self.assertEqual(self.stats_httpd.mccs.get_socket().fileno(),
+ id(self.stats_httpd.mccs.get_socket()))
+ for ht in self.stats_httpd.httpd:
+ self.assertFalse(ht.socket._closed)
+ self.assertEqual(ht.socket.fileno(), id(ht.socket))
+ fake_socket._CLOSED = True
+ self.assertRaises(isc.cc.session.SessionError,
+ stats_httpd.StatsHttpd)
+ fake_socket._CLOSED = False
+
+ def test_mccs(self):
+ self.stats_httpd.open_mccs()
+ self.assertTrue(
+ isinstance(self.stats_httpd.mccs.get_socket(), fake_socket.socket))
+ self.assertTrue(
+ isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
+ self.assertTrue(
+ isinstance(self.stats_httpd.stats_module_spec, isc.config.ModuleSpec))
+ for cfg in self.stats_httpd.stats_config_spec:
+ self.assertTrue('item_name' in cfg)
+ self.assertTrue(cfg['item_name'] in DUMMY_DATA)
+ self.assertTrue(len(self.stats_httpd.stats_config_spec), len(DUMMY_DATA))
+
+ def test_load_config(self):
+ self.stats_httpd.load_config()
+ self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
+
+ def test_httpd(self):
+ # dual stack (addresses is ipv4 and ipv6)
+ fake_socket.has_ipv6 = True
+ self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
+ self.stats_httpd.http_addrs = [ ('::1', 8000), ('127.0.0.1', 8000) ]
+ self.assertTrue(
+ stats_httpd.HttpServer.address_family in set([fake_socket.AF_INET, fake_socket.AF_INET6]))
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ # dual stack (address is ipv6)
+ fake_socket.has_ipv6 = True
+ self.stats_httpd.http_addrs = [ ('::1', 8000) ]
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ # dual stack (address is ipv4)
+ fake_socket.has_ipv6 = True
+ self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ # only-ipv4 single stack
+ fake_socket.has_ipv6 = False
+ self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ # only-ipv4 single stack (force set ipv6 )
+ fake_socket.has_ipv6 = False
+ self.stats_httpd.http_addrs = [ ('::1', 8000) ]
+ self.assertRaises(stats_httpd.HttpServerError,
+ self.stats_httpd.open_httpd)
+
+ # hostname
+ self.stats_httpd.http_addrs = [ ('localhost', 8000) ]
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ self.stats_httpd.http_addrs = [ ('my.host.domain', 8000) ]
+ self.stats_httpd.open_httpd()
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht.socket, fake_socket.socket))
+ self.stats_httpd.close_httpd()
+
+ # over flow of port number
+ self.stats_httpd.http_addrs = [ ('', 80000) ]
+ self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
+ # negative
+ self.stats_httpd.http_addrs = [ ('', -8000) ]
+ self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
+ # alphabet
+ self.stats_httpd.http_addrs = [ ('', 'ABCDE') ]
+ self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
+
+ def test_start(self):
+ self.stats_httpd.cc_session.group_sendmsg(
+ { 'command': [ "shutdown" ] }, "StatsHttpd")
+ self.stats_httpd.start()
+ self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+ self.stats_httpd.cc_session.verbose = False
+ self.assertRaises(
+ fake_select.error, self.stats_httpd.start)
+
+ def test_stop(self):
+ # success case
+ fake_socket._CLOSED = False
+ self.stats_httpd.stop()
+ self.assertFalse(self.stats_httpd.running)
+ self.assertIsNone(self.stats_httpd.mccs)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(ht.socket._closed)
+ self.assertTrue(self.stats_httpd.cc_session._socket._closed)
+ # failure case
+ self.stats_httpd.cc_session._socket._closed = False
+ self.stats_httpd.open_mccs()
+ self.stats_httpd.cc_session._socket._closed = True
+ self.stats_httpd.stop() # No excetion raises
+ self.stats_httpd.cc_session._socket._closed = False
+
+ def test_open_template(self):
+ # successful conditions
+ tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(
+ xml_string="<dummy></dummy>",
+ xsd_namespace="http://host/path/to/",
+ xsd_url_path="/path/to/",
+ xsl_url_path="/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertTrue(lines.find(opts[n])>0)
+ tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(
+ xsd_string="<dummy></dummy>",
+ xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertTrue(lines.find(opts[n])>0)
+ tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(
+ xsl_string="<dummy></dummy>",
+ xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertTrue(lines.find(opts[n])>0)
+ # unsuccessful condition
+ self.assertRaises(
+ IOError,
+ self.stats_httpd.open_template, '/path/to/foo/bar')
+
+ def test_commands(self):
+ self.assertEqual(self.stats_httpd.command_handler("status", None),
+ isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
+ self.stats_httpd.running = True
+ self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
+ isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is shutting down."))
+ self.assertFalse(self.stats_httpd.running)
+ self.assertEqual(
+ self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
+ isc.config.ccsession.create_answer(
+ 1, "Unknown command: __UNKNOWN_COMMAND__"))
+
+ def test_config(self):
+ self.assertEqual(
+ self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
+ isc.config.ccsession.create_answer(
+ 1, "Unknown known config: _UNKNOWN_KEY_"))
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="::2",port=8000)])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == "::2")
+ self.assertTrue(addr["port"] == 8000)
+
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="::1",port=80)])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == "::1")
+ self.assertTrue(addr["port"] == 80)
+
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="1.2.3.4",port=54321)])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == "1.2.3.4")
+ self.assertTrue(addr["port"] == 54321)
+ (ret, arg) = isc.config.ccsession.parse_answer(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
+ )
+ self.assertEqual(ret, 1)
+
+ def test_for_without_B10_FROM_SOURCE(self):
+ # just lets it go through the code without B10_FROM_SOURCE env
+ # variable
+ if "B10_FROM_SOURCE" in os.environ:
+ tmppath = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
+ imp.reload(stats_httpd)
+ os.environ["B10_FROM_SOURCE"] = tmppath
+ imp.reload(stats_httpd)
+ stats_httpd.socket = fake_socket
+ stats_httpd.select = fake_select
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_stub_test.py b/src/bin/stats/tests/b10-stats_stub_test.py
deleted file mode 100644
index b8983c5..0000000
--- a/src/bin/stats/tests/b10-stats_stub_test.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# 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.
-
-__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()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 3566bc4..170e464 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010,2011 Internet Systems Consortium.
+# Copyright (C) 2010, 2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -535,9 +535,9 @@ class TestStats2(unittest.TestCase):
Test for specfile
"""
- if "B10_FROM_BUILD" in os.environ:
+ if "B10_FROM_SOURCE" in os.environ:
self.assertEqual(stats.SPECFILE_LOCATION,
- os.environ["B10_FROM_BUILD"] + "/src/bin/stats/stats.spec")
+ os.environ["B10_FROM_SOURCE"] + os.sep + "stats.spec")
imp.reload(stats)
# change path of SPECFILE_LOCATION
stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
@@ -631,13 +631,13 @@ class TestStats2(unittest.TestCase):
def test_osenv(self):
"""
- test for not having environ "B10_FROM_BUILD"
+ test for not having environ "B10_FROM_SOURCE"
"""
- if "B10_FROM_BUILD" in os.environ:
- path = os.environ["B10_FROM_BUILD"]
- os.environ.pop("B10_FROM_BUILD")
+ if "B10_FROM_SOURCE" in os.environ:
+ path = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
imp.reload(stats)
- os.environ["B10_FROM_BUILD"] = path
+ os.environ["B10_FROM_SOURCE"] = path
imp.reload(stats)
def result_ok(*args):
diff --git a/src/bin/stats/tests/fake_select.py b/src/bin/stats/tests/fake_select.py
new file mode 100644
index 0000000..ca0ca82
--- /dev/null
+++ b/src/bin/stats/tests/fake_select.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+A mock-up module of select
+
+*** NOTE ***
+It is only for testing stats_httpd module and not reusable for
+external module.
+"""
+
+import fake_socket
+import errno
+
+class error(Exception):
+ pass
+
+def select(rlst, wlst, xlst, timeout):
+ if type(timeout) != int and type(timeout) != float:
+ raise TypeError("Error: %s must be integer or float"
+ % timeout.__class__.__name__)
+ for s in rlst + wlst + xlst:
+ if type(s) != fake_socket.socket:
+ raise TypeError("Error: %s must be a dummy socket"
+ % s.__class__.__name__)
+ s._called = s._called + 1
+ if s._called > 3:
+ raise error("Something is happened!")
+ elif s._called > 2:
+ raise error(errno.EINTR)
+ return (rlst, wlst, xlst)
diff --git a/src/bin/stats/tests/fake_socket.py b/src/bin/stats/tests/fake_socket.py
new file mode 100644
index 0000000..4e3a458
--- /dev/null
+++ b/src/bin/stats/tests/fake_socket.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+A mock-up module of socket
+
+*** NOTE ***
+It is only for testing stats_httpd module and not reusable for
+external module.
+"""
+
+import re
+
+AF_INET = 'AF_INET'
+AF_INET6 = 'AF_INET6'
+_ADDRFAMILY = AF_INET
+has_ipv6 = True
+_CLOSED = False
+
+class gaierror(Exception):
+ pass
+
+class error(Exception):
+ pass
+
+class socket:
+
+ def __init__(self, family=None):
+ if family is None:
+ self.address_family = _ADDRFAMILY
+ else:
+ self.address_family = family
+ self._closed = _CLOSED
+ if self._closed:
+ raise error('socket is already closed!')
+ self._called = 0
+
+ def close(self):
+ self._closed = True
+
+ def fileno(self):
+ return id(self)
+
+ def bind(self, server_class):
+ (self.server_address, self.server_port) = server_class
+ if self.address_family not in set([AF_INET, AF_INET6]):
+ raise error("Address family not supported by protocol: %s" % self.address_family)
+ if self.address_family == AF_INET6 and not has_ipv6:
+ raise error("Address family not supported in this machine: %s has_ipv6: %s"
+ % (self.address_family, str(has_ipv6)))
+ if self.address_family == AF_INET and re.search(':', self.server_address) is not None:
+ raise gaierror("Address family for hostname not supported : %s %s" % (self.server_address, self.address_family))
+ if self.address_family == AF_INET6 and re.search(':', self.server_address) is None:
+ raise error("Cannot assign requested address : %s" % str(self.server_address))
+ if type(self.server_port) is not int:
+ raise TypeError("an integer is required: %s" % str(self.server_port))
+ if self.server_port < 0 or self.server_port > 65535:
+ raise OverflowError("port number must be 0-65535.: %s" % str(self.server_port))
diff --git a/src/bin/stats/tests/http/Makefile.am b/src/bin/stats/tests/http/Makefile.am
new file mode 100644
index 0000000..879e8a8
--- /dev/null
+++ b/src/bin/stats/tests/http/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = __init__.py server.py
+CLEANFILES = __init__.pyc server.pyc
diff --git a/src/bin/stats/tests/http/__init__.py b/src/bin/stats/tests/http/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/bin/stats/tests/http/server.py b/src/bin/stats/tests/http/server.py
new file mode 100644
index 0000000..70ed6fa
--- /dev/null
+++ b/src/bin/stats/tests/http/server.py
@@ -0,0 +1,96 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+A mock-up module of http.server
+
+*** NOTE ***
+It is only for testing stats_httpd module and not reusable for
+external module.
+"""
+
+import fake_socket
+
+class DummyHttpResponse:
+ def __init__(self, path):
+ self.path = path
+ self.headers={}
+ self.log = ""
+
+ def _write_log(self, msg):
+ self.log = self.log + msg
+
+class HTTPServer:
+ """
+ A mock-up class of http.server.HTTPServer
+ """
+ address_family = fake_socket.AF_INET
+ def __init__(self, server_class, handler_class):
+ self.socket = fake_socket.socket(self.address_family)
+ self.server_class = server_class
+ self.socket.bind(self.server_class)
+ self._handler = handler_class(None, None, self)
+
+ def handle_request(self):
+ pass
+
+ def server_close(self):
+ self.socket.close()
+
+class BaseHTTPRequestHandler:
+ """
+ A mock-up class of http.server.BaseHTTPRequestHandler
+ """
+
+ def __init__(self, request, client_address, server):
+ self.path = "/path/to"
+ self.headers = {}
+ self.server = server
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.write = self._write
+ self.wfile = self.response
+
+ def send_response(self, code=0):
+ if self.path != self.response.path:
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.code = code
+
+ def send_header(self, key, value):
+ if self.path != self.response.path:
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.headers[key] = value
+
+ def end_headers(self):
+ if self.path != self.response.path:
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.wrote_headers = True
+
+ def send_error(self, code, message=None):
+ if self.path != self.response.path:
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.code = code
+ self.response.body = message
+
+ def address_string(self):
+ return 'dummyhost'
+
+ def log_date_time_string(self):
+ return '[DD/MM/YYYY HH:MI:SS]'
+
+ def _write(self, obj):
+ if self.path != self.response.path:
+ self.response = DummyHttpResponse(path=self.path)
+ self.response.body = obj.decode()
+
diff --git a/src/bin/stats/tests/isc/cc/session.py b/src/bin/stats/tests/isc/cc/session.py
index 4d12adc..e16d6a9 100644
--- a/src/bin/stats/tests/isc/cc/session.py
+++ b/src/bin/stats/tests/isc/cc/session.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010,2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -13,11 +13,16 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# This module is a mock-up class of isc.cc.session
+"""
+A mock-up module of isc.cc.session
-__version__ = "$Revision$"
+*** NOTE ***
+It is only for testing stats_httpd module and not reusable for
+external module.
+"""
import sys
+import fake_socket
# set a dummy lname
_TEST_LNAME = '123abc at xxxx'
@@ -33,12 +38,18 @@ class Queue():
class SessionError(Exception):
pass
+class SessionTimeout(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
+ try:
+ self._socket = fake_socket.socket()
+ except fake_socket.error as se:
+ raise SessionError(se)
self.verbose = verbose
@property
@@ -46,13 +57,17 @@ class Session:
return self._lname
def close(self):
- self._socket = False
+ self._socket.close()
+
+ def _clear_queues(self):
+ while len(self.message_queue) > 0:
+ self.dequeue()
def _next_sequence(self, que=None):
return len(self.message_queue)
def enqueue(self, msg=None, env={}):
- if not self._socket:
+ if self._socket._closed:
raise SessionError("Session has been closed.")
seq = self._next_sequence()
env.update({"seq": 0}) # fixed here
@@ -62,12 +77,12 @@ class Session:
sys.stdout.write("[Session] enqueue: " + str(que.dump()) + "\n")
return seq
- def dequeue(self, seq=0):
- if not self._socket:
+ def dequeue(self):
+ if self._socket._closed:
raise SessionError("Session has been closed.")
que = None
try:
- que = self.message_queue.pop(seq)
+ que = self.message_queue.pop(0) # always pop at index 0
self.old_message_queue.append(que)
except IndexError:
que = Queue()
@@ -76,7 +91,7 @@ class Session:
return que
def get_queue(self, seq=None):
- if not self._socket:
+ if self._socket._closed:
raise SessionError("Session has been closed.")
if seq is None:
seq = len(self.message_queue) - 1
@@ -99,7 +114,7 @@ class Session:
"instance": instance })
def group_recvmsg(self, nonblock=True, seq=0):
- que = self.dequeue(seq)
+ que = self.dequeue()
return que.msg, que.env
def group_reply(self, routing, msg):
@@ -112,7 +127,7 @@ class Session:
"reply": routing["seq"] })
def get_message(self, group, to='*'):
- if not self._socket:
+ if self._socket._closed:
raise SessionError("Session has been closed.")
que = Queue()
for q in self.message_queue:
@@ -124,3 +139,10 @@ class Session:
sys.stdout.write("[Session] get_message: " + str(que.dump()) + "\n")
return q.msg
+ def group_subscribe(self, group, instance = "*"):
+ if self._socket._closed:
+ raise SessionError("Session has been closed.")
+
+ def group_unsubscribe(self, group, instance = "*"):
+ if self._socket._closed:
+ raise SessionError("Session has been closed.")
diff --git a/src/bin/stats/tests/isc/config/ccsession.py b/src/bin/stats/tests/isc/config/ccsession.py
index fc285a7..a4e9c37 100644
--- a/src/bin/stats/tests/isc/config/ccsession.py
+++ b/src/bin/stats/tests/isc/config/ccsession.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010,2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -13,16 +13,22 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# This module is a mock-up class of isc.cc.session
+"""
+A mock-up module of isc.cc.session
-__version__ = "$Revision$"
+*** NOTE ***
+It is only for testing stats_httpd module and not reusable for
+external module.
+"""
import json
+import os
from isc.cc.session import Session
COMMAND_CONFIG_UPDATE = "config_update"
def parse_answer(msg):
+ assert 'result' in msg
try:
return msg['result'][0], msg['result'][1]
except IndexError:
@@ -35,6 +41,7 @@ def create_answer(rcode, arg = None):
return { 'result': [ rcode, arg ] }
def parse_command(msg):
+ assert 'command' in msg
try:
return msg['command'][0], msg['command'][1]
except IndexError:
@@ -47,9 +54,21 @@ def create_command(command_name, params = None):
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)
+ try:
+ file = open(spec_file)
+ json_str = file.read()
+ module_spec = json.loads(json_str)
+ file.close()
+ return ModuleSpec(module_spec['module_spec'], check)
+ except IOError as ioe:
+ raise ModuleSpecError("JSON read error: " + str(ioe))
+ except ValueError as ve:
+ raise ModuleSpecError("JSON parse error: " + str(ve))
+ except KeyError as err:
+ raise ModuleSpecError("Data definition has no module_spec element")
+
+class ModuleSpecError(Exception):
+ pass
class ModuleSpec:
def __init__(self, module_spec, check = True):
@@ -67,10 +86,34 @@ class ModuleSpec:
class ModuleCCSessionError(Exception):
pass
+class DataNotFoundError(Exception):
+ pass
+
class ConfigData:
def __init__(self, specification):
self.specification = specification
+ def get_value(self, identifier):
+ """Returns a tuple where the first item is the value at the
+ given identifier, and the second item is absolutely False
+ even if the value is an unset default or not. Raises an
+ DataNotFoundError if the identifier is not found in the
+ specification file.
+ *** NOTE ***
+ There are some differences from the original method. This
+ method never handles local settings like the original
+ method. But these different behaviors aren't so big issues
+ for a mock-up method of stats_httpd because stats_httpd
+ calls this method at only first."""
+ for config_map in self.get_module_spec().get_config_spec():
+ if config_map['item_name'] == identifier:
+ if 'item_default' in config_map:
+ return config_map['item_default'], False
+ raise DataNotFoundError("item_name %s is not found in the specfile" % identifier)
+
+ def get_module_spec(self):
+ return self.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)
@@ -111,3 +154,7 @@ class ModuleCCSession(ConfigData):
def get_module_spec(self):
return self.specification
+
+ def get_socket(self):
+ return self._session._socket
+
diff --git a/src/bin/stats/tests/isc/util/process.py b/src/bin/stats/tests/isc/util/process.py
index 7274a48..0f764c1 100644
--- a/src/bin/stats/tests/isc/util/process.py
+++ b/src/bin/stats/tests/isc/util/process.py
@@ -13,6 +13,9 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# A dummy function of isc.util.process.rename()
+"""
+A dummy function of isc.util.process.rename()
+"""
+
def rename(name=None):
pass
diff --git a/src/bin/stats/tests/isc/utils/Makefile.am b/src/bin/stats/tests/isc/utils/Makefile.am
deleted file mode 100644
index b09fdee..0000000
--- a/src/bin/stats/tests/isc/utils/Makefile.am
+++ /dev/null
@@ -1,2 +0,0 @@
-EXTRA_DIST = __init__.py process.py
-CLEANFILES = __init__.pyc process.pyc
diff --git a/src/bin/stats/tests/isc/utils/__init__.py b/src/bin/stats/tests/isc/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/bin/stats/tests/isc/utils/process.py b/src/bin/stats/tests/isc/utils/process.py
deleted file mode 100644
index 4c0cf8c..0000000
--- a/src/bin/stats/tests/isc/utils/process.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-
-# A dummy function of isc.utils.process.rename()
-def rename(name=None):
- pass
diff --git a/src/bin/stats/tests/stats_test.in b/src/bin/stats/tests/stats_test.in
index a3279a7..39b5a94 100644
--- a/src/bin/stats/tests/stats_test.in
+++ b/src/bin/stats/tests/stats_test.in
@@ -1,6 +1,6 @@
#! /bin/sh
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010, 2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -21,11 +21,11 @@ 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
+B10_FROM_SOURCE=@abs_top_srcdir@/src/bin/stats
+export B10_FROM_SOURCE
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 $*
+${PYTHON_EXEC} -O b10-stats-httpd_test.py $*
More information about the bind10-changes
mailing list