2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

- merge trac #191 (Implement a initial version of stats)

- remove obsoleted stats files


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@3218 e5f2f494-b856-4b98-b285-d166d9295462
This commit is contained in:
Naoki Kambe
2010-10-15 06:34:33 +00:00
parent 2132a059e8
commit fc2610f5ad
32 changed files with 2197 additions and 447 deletions

View File

@@ -1,3 +1,10 @@
109. [func] naokikambe
Added the initial version of the stats module for the statistics
feature of BIND 10, which supports the restricted features and
items and reports via bindctl command (Trac #191, rXXXX)
Added the document of the stats module, which is about how stats
module collects the data (Trac #170, [wiki:StatsModule])
108. [func] jerry
src/bin/zonemgr: Provide customizable configurations for
lowerbound_refresh, lowerbound_retry, max_transfer_timeout and

View File

@@ -471,6 +471,8 @@ AC_CONFIG_FILES([Makefile
src/bin/xfrout/tests/Makefile
src/bin/zonemgr/Makefile
src/bin/zonemgr/tests/Makefile
src/bin/stats/Makefile
src/bin/stats/tests/Makefile
src/bin/usermgr/Makefile
src/bin/tests/Makefile
src/lib/Makefile
@@ -523,6 +525,12 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/bin/zonemgr/zonemgr.spec.pre
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/run_b10-stats.sh
src/bin/stats/run_b10-stats_stub.sh
src/bin/stats/tests/stats_test
src/bin/bind10/bind10.py
src/bin/bind10/tests/bind10_test
src/bin/bind10/run_bind10.sh
@@ -556,6 +564,9 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
chmod +x src/bin/xfrin/run_b10-xfrin.sh
chmod +x src/bin/xfrout/run_b10-xfrout.sh
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/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/tests/cmdctl_test
chmod +x src/bin/xfrin/tests/xfrin_test

View File

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

View File

@@ -73,6 +73,9 @@ isc.utils.process.rename(sys.argv[0])
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "bind10 20100916 (BIND 10 @PACKAGE_VERSION@)"
# This is for bind10.boottime of stats module
_BASETIME = time.gmtime()
class RestartSchedule:
"""
Keeps state when restarting something (in this case, a process).
@@ -424,6 +427,27 @@ class BoB:
sys.stdout.write("[bind10] Started b10-zonemgr(PID %d)\n" %
zonemgr.pid)
# start b10-stats
stats_args = ['b10-stats']
if self.verbose:
sys.stdout.write("[bind10] Starting b10-stats\n")
stats_args += ['-v']
try:
statsd = ProcessInfo("b10-stats", stats_args,
c_channel_env)
except Exception as e:
c_channel.process.kill()
bind_cfgd.process.kill()
xfrout.process.kill()
auth.process.kill()
xfrind.process.kill()
zonemgr.process.kill()
return "Unable to start b10-stats; " + str(e)
self.processes[statsd.pid] = statsd
if self.verbose:
sys.stdout.write("[bind10] Started b10-stats (PID %d)\n" % statsd.pid)
# start the b10-cmdctl
# XXX: we hardcode port 8080
cmdctl_args = ['b10-cmdctl']
@@ -440,6 +464,7 @@ class BoB:
auth.process.kill()
xfrind.process.kill()
zonemgr.process.kill()
statsd.process.kill()
return "Unable to start b10-cmdctl; " + str(e)
self.processes[cmd_ctrld.pid] = cmd_ctrld
if self.verbose:
@@ -459,6 +484,7 @@ class BoB:
self.cc_session.group_sendmsg(cmd, "Boss", "Xfrout")
self.cc_session.group_sendmsg(cmd, "Boss", "Xfrin")
self.cc_session.group_sendmsg(cmd, "Boss", "Zonemgr")
self.cc_session.group_sendmsg(cmd, "Boss", "Stats")
def stop_process(self, process):
"""Stop the given process, friendly-like."""
@@ -723,6 +749,17 @@ def main():
sys.exit(1)
sys.stdout.write("[bind10] BIND 10 started\n")
# send "bind10.boot_time" to b10-stats
time.sleep(1) # wait a second
if options.verbose:
sys.stdout.write("[bind10] send \"bind10.boot_time\" to b10-stats\n")
cmd = isc.config.ccsession.create_command('set',
{ "stats_data": {
'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
}
})
boss_of_bind.cc_session.group_sendmsg(cmd, 'Stats')
# In our main loop, we check for dead processes or messages
# on the c-channel.
wakeup_fd = wakeup_pipe[0]

View File

@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs

37
src/bin/stats/Makefile.am Normal file
View File

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

68
src/bin/stats/b10-stats.8 Normal file
View File

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

124
src/bin/stats/b10-stats.xml Normal file
View File

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

View File

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

View File

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

416
src/bin/stats/stats.py.in Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -316,7 +316,9 @@ def _validate_spec(spec, full, data, errors):
item_name = spec['item_name']
item_optional = spec['item_optional']
if item_name in data:
if not data and item_optional:
return True
elif item_name in data:
return _validate_item(spec, full, data[item_name], errors)
elif full and not item_optional:
if errors != None: