Parcourir la source

[2158] Merge branch 'trac2136' of ssh://git.bind10.isc.org/var/bind10/git/bind10 into trac2158

Conflicts:
	tests/system/bindctl/nsx1/b10-config.db.template.in
Naoki Kambe il y a 12 ans
Parent
commit
8647cc1291

+ 1 - 0
configure.ac

@@ -1101,6 +1101,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/zonemgr/tests/Makefile
                  src/bin/stats/Makefile
                  src/bin/stats/tests/Makefile
+                 src/bin/stats/tests/testdata/Makefile
                  src/bin/usermgr/Makefile
                  src/bin/tests/Makefile
                  src/lib/Makefile

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

@@ -1621,9 +1621,8 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</pre><p>
     </p><p>
 
        This stats daemon provides commands to identify if it is
-       running, show specified or all statistics data, show specified
-       or all statistics data schema, and set specified statistics
-       data.
+       running, show specified or all statistics data, and show specified
+       or all statistics data schema.
 
        For example, using <span class="command"><strong>bindctl</strong></span>:
 

+ 2 - 2
doc/guide/bind10-guide.txt

@@ -1790,8 +1790,8 @@ Chapter 16. Statistics
    statistics data from various modules and aggregates it.
 
    This stats daemon provides commands to identify if it is running, show
-   specified or all statistics data, show specified or all statistics data
-   schema, and set specified statistics data. For example, using bindctl:
+   specified or all statistics data, and show specified or all statistics data
+   schema. For example, using bindctl:
 
  > Stats show
  {

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

@@ -3027,9 +3027,8 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";</screen>
     <para>
 
        This stats daemon provides commands to identify if it is
-       running, show specified or all statistics data, show specified
-       or all statistics data schema, and set specified statistics
-       data.
+       running, show specified or all statistics data, and show specified
+       or all statistics data schema.
 
        For example, using <command>bindctl</command>:
 

+ 1 - 1
src/bin/bind10/bind10.8

@@ -229,7 +229,7 @@ daemon immediately\&.
 
 \fBshow_processes\fR
 lists the current processes managed by
-\fBbind10\fR\&. The output is an array in JSON format containing the process ID and the name for each\&.
+\fBbind10\fR\&. The output is an array in JSON format containing the process ID, the name for each and the address name used on each message bus\&.
 
 
 .PP

+ 1 - 1
src/bin/bind10/bind10.xml

@@ -414,7 +414,7 @@ xfrin
       <command>show_processes</command> lists the current processes
       managed by <command>bind10</command>.
       The output is an array in JSON format containing the process
-      ID and the name for each.
+      ID, the name for each and the address name used on each message bus.
 <!-- TODO: what is name? -->
 <!-- TODO: change to JSON object format? -->
 <!-- TODO: ticket #1406 -->

+ 2 - 1
src/bin/bind10/bind10_src.py.in

@@ -281,7 +281,8 @@ class BoB:
         pids.sort()
         process_list = [ ]
         for pid in pids:
-            process_list.append([pid, self.components[pid].name()])
+            process_list.append([pid, self.components[pid].name(),
+                                 self.components[pid].address()])
         return process_list
 
     def _get_stats_data(self):

+ 5 - 4
src/bin/bind10/tests/bind10_test.py.in

@@ -938,9 +938,10 @@ class TestStartStopProcessesBob(unittest.TestCase):
         #self.check_started_dhcp(bob, True, True)
 
 class MockComponent:
-    def __init__(self, name, pid):
+    def __init__(self, name, pid, address=None):
         self.name = lambda: name
         self.pid = lambda: pid
+        self.address = lambda: address
 
 
 class TestBossCmd(unittest.TestCase):
@@ -966,10 +967,10 @@ class TestBossCmd(unittest.TestCase):
         """
         bob = MockBob()
         bob.register_process(1, MockComponent('first', 1))
-        bob.register_process(2, MockComponent('second', 2))
+        bob.register_process(2, MockComponent('second', 2, 'Second'))
         answer = bob.command_handler("show_processes", None)
-        processes = [[1, 'first'],
-                     [2, 'second']]
+        processes = [[1, 'first', None],
+                     [2, 'second', 'Second']]
         self.assertEqual(answer, {'result': [0, processes]})
 
 class TestParseArgs(unittest.TestCase):

+ 27 - 10
src/bin/stats/b10-stats.8

@@ -1,7 +1,7 @@
 '\" 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/>
+.\" Generator: DocBook XSL Stylesheets v1.77.1 <http://docbook.sf.net/>
 .\"      Date: June 20, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
@@ -9,6 +9,15 @@
 .\"
 .TH "B10\-STATS" "8" "June 20, 2012" "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
@@ -37,7 +46,12 @@ and communicates by using the Command Channel by
 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\&. The stats module collects data and aggregates it\&.
+and so on\&.
+\fBb10\-stats\fR
+periodically requests statistics data to each module and receives\&. The interval time can be configured via
+\fBbindctl\fR\&.
+\fBb10\-stats\fR
+cannot accept any command from other modules for updating statistics data\&. The stats module collects data and aggregates it\&.
 \fBb10\-stats\fR
 invokes an internal command for
 \fBbind10\fR
@@ -53,17 +67,20 @@ This enables maximum debug logging\&.
 .RE
 .SH "CONFIGURATION AND COMMANDS"
 .PP
-The
+The configurable setting in
+stats\&.spec
+is:
+.PP
+\fIpoll\-interval\fR
+.RS 4
+is a timer interval in seconds for
 \fBb10\-stats\fR
-command does not have any configurable settings\&.
+to polling each module for its statistics data\&. The default is 60 second\&. Polling can be disabled by setting to 0\&. The type of the value should be an unsigned integer\&. Setting to a negative integer is ignored\&.
+.RE
 .PP
 The configuration commands are:
 .PP
 
-\fBset\fR
-will set new statistics data specified in arguments\&. Statistics data to be set and the module name which owns statistics data are required in argument\&. Pid of the module in argument is optional\&.
-.PP
-
 \fBshow\fR
 will send the statistics data in JSON format\&. By default, it outputs all the statistics data it has collected\&. An optional item name may be specified to receive individual output\&.
 .PP
@@ -93,7 +110,7 @@ statistics:
 .PP
 boot_time
 .RS 4
-The date and time when this daemon was started in ISO 8601 format\&. This is a constant which can\'t be reset except by restarting
+The date and time when this daemon was started in ISO 8601 format\&. This is a constant which can\*(Aqt be reset except by restarting
 \fBb10\-stats\fR\&.
 .RE
 .PP
@@ -106,7 +123,7 @@ lname
 .RS 4
 This is the name used for the
 \fBb10\-msgq\fR
-command\-control channel\&. (This is a constant which can\'t be reset except by restarting
+command\-control channel\&. (This is a constant which can\*(Aqt be reset except by restarting
 \fBb10\-stats\fR\&.)
 .RE
 .PP

+ 25 - 18
src/bin/stats/b10-stats.xml

@@ -1,6 +1,6 @@
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
-	       [<!ENTITY mdash "&#8212;">]>
+               [<!ENTITY mdash "&#8212;">]>
 <!--
  - Copyright (C) 2010-2012  Internet Systems Consortium, Inc. ("ISC")
  -
@@ -59,11 +59,11 @@
       <command>bind10</command> and 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. The stats module collects data and
+      and so on. <command>b10-stats</command> periodically requests statistics
+      data from each module. The interval time can be configured
+      via <command>bindctl</command>. <command>b10-stats</command> cannot
+      accept any command from other modules for updating statistics data. The
+      stats module collects data and
       aggregates it. <command>b10-stats</command> invokes an internal
       command for <command>bind10</command> after its initial
       starting to make sure it collects statistics data from
@@ -78,9 +78,9 @@
       <varlistentry>
         <term><option>-v</option>, <option>--verbose</option></term>
         <listitem>
-	  <para>
-          This enables maximum debug logging.
-	  </para>
+          <para>
+            This enables maximum debug logging.
+          </para>
         </listitem>
       </varlistentry>
     </variablelist>
@@ -90,23 +90,30 @@
     <title>CONFIGURATION AND COMMANDS</title>
 
     <para>
-      The <command>b10-stats</command> command does not have any
-      configurable settings.
+      The only configurable setting in <filename>stats.spec</filename>
+      is:
     </para>
 
+    <variablelist>
+      <varlistentry>
+        <term><varname>poll-interval</varname></term>
+        <listitem>
+          <para>
+            is a time interval in seconds at which <command>b10-stats</command>
+            requests each module to return its statistics data. The default is 60
+            seconds. Polling can be disabled by setting it to 0. The type of the
+            value should be an unsigned integer. Negative integers are ignored.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+
 <!-- TODO: formating -->
     <para>
       The configuration commands are:
     </para>
 
     <para>
-      <command>set</command> will set new statistics data specified in
-      arguments. Statistics data to be set and the module name which owns
-      statistics data are required in argument. Pid of the module in argument
-      is optional.
-    </para>
-
-    <para>
       <command>show</command> will send the statistics data
       in JSON format.
       By default, it outputs all the statistics data it has collected.

+ 251 - 140
src/bin/stats/stats.py.in

@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# Copyright (C) 2010, 2011  Internet Systems Consortium.
+# Copyright (C) 2010, 2011, 2012  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
@@ -23,6 +23,8 @@ import sys; sys.path.append ('@@PYTHONPATH@@')
 import os
 from time import time, strftime, gmtime
 from optparse import OptionParser, OptionValueError
+import errno
+import select
 
 import isc
 import isc.util.process
@@ -96,6 +98,45 @@ def get_spec_defaults(spec):
             return spec.get("item_default", None)
     return dict([ (s['item_name'], _get_spec_defaults(s)) for s in spec ])
 
+def _accum(a, b):
+    """If the first arg is dict or list type, two values
+    would be merged and accumlated. This is for internal use."""
+
+    # If both of args are dict or list type, two
+    # values are merged.
+    if type(a) is dict and type(b) is dict:
+        return dict([ (k, _accum(v, b[k])) \
+                          if k in b else (k, v) \
+                          for (k, v) in a.items() ] \
+                        + [ (k, v) \
+                                for (k, v) in b.items() \
+                                if k not in a ])
+    elif type(a) is list and type(b) is list:
+        return [ _accum(a[i], b[i]) \
+                     if len(b) > i else a[i] \
+                     for i in range(len(a)) ] \
+                     + [ b[i] \
+                             for i in range(len(b)) \
+                             if len(a) <= i ]
+    # If both of args are integer or float type, two
+    # values are added.
+    elif (type(a) is int and type(b) is int) \
+            or (type(a) is float or type(b) is float):
+        return a + b
+
+    # If both of args are string type,
+    # values are compared and bigger one is returned.
+    elif type(a) is str and type(b) is str:
+        if a < b: return b
+        return a
+
+    # If the first arg is None type, the second value is returned.
+    elif a is None:
+        return b
+
+    # Nothing matches above, the first arg is returned
+    return a
+
 class Callback():
     """
     A Callback handler class
@@ -129,8 +170,8 @@ class Stats:
         self.module_name = self.mccs.get_module_spec().get_module_name()
         self.modules = {}
         self.statistics_data = {}
-        # statistics data by each pid
-        self.statistics_data_bypid = {}
+        # statistics data by each mid
+        self.statistics_data_bymid = {}
         # get commands spec
         self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
         # add event handler related command_handler of ModuleCCSession
@@ -144,51 +185,162 @@ class Stats:
                 self.callbacks[name] = Callback(command=callback, kwargs=kwargs)
             except AttributeError:
                 raise StatsError(STATS_UNKNOWN_COMMAND_IN_SPEC, cmd["command_name"])
+        self.config = {}
         self.mccs.start()
+        # setup my config
+        self.config = dict([
+                (itm['item_name'], self.mccs.get_value(itm['item_name'])[0])
+                for itm in self.mccs.get_module_spec().get_config_spec()
+                ])
+        # set a absolute timestamp polling at next time
+        self.next_polltime = get_timestamp() + self.get_interval()
+        # initialized Statistics data
+        self.update_modules()
+        if self.update_statistics_data(
+            self.module_name,
+            self.cc_session.lname,
+            {'lname': self.cc_session.lname,
+             'boot_time': get_datetime(_BASETIME),
+             'last_update_time': get_datetime()}):
+            logger.warn(STATS_RECEIVED_INVALID_STATISTICS_DATA,
+                        self.module_name)
+        # define the variable of the last time of polling
+        self._lasttime_poll = 0.0
+
+    def get_interval(self):
+        """return the current value of 'poll-interval'"""
+        return self.config['poll-interval']
+
+    def do_polling(self):
+        """Polls modules for statistics data. Return nothing. First
+           search multiple instances of same module. Second requests
+           each module to invoke 'getstats'. Finally updates internal
+           statistics data every time it gets from each instance."""
+
+        # It counts the number of instances of same module by
+        # examining the third value from the array result of
+        # 'show_processes' of Boss
+        seq = self.cc_session.group_sendmsg(
+            isc.config.ccsession.create_command("show_processes"),
+            'Boss')
+        (answer, env) = self.cc_session.group_recvmsg(False, seq)
+        modules = []
+        if answer:
+            (rcode, value) = isc.config.ccsession.parse_answer(answer)
+            if rcode == 0 and type(value) is list:
+                # NOTE: For example, the "show_processes" command
+                # of Boss is assumed to return the response in this
+                # format:
+                #  [
+                #  ...
+                #    [
+                #      20061,
+                #      "b10-auth",
+                #      "Auth"
+                #    ],
+                #    [
+                #      20103,
+                #      "b10-auth-2",
+                #      "Auth"
+                #    ]
+                #  ...
+                #  ]
+                # If multiple instances of the same module are
+                # running, the address names of them, which are at the
+                # third element, must be also same. Thus, the value of
+                # the third element of each outer element is read here
+                # for counting multiple instances.  This is a
+                # workaround for counting the instances. This should
+                # be fixed in another proper way in the future
+                # release.
+                modules = [ v[2] if type(v) is list and len(v) > 2 \
+                                else None for v in value ]
+        # start requesting each module to collect statistics data
+        sequences = []
+        for (module_name, data) in self.get_statistics_data().items():
+            # skip if module_name is 'Stats'
+            if module_name == self.module_name:
+                continue
+            logger.debug(DBG_STATS_MESSAGING, STATS_SEND_STATISTICS_REQUEST,
+                         module_name)
+            cmd = isc.config.ccsession.create_command(
+                "getstats", None) # no argument
+            seq = self.cc_session.group_sendmsg(cmd, module_name)
+            sequences.append((module_name, seq))
+            cnt = modules.count(module_name)
+            if cnt > 1:
+                sequences = sequences + [ (module_name, seq) \
+                                              for i in range(cnt-1) ]
+        # start receiving statistics data
+        _statistics_data = []
+        while len(sequences) > 0:
+            try:
+                (module_name, seq) = sequences.pop(0)
+                answer, env = self.cc_session.group_recvmsg(
+                    False, seq)
+                if answer:
+                    rcode, args = isc.config.ccsession.parse_answer(
+                        answer)
+                    if rcode == 0:
+                        _statistics_data.append(
+                            (module_name, env['from'], args))
+            # skip this module if SessionTimeout raised
+            except isc.cc.session.SessionTimeout:
+                pass
+
+        # update statistics data
+        self.update_modules()
+        while len(_statistics_data) > 0:
+            (_module_name, _lname, _args) = _statistics_data.pop(0)
+            if self.update_statistics_data(_module_name, _lname, _args):
+                logger.warn(
+                STATS_RECEIVED_INVALID_STATISTICS_DATA,
+                _module_name)
+            else:
+                if self.update_statistics_data(
+                    self.module_name,
+                    self.cc_session.lname,
+                    {'last_update_time': get_datetime()}):
+                    logger.warn(
+                        STATS_RECEIVED_INVALID_STATISTICS_DATA,
+                        self.module_name)
+        # if successfully done, set the last time of polling
+        self._lasttime_poll = get_timestamp()
 
     def start(self):
         """
         Start stats module
         """
-        self.running = True
         logger.info(STATS_STARTING)
 
-        # request Bob to send statistics data
-        logger.debug(DBG_STATS_MESSAGING, STATS_SEND_REQUEST_BOSS)
-        cmd = isc.config.ccsession.create_command("getstats", None)
-        seq = self.cc_session.group_sendmsg(cmd, 'Boss')
-        try:
-            answer, env = self.cc_session.group_recvmsg(False, seq)
-            if answer:
-                rcode, args = isc.config.ccsession.parse_answer(answer)
-                if rcode == 0:
-                    errors = self.update_statistics_data(
-                        args["owner"], **args["data"])
-                    if errors:
-                        raise StatsError("boss spec file is incorrect: "
-                                         + ", ".join(errors))
-                    errors = self.update_statistics_data(
-                                self.module_name,
-                                last_update_time=get_datetime())
-                    if errors:
-                        raise StatsError("stats spec file is incorrect: "
-                                         + ", ".join(errors))
-        except isc.cc.session.SessionTimeout:
-            pass
-
-        # initialized Statistics data
-        errors = self.update_statistics_data(
-            self.module_name,
-            lname=self.cc_session.lname,
-            boot_time=get_datetime(_BASETIME)
-            )
-        if errors:
-            raise StatsError("stats spec file is incorrect: "
-                             + ", ".join(errors))
+        def _check_command(nonblock=False):
+            """check invoked command by waiting for 'poll-interval'
+            seconds"""
+            # backup original timeout
+            orig_timeout = self.cc_session.get_timeout()
+            # set cc-session timeout to half of a second(500ms)
+            self.cc_session.set_timeout(500)
+            try:
+                answer, env = self.cc_session.group_recvmsg(nonblock)
+                self.mccs.check_command_without_recvmsg(answer, env)
+            except isc.cc.session.SessionTimeout:
+                pass # waited for poll-interval seconds
+            # restore timeout
+            self.cc_session.set_timeout(orig_timeout)
 
         try:
+            self.running = True
             while self.running:
-                self.mccs.check_command(False)
+                _check_command()
+                now = get_timestamp()
+                intval = self.get_interval()
+                if intval > 0 and now >= self.next_polltime:
+                    # decide the next polling timestamp
+                    self.next_polltime += intval
+                    # adjust next time
+                    if self.next_polltime < now:
+                        self.next_polltime = now
+                    self.do_polling()
         finally:
             self.mccs.send_stopping()
 
@@ -198,7 +350,21 @@ class Stats:
         """
         logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_NEW_CONFIG,
                      new_config)
-        # do nothing currently
+        errors = []
+        if not self.mccs.get_module_spec().\
+                validate_config(False, new_config, errors):
+                return isc.config.ccsession.create_answer(
+                    1, ", ".join(errors))
+
+        if 'poll-interval' in new_config \
+                and new_config['poll-interval'] < 0:
+            return isc.config.ccsession.create_answer(
+                1, "Negative integer ignored")
+
+        self.config.update(new_config)
+        if 'poll-interval' in self.config:
+            # update next polling timestamp
+            self.next_polltime = get_timestamp() + self.get_interval()
         return isc.config.create_answer(0)
 
     def command_handler(self, command, kwargs):
@@ -248,7 +414,7 @@ class Stats:
         module. If it can't find specified statistics data, it raises
         StatsError.
         """
-        self.update_statistics_data()
+        self.update_modules()
         if owner and name:
             try:
                 return {owner:{name:self.statistics_data[owner][name]}}
@@ -267,14 +433,14 @@ class Stats:
                          + "owner: " + str(owner) + ", "
                          + "name: " + str(name))
 
-    def update_statistics_data(self, owner=None, pid=-1, **data):
+    def update_statistics_data(self, owner=None, mid=None, data=None):
         """
-        change statistics date of specified module into specified
+        change statistics data of specified module into specified
         data. It updates information of each module first, and it
         updates statistics data. If specified data is invalid for
         statistics spec of specified owner, it returns a list of error
         messages. If there is no error or if neither owner nor data is
-        specified in args, it returns None. pid is the process id of
+        specified in args, it returns None. The 'mid' argument is an identifier of
         the sender module in order for stats to identify which
         instance sends statistics data in the situation that multiple
         instances are working.
@@ -282,49 +448,32 @@ class Stats:
         # Note:
         # The fix of #1751 is for multiple instances working. It is
         # assumed here that they send different statistics data with
-        # each PID. Stats should save their statistics data by
-        # PID. The statistics data, which is the existing variable, is
-        # preserved by accumlating from statistics data by PID. This
+        # each sender module id (mid). Stats should save their statistics data by
+        # mid. The statistics data, which is the existing variable, is
+        # preserved by accumlating from statistics data by the mid. This
         # is an ad-hoc fix because administrators can not see
         # statistics by each instance via bindctl or HTTP/XML. These
         # interfaces aren't changed in this fix.
 
-        def _accum_bymodule(statistics_data_bypid):
-            # This is an internal function for the superordinate
-            # function. It accumulates statistics data of each PID by
-            # module. It returns the accumulation result.
-            def _accum(a, b):
-                # If the first arg is dict or list type, two values
-                # would be merged and accumlated.
-                if type(a) is dict:
-                    return dict([ (k, _accum(v, b[k])) \
-                                      if k in b else (k, v) \
-                                      for (k, v) in a.items() ] \
-                                    + [ (k, v) \
-                                            for (k, v) in b.items() \
-                                            if k not in a ])
-                elif type(a) is list:
-                    return [ _accum(a[i], b[i]) \
-                                 if len(b) > i else a[i] \
-                                 for i in range(len(a)) ] \
-                                 + [ b[i] \
-                                         for i in range(len(b)) \
-                                         if len(a) <= i ]
-                # If the first arg is integer or float type, two
-                # values are just added.
-                elif type(a) is int or type(a) is float:
-                    return a + b
-                # If the first arg is str or other types than above,
-                # then it just returns the first arg which is assumed
-                # to be the newer value.
-                return a
+        def _accum_bymodule(statistics_data_bymid):
+            """This is an internal method for the superordinate
+            method. It accumulates statistics data of each module id
+            by module. It returns a accumulated result."""
+            # FIXME: A issue might happen when consolidating
+            # statistics of the multiple instances. If they have
+            # different statistics data which are not for adding each
+            # other, this might happen: If these are integer or float,
+            # these are added each other. If these are string , these
+            # are compared and consolidated into bigger one.  If one
+            # of them is None type , these might be consolidated
+            # into not None-type one. Otherwise these are overwritten
+            # into one of them.
             ret = {}
-            for data in statistics_data_bypid.values():
+            for data in statistics_data_bymid.values():
                 ret.update(_accum(data, ret))
             return ret
 
         # Firstly, it gets default statistics data in each spec file.
-        self.update_modules()
         statistics_data = {}
         for (name, module) in self.modules.items():
             value = get_spec_defaults(module.get_statistics_spec())
@@ -333,62 +482,32 @@ class Stats:
         self.statistics_data = statistics_data
 
         # If the "owner" and "data" arguments in this function are
-        # specified, then the variable of statistics data of each pid
+        # specified, then the variable of statistics data of each module id
         # would be updated.
         errors = []
         if owner and data:
             try:
                 if self.modules[owner].validate_statistics(False, data, errors):
-                    if owner in self.statistics_data_bypid:
-                        if pid in self.statistics_data_bypid[owner]:
-                            self.statistics_data_bypid[owner][pid].update(data)
+                    if owner in self.statistics_data_bymid:
+                        if mid in self.statistics_data_bymid[owner]:
+                            self.statistics_data_bymid[owner][mid].update(data)
                         else:
-                            self.statistics_data_bypid[owner][pid] = data
+                            self.statistics_data_bymid[owner][mid] = data
                     else:
-                        self.statistics_data_bypid[owner] = { pid : data }
+                        self.statistics_data_bymid[owner] = { mid : data }
             except KeyError:
                 errors.append("unknown module name: " + str(owner))
 
-        # If there are inactive instances, which was actually running
-        # on the system before, their statistics data would be
-        # removed. To find inactive instances, it invokes the
-        # "show_processes" command to Boss via the cc session. Then it
-        # gets active instance list and compares its PIDs with PIDs in
-        # statistics data which it already has. If inactive instances
-        # are found, it would remove their statistics data.
-        seq = self.cc_session.group_sendmsg(
-            isc.config.ccsession.create_command("show_processes", None),
-            "Boss")
-        (answer, env) = self.cc_session.group_recvmsg(False, seq)
-        if answer:
-            (rcode, value) = isc.config.ccsession.parse_answer(answer)
-            if rcode == 0:
-                if type(value) is list and len(value) > 0 \
-                        and type(value[0]) is list and len(value[0]) > 1:
-                    mlist = [ k for k in self.statistics_data_bypid.keys() ]
-                    for m in mlist:
-                        # PID list which it has before except for -1
-                        plist1 = [ p for p in self.statistics_data_bypid[m]\
-                                       .keys() if p != -1]
-                        # PID list of active instances which is
-                        # received from Boss
-                        plist2 = [ v[0] for v in value \
-                                       if v[1].lower().find(m.lower()) \
-                                       >= 0 ]
-                        # get inactive instance list by the difference
-                        # between plist1 and plist2
-                        nplist = set(plist1).difference(set(plist2))
-                        for p in nplist:
-                            self.statistics_data_bypid[m].pop(p)
-                        if self.statistics_data_bypid[m]:
-                            if m in self.statistics_data:
-                                self.statistics_data[m].update(
-                                    _accum_bymodule(
-                                        self.statistics_data_bypid[m]))
-                        # remove statistics data of the module with no
-                        # PID
-                        else:
-                            self.statistics_data_bypid.pop(m)
+        # Just consolidate statistics data of each module without
+        # removing that of modules which have been already dead
+        mlist = [ k for k in self.statistics_data_bymid.keys() ]
+        for m in mlist:
+            if self.statistics_data_bymid[m]:
+                if m in self.statistics_data:
+                    self.statistics_data[m].update(
+                        _accum_bymodule(
+                            self.statistics_data_bymid[m]))
+
         if errors: return errors
 
     def command_status(self):
@@ -413,6 +532,13 @@ class Stats:
         """
         handle show command
         """
+        # decide if polling should be done based on the the last time of
+        # polling. If more than one second has passed since the last
+        # request to each module, the stats module requests each module
+        # statistics data and then shows the latest result. Otherwise,
+        # the stats module just shows statistics data which it has.
+        if get_timestamp() - self._lasttime_poll > 1.0:
+            self.do_polling()
         if owner or name:
             logger.debug(DBG_STATS_MESSAGING,
                          STATS_RECEIVED_SHOW_NAME_COMMAND,
@@ -422,8 +548,9 @@ class Stats:
                          STATS_RECEIVED_SHOW_ALL_COMMAND)
         errors = self.update_statistics_data(
             self.module_name,
-            timestamp=get_timestamp(),
-            report_time=get_datetime()
+            self.cc_session.lname,
+            {'timestamp': get_timestamp(),
+             'report_time': get_datetime()}
             )
         if errors:
             raise StatsError("stats spec file is incorrect: "
@@ -474,22 +601,6 @@ class Stats:
                 1, "specified arguments are incorrect: " \
                     + "owner: " + str(owner) + ", name: " + str(name))
 
-    def command_set(self, owner, pid=-1, data={}):
-        """
-        handle set command
-        """
-        errors = self.update_statistics_data(owner, pid, **data)
-        if errors:
-            return isc.config.create_answer(
-                1, "errors while setting statistics data: " \
-                    + ", ".join(errors))
-        errors = self.update_statistics_data(
-            self.module_name, last_update_time=get_datetime() )
-        if errors:
-            raise StatsError("stats spec file is incorrect: "
-                             + ", ".join(errors))
-        return isc.config.create_answer(0)
-
 if __name__ == "__main__":
     try:
         parser = OptionParser()

+ 7 - 29
src/bin/stats/stats.spec

@@ -2,7 +2,13 @@
   "module_spec": {
     "module_name": "Stats",
     "module_description": "Stats daemon",
-    "config_data": [],
+    "config_data": [
+      { "item_name": "poll-interval",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 60
+      }
+    ],
     "commands": [
       {
         "command_name": "status",
@@ -59,34 +65,6 @@
             "item_description": "statistics item name of the owner"
           }
         ]
-      },
-      {
-        "command_name": "set",
-        "command_description": "set the value of specified name in statistics data",
-        "command_args": [
-          {
-            "item_name": "owner",
-            "item_type": "string",
-            "item_optional": false,
-            "item_default": "",
-            "item_description": "module name of the owner of the statistics data"
-          },
-	  {
-	    "item_name": "pid",
-            "item_type": "integer",
-            "item_optional": true,
-            "item_default": -1,
-            "item_description": "process id of the owner module"
-          },
-	  {
-	    "item_name": "data",
-            "item_type": "map",
-            "item_optional": false,
-            "item_default": {},
-            "item_description": "statistics data set of the owner",
-            "map_item_spec": []
-          }
-        ]
       }
     ],
     "statistics": [

+ 7 - 3
src/bin/stats/stats_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011, 2012  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
@@ -53,8 +53,12 @@ response indicating that it is running normally.
 An unknown command has been sent to the stats module. The stats module
 will respond with an error and the command will be ignored.
 
-% STATS_SEND_REQUEST_BOSS requesting boss to send statistics
-This debug message is printed when a request is sent to the boss module
+% STATS_RECEIVED_INVALID_STATISTICS_DATA received invalid statistics data from %1
+Invalid statistics data has been received from the module while
+polling and it has been discarded.
+
+% STATS_SEND_STATISTICS_REQUEST requesting %1 to send statistics
+This debug message is printed when a request is sent to the module
 to send its data to the stats module.
 
 % STATS_STARTING starting

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

@@ -1,3 +1,5 @@
+SUBDIRS = testdata .
+
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
 EXTRA_DIST = $(PYTESTS) test_utils.py
@@ -23,7 +25,7 @@ endif
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/stats:$(abs_top_builddir)/src/bin/stats/tests:$(abs_top_builddir)/src/bin/msgq:$(abs_top_builddir)/src/lib/python/isc/config \
 	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
-	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
+	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/bin/stats/tests/testdata \
 	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 276 - 270
src/bin/stats/tests/b10-stats_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2010, 2011  Internet Systems Consortium.
+# Copyright (C) 2010, 2011, 2012  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
@@ -100,6 +100,44 @@ class TestUtilties(unittest.TestCase):
         self.assertNotEqual(stats.get_datetime(
                 (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
 
+    def test__accum(self):
+        self.assertEqual(stats._accum(None, None), None)
+        self.assertEqual(stats._accum(None, "b"), "b")
+        self.assertEqual(stats._accum("a", None), "a")
+        self.assertEqual(stats._accum(1, 2), 3)
+        self.assertEqual(stats._accum(0.5, 0.3), 0.8)
+        self.assertEqual(stats._accum('aa','bb'), 'bb')
+        self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
+                         '2012-08-09T09:33:31Z')
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [4, 5]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [4, 5], [1, 2, 3]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, 5, 6]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [None, 5, 6], [1, 2, 3]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, None, None, None]), [1,2,3,None])
+        self.assertEqual(stats._accum(
+                [[1,2],3],[[],5,6]), [[1,2],8,6])
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'one': 4, 'two': 5}),
+                         {'one': 5, 'two': 7, 'three': 3})
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'four': 4, 'five': 5}),
+                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+        self.assertEqual(stats._accum(
+                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+                         {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
+        self.assertEqual(stats._accum(
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
+
 class TestCallback(unittest.TestCase):
     def setUp(self):
         self.dummy_func = lambda *x, **y : (x, y)
@@ -150,7 +188,6 @@ class TestStats(unittest.TestCase):
         # set the signal handler for deadlock
         self.sig_handler = SignalHandler(self.fail)
         self.base = BaseModules()
-        self.stats = stats.Stats()
         self.const_timestamp = 1308730448.965706
         self.const_datetime = '2011-06-22T08:14:08Z'
         self.const_default_datetime = '1970-01-01T00:00:00Z'
@@ -161,6 +198,7 @@ class TestStats(unittest.TestCase):
         self.sig_handler.reset()
 
     def test_init(self):
+        self.stats = stats.Stats()
         self.assertEqual(self.stats.module_name, 'Stats')
         self.assertFalse(self.stats.running)
         self.assertTrue('command_show' in self.stats.callbacks)
@@ -168,7 +206,7 @@ class TestStats(unittest.TestCase):
         self.assertTrue('command_shutdown' in self.stats.callbacks)
         self.assertTrue('command_show' in self.stats.callbacks)
         self.assertTrue('command_showschema' in self.stats.callbacks)
-        self.assertTrue('command_set' in self.stats.callbacks)
+        self.assertEqual(self.stats.config['poll-interval'], 60)
 
     def test_init_undefcmd(self):
         spec_str = """\
@@ -217,22 +255,12 @@ class TestStats(unittest.TestCase):
         # Also temporarily disabled for #1668, see above
         #self.assertTrue(self.stats.mccs.stopped)
 
-        # start with err
-        self.stats = stats.Stats()
-        self.stats.update_statistics_data = lambda x,**y: ['an error']
-        self.assertRaises(stats.StatsError, self.stats.start)
-
     def test_handlers(self):
         self.stats_server = ThreadingServerManager(MyStats)
         self.stats = self.stats_server.server
         self.stats_server.run()
-        # config_handler
-        self.assertEqual(self.stats.config_handler({'foo':'bar'}),
-                         isc.config.create_answer(0))
 
         # command_handler
-        self.base.boss.server._started.wait()
-        self.base.boss.server._started.clear()
         self.assertEqual(
             send_command(
                 'show', 'Stats',
@@ -241,12 +269,6 @@ class TestStats(unittest.TestCase):
             (0, {'Boss': {'boot_time': self.const_datetime}}))
         self.assertEqual(
             send_command(
-                'set', 'Stats',
-                params={ 'owner' : 'Boss',
-                  'data'  : { 'boot_time' : self.const_datetime } }),
-            (0, None))
-        self.assertEqual(
-            send_command(
                 'show', 'Stats',
                 params={ 'owner' : 'Boss',
                   'name'  : 'boot_time' }),
@@ -305,7 +327,8 @@ class TestStats(unittest.TestCase):
         self.stats_server.shutdown()
 
     def test_update_modules(self):
-        self.assertEqual(len(self.stats.modules), 0)
+        self.stats = stats.Stats()
+        self.assertEqual(len(self.stats.modules), 3) # Auth, Boss, Stats
         self.stats.update_modules()
         self.assertTrue('Stats' in self.stats.modules)
         self.assertTrue('Boss' in self.stats.modules)
@@ -330,6 +353,7 @@ class TestStats(unittest.TestCase):
         stats.isc.config.ccsession.parse_answer = orig_parse_answer
 
     def test_get_statistics_data(self):
+        self.stats = stats.Stats()
         my_statistics_data = self.stats.get_statistics_data()
         self.assertTrue('Stats' in my_statistics_data)
         self.assertTrue('Boss' in my_statistics_data)
@@ -345,13 +369,13 @@ class TestStats(unittest.TestCase):
         my_statistics_data = self.stats.get_statistics_data(owner='Stats', name='report_time')
         self.assertEqual(my_statistics_data['Stats']['report_time'], self.const_default_datetime)
         my_statistics_data = self.stats.get_statistics_data(owner='Stats', name='boot_time')
-        self.assertEqual(my_statistics_data['Stats']['boot_time'], self.const_default_datetime)
+        self.assertTrue('boot_time' in my_statistics_data['Stats'])
         my_statistics_data = self.stats.get_statistics_data(owner='Stats', name='last_update_time')
-        self.assertEqual(my_statistics_data['Stats']['last_update_time'], self.const_default_datetime)
+        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
         my_statistics_data = self.stats.get_statistics_data(owner='Stats', name='timestamp')
         self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
         my_statistics_data = self.stats.get_statistics_data(owner='Stats', name='lname')
-        self.assertEqual(my_statistics_data, {'Stats': {'lname':''}})
+        self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
         self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
                           owner='Stats', name='Bar')
         self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
@@ -360,82 +384,144 @@ class TestStats(unittest.TestCase):
                           name='Bar')
 
     def test_update_statistics_data(self):
-        self.stats.update_statistics_data(owner='Stats', lname='foo@bar')
-        self.assertTrue('Stats' in self.stats.statistics_data)
-        my_statistics_data = self.stats.statistics_data['Stats']
-        self.assertEqual(my_statistics_data['lname'], 'foo@bar')
-        self.stats.update_statistics_data(owner='Stats', last_update_time=self.const_datetime)
-        self.assertTrue('Stats' in self.stats.statistics_data)
-        my_statistics_data = self.stats.statistics_data['Stats']
-        self.assertEqual(my_statistics_data['last_update_time'], self.const_datetime)
-        self.assertEqual(self.stats.update_statistics_data(owner='Stats', lname=0.0),
+        self.stats = stats.Stats()
+
+        # success
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         self.stats.cc_session.lname)
+        self.stats.update_statistics_data(
+            'Stats', self.stats.cc_session.lname,
+            {'lname': 'foo@bar'})
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         'foo@bar')
+        # error case
+        self.assertEqual(self.stats.update_statistics_data('Stats', None,
+                                                           {'lname': 0.0}),
                          ['0.0 should be a string'])
-        self.assertEqual(self.stats.update_statistics_data(owner='Dummy', foo='bar'),
+        self.assertEqual(self.stats.update_statistics_data('Dummy', None,
+                                                           {'foo': 'bar'}),
                          ['unknown module name: Dummy'])
 
-    def test_update_statistics_data_withpid(self):
-        # one pid of Auth
-        self.stats.update_statistics_data(owner='Auth',
-                                          pid=9999,
-                                          **{'queries.tcp':1001})
+    def test_update_statistics_data_withmid(self):
+        self.stats = stats.Stats()
+        self.stats.do_polling()
+        # samples of query number
+        bar1_tcp = 1001
+        bar2_tcp = 2001
+        bar3_tcp = 1002
+        bar3_udp = 1003
+        # two auth instances invoked
+        list_auth = [ self.base.auth.server,
+                      self.base.auth2.server ]
+        sum_qtcp = 0
+        for a in list_auth:
+            sum_qtcp += a.queries_tcp
+        sum_qudp = 0
+        for a in list_auth:
+            sum_qudp += a.queries_udp
+        self.stats.update_statistics_data('Auth', "bar1@foo",
+                                          {'queries.tcp':bar1_tcp})
         self.assertTrue('Auth' in self.stats.statistics_data)
         self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid,
-                         {'Auth': {9999: {'queries.tcp': 1001}}})
-        # non-existent pid of Auth, but no changes in statistics data
-        self.stats.update_statistics_data(owner='Auth',
-                                          pid=10000,
-                                          **{'queries.tcp':2001})
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1@foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo'],
+                         {'queries.tcp': bar1_tcp})
+        # check consolidation of statistics data even if there is
+        # non-existent mid of Auth
+        self.stats.update_statistics_data('Auth', "bar2@foo",
+                                          {'queries.tcp': bar2_tcp})
         self.assertTrue('Auth' in self.stats.statistics_data)
         self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid,
-                         {'Auth': {9999: {'queries.tcp': 1001}}})
-        # kill running Auth, then statistics is reset
-        self.assertEqual(self.base.boss.server.pid_list[0][0], 9999)
-        killed = self.base.boss.server.pid_list.pop(0)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1@foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo'],
+                         {'queries.tcp': bar1_tcp})
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2@foo'],
+                         {'queries.tcp': bar2_tcp})
+        # kill running Auth but the statistics data doesn't change
+        self.base.auth2.server.shutdown()
         self.stats.update_statistics_data()
         self.assertTrue('Auth' in self.stats.statistics_data)
         self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
         self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 0)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 0)
-        self.assertFalse('Auth' in self.stats.statistics_data_bypid)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
         # restore statistics data of killed auth
-        self.base.boss.server.pid_list = [ killed ] + self.base.boss.server.pid_list[:]
-        self.stats.update_statistics_data(owner='Auth',
-                                          pid=9999,
-                                          **{'queries.tcp':1001})
-        # another pid of Auth
-        self.stats.update_statistics_data(owner='Auth',
-                                          pid=9998,
-                                          **{'queries.tcp':1002,
-                                             'queries.udp':1003})
+        # self.base.boss.server.pid_list = [ killed ] + self.base.boss.server.pid_list[:]
+        self.stats.update_statistics_data('Auth',
+                                          "bar1@foo",
+                                          {'queries.tcp': bar1_tcp})
+        # set another mid of Auth
+        self.stats.update_statistics_data('Auth',
+                                          "bar3@foo",
+                                          {'queries.tcp':bar3_tcp,
+                                           'queries.udp':bar3_udp})
         self.assertTrue('Auth' in self.stats.statistics_data)
         self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
         self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9999 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue(9998 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9999])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9998])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9999]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.tcp'], 1002)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9998]['queries.udp'], 1003)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+                         bar3_udp + sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1@foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('bar3@foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1@foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3@foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3@foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1@foo']['queries.tcp'], bar1_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3@foo']['queries.tcp'], bar3_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3@foo']['queries.udp'], bar3_udp)
+
+    def test_config(self):
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_timestamp = lambda : self.const_timestamp
+        stats_server = ThreadingServerManager(MyStats)
+        stat = stats_server.server
+        # test updating poll-interval
+        self.assertEqual(stat.config['poll-interval'], 60)
+        self.assertEqual(stat.get_interval(), 60)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
+        self.assertEqual(stat.config_handler({'poll-interval': 120}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 120)
+        self.assertEqual(stat.get_interval(), 120)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
+        stats.get_timestamp = orig_get_timestamp
+        self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
+                         isc.config.create_answer(1, 'foo should be an integer'))
+        self.assertEqual(stat.config_handler({'poll-interval': -1}),
+                         isc.config.create_answer(1, 'Negative integer ignored'))
+        # unknown item
+        self.assertEqual(
+            stat.config_handler({'_UNKNOWN_KEY_': None}),
+            isc.config.ccsession.create_answer(
+                1, "unknown item _UNKNOWN_KEY_"))
+        # test no change if zero interval time
+        self.assertEqual(stat.config_handler({'poll-interval': 0}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 0)
+        stats_server.run()
+        self.assertEqual(
+            send_command(
+                'show', 'Stats',
+                params={ 'owner' : 'Boss',
+                  'name'  : 'boot_time' }),
+            (0, {'Boss': {'boot_time': self.const_datetime}}))
+        stats_server.shutdown()
 
     def test_commands(self):
+        self.stats = stats.Stats()
+
         # status
         self.assertEqual(self.stats.command_status(),
                 isc.config.create_answer(
@@ -448,6 +534,14 @@ class TestStats(unittest.TestCase):
         self.assertFalse(self.stats.running)
 
     def test_command_show(self):
+        # two auth instances invoked
+        list_auth = [ self.base.auth.server,
+                      self.base.auth2.server ]
+        sum_qtcp = 0
+        sum_qudp = 0
+        sum_qtcp_perzone = 0
+        sum_qudp_perzone = 0
+        self.stats = stats.Stats()
         self.assertEqual(self.stats.command_show(owner='Foo', name=None),
                          isc.config.create_answer(
                 1, "specified arguments are incorrect: owner: Foo, name: None"))
@@ -457,46 +551,50 @@ class TestStats(unittest.TestCase):
         self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
                          isc.config.create_answer(
                 1, "specified arguments are incorrect: owner: Foo, name: bar"))
+
+        for a in list_auth:
+            sum_qtcp += a.queries_tcp
+            sum_qudp += a.queries_udp
+            zonename = a.queries_per_zone[0]['zonename']
+            sum_qtcp_perzone += a.queries_per_zone[0]['queries.tcp']
+            sum_qudp_perzone += a.queries_per_zone[0]['queries.udp']
+
         self.assertEqual(self.stats.command_show(owner='Auth'),
                          isc.config.create_answer(
-                0, {'Auth':{ 'queries.udp': 0,
-                     'queries.tcp': 0,
+                0, {'Auth':{ 'queries.udp': sum_qudp,
+                     'queries.tcp': sum_qtcp,
                      'queries.perzone': [{ 'zonename': 'test1.example',
-                                           'queries.udp': 1,
-                                           'queries.tcp': 2 },
-                                         { 'zonename': 'test2.example',
-                                           'queries.udp': 3,
-                                           'queries.tcp': 4 }] }}))
+                                           'queries.udp': sum_qudp_perzone,
+                                           'queries.tcp': sum_qtcp_perzone }
+                                         ]}}))
         self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
                          isc.config.create_answer(
-                0, {'Auth': {'queries.udp':0}}))
+                0, {'Auth': {'queries.udp': sum_qudp}}))
         self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
                          isc.config.create_answer(
                 0, {'Auth': {'queries.perzone': [{ 'zonename': 'test1.example',
-                      'queries.udp': 1,
-                      'queries.tcp': 2 },
-                    { 'zonename': 'test2.example',
-                      'queries.udp': 3,
-                      'queries.tcp': 4 }]}}))
-        orig_get_timestamp = stats.get_timestamp
+                      'queries.udp': sum_qudp_perzone,
+                      'queries.tcp': sum_qtcp_perzone }]}}))
         orig_get_datetime = stats.get_datetime
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_datetime = lambda x=None: self.const_datetime
         stats.get_timestamp = lambda : self.const_timestamp
-        stats.get_datetime = lambda : self.const_datetime
-        self.assertEqual(stats.get_timestamp(), self.const_timestamp)
-        self.assertEqual(stats.get_datetime(), self.const_datetime)
-        self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'), \
-                             isc.config.create_answer(0, {'Stats': {'report_time':self.const_datetime}}))
-        self.assertEqual(self.stats.statistics_data['Stats']['timestamp'], self.const_timestamp)
-        self.assertEqual(self.stats.statistics_data['Stats']['boot_time'], self.const_default_datetime)
-        stats.get_timestamp = orig_get_timestamp
+        self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'),
+                         isc.config.create_answer(
+                0, {'Stats': {'report_time':self.const_datetime}}))
+        self.assertEqual(self.stats.command_show(owner='Stats', name='timestamp'),
+                         isc.config.create_answer(
+                0, {'Stats': {'timestamp':self.const_timestamp}}))
         stats.get_datetime = orig_get_datetime
-        self.stats.mccs.specification = isc.config.module_spec.ModuleSpec(
+        stats.get_timestamp = orig_get_timestamp
+        self.stats.modules[self.stats.module_name] = isc.config.module_spec.ModuleSpec(
             { "module_name": self.stats.module_name,
               "statistics": [] } )
         self.assertRaises(
-            stats.StatsError, self.stats.command_show, owner='Foo', name='bar')
+            stats.StatsError, self.stats.command_show, owner=self.stats.module_name, name='bar')
 
     def test_command_showchema(self):
+        self.stats = stats.Stats()
         (rcode, value) = isc.config.ccsession.parse_answer(
             self.stats.command_showschema())
         self.assertEqual(rcode, 0)
@@ -724,170 +822,78 @@ class TestStats(unittest.TestCase):
                          isc.config.create_answer(
                 1, "module name is not specified"))
 
-    def test_command_set(self):
-        orig_get_datetime = stats.get_datetime
-        stats.get_datetime = lambda : self.const_datetime
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_set(owner='Boss',
-                                   data={ 'boot_time' : self.const_datetime }))
-        stats.get_datetime = orig_get_datetime
-        self.assertEqual(rcode, 0)
-        self.assertTrue(value is None)
-        self.assertEqual(self.stats.statistics_data['Boss']['boot_time'],
-                         self.const_datetime)
-        self.assertEqual(self.stats.statistics_data['Stats']['last_update_time'],
-                         self.const_datetime)
-        self.assertEqual(self.stats.command_set(owner='Stats',
-                                                data={ 'lname' : 'foo@bar' }),
-                         isc.config.create_answer(0, None))
-        self.stats.statistics_data['Stats'] = {}
-        self.stats.mccs.specification = isc.config.module_spec.ModuleSpec(
-            { "module_name": self.stats.module_name,
-              "statistics": [] } )
-        self.assertEqual(self.stats.command_set(owner='Stats',
-                                                data={ 'lname' : '_foo_@_bar_' }),
-                         isc.config.create_answer(
-                1,
-                "errors while setting statistics data: unknown item lname"))
-        self.stats.statistics_data['Stats'] = {}
-        self.stats.mccs.specification = isc.config.module_spec.ModuleSpec(
-            { "module_name": self.stats.module_name } )
-        self.assertEqual(self.stats.command_set(owner='Stats',
-                                                data={ 'lname' : '_foo_@_bar_' }),
-                         isc.config.create_answer(
-                1,
-                "errors while setting statistics data: No statistics specification"))
-        self.stats.statistics_data['Stats'] = {}
-        self.stats.mccs.specification = isc.config.module_spec.ModuleSpec(
-            { "module_name": self.stats.module_name,
-              "statistics": [
-                    {
-                        "item_name": "dummy",
-                        "item_type": "string",
-                        "item_optional": False,
-                        "item_default": "",
-                        "item_title": "Local Name",
-                        "item_description": "brabra"
-                        } ] } )
-        self.assertRaises(stats.StatsError,
-                          self.stats.command_set, owner='Stats', data={ 'dummy' : '_xxxx_yyyy_zzz_' })
-
-    def test_command_set_withpid(self):
-        # one pid of Auth
-        retval = isc.config.ccsession.parse_answer(
-            self.stats.command_set(owner='Auth',
-                                   pid=9997,
-                                   data={ 'queries.tcp' : 1001,
-                                          'queries.perzone':
-                                              [{ 'zonename': 'test1.example',
-                                                 'queries.tcp': 1 },
-                                               { 'zonename': 'test2.example',
-                                                 'queries.tcp': 2,
-                                                 'queries.udp': 3 }]}))
-        self.assertEqual(retval, (0,None))
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 1 },
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 2,
-                            'queries.udp': 3 }])
-        self.assertTrue('Stats' in self.stats.statistics_data)
-        self.assertTrue('last_update_time' in self.stats.statistics_data['Stats'])
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
-        self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9997])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 1 },
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 2,
-                            'queries.udp': 3 }])
-        # non-existent pid of Auth, but no changes in statistics data
-        retval = isc.config.ccsession.parse_answer(
-            self.stats.command_set(owner='Auth',
-                                   pid=10000,
-                                   data={ 'queries.tcp' : 2001,
-                                          'queries.perzone':
-                                              [{ 'zonename': 'test1.example',
-                                                 'queries.tcp': 101 },
-                                               { 'zonename': 'test2.example',
-                                                 'queries.tcp': 102,
-                                                 'queries.udp': 103 }]}))
-        self.assertEqual(retval, (0,None))
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 1 },
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 2,
-                            'queries.udp': 3 }])
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 1 },
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 2,
-                            'queries.udp': 3 }])
-        # another pid of Auth
-        retval = isc.config.ccsession.parse_answer(
-            self.stats.command_set(owner='Auth',
-                                   pid=9996,
-                                   data={ 'queries.tcp' : 1002,
-                                          'queries.udp' : 1003,
-                                          'queries.perzone':
-                                              [{ 'zonename': 'test1.example',
-                                                 'queries.tcp': 10,
-                                                 'queries.udp': 11},
-                                               { 'zonename': 'test2.example',
-                                                 'queries.tcp': 12,
-                                                 'queries.udp': 13 }]}))
-        self.assertEqual(retval, (0,None))
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.perzone' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'], 2003)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'], 1003)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 11,
-                            'queries.udp': 11},
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 14,
-                            'queries.udp': 16 }])
-        self.assertTrue('Auth' in self.stats.statistics_data_bypid)
-        self.assertTrue(9997 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue(9996 in self.stats.statistics_data_bypid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bypid['Auth'][9997])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bypid['Auth'][9996])
-        self.assertTrue('queries.perzone' in self.stats.statistics_data_bypid['Auth'][9996])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.tcp'], 1001)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9997]['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 1 },
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 2,
-                            'queries.udp': 3 }])
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.tcp'], 1002)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.udp'], 1003)
-        self.assertEqual(self.stats.statistics_data_bypid['Auth'][9996]['queries.perzone'],
-                         [{ 'zonename': 'test1.example',
-                            'queries.tcp': 10,
-                            'queries.udp': 11},
-                          { 'zonename': 'test2.example',
-                            'queries.tcp': 12,
-                            'queries.udp': 13 }])
+    def test_polling(self):
+        stats_server = ThreadingServerManager(MyStats)
+        stat = stats_server.server
+        stats_server.run()
+        self.assertEqual(
+            send_command('show', 'Stats'),
+            (0, stat.statistics_data))
+        # check statistics data of 'Boss'
+        boss = self.base.boss.server
+        self.assertEqual(
+            stat.statistics_data_bymid['Boss'][boss.cc_session.lname],
+            {'boot_time': self.const_datetime})
+        self.assertEqual(
+            len(stat.statistics_data_bymid['Boss']), 1)
+        self.assertEqual(
+            stat.statistics_data['Boss'],
+            {'boot_time': self.const_datetime})
+        # check statistics data of each 'Auth' instances
+        list_auth = ['', '2']
+        for i in list_auth:
+            auth = getattr(self.base,"auth"+i).server
+            for s in stat.statistics_data_bymid['Auth'].values():
+                self.assertEqual(
+                    s, {'queries.perzone': auth.queries_per_zone,
+                        'queries.tcp': auth.queries_tcp,
+                        'queries.udp': auth.queries_udp})
+            n = len(stat.statistics_data_bymid['Auth'])
+            self.assertEqual(n, len(list_auth))
+            # check consolidation of statistics data of the auth
+            # instances
+            self.assertEqual(
+                stat.statistics_data['Auth'],
+                {'queries.perzone': [
+                        {'zonename':
+                             auth.queries_per_zone[0]['zonename'],
+                         'queries.tcp':
+                             auth.queries_per_zone[0]['queries.tcp']*n,
+                         'queries.udp':
+                             auth.queries_per_zone[0]['queries.udp']*n}],
+                 'queries.tcp': auth.queries_tcp*n,
+                 'queries.udp': auth.queries_udp*n})
+        # check statistics data of 'Stats'
+        self.assertEqual(
+            len(stat.statistics_data['Stats']), 5)
+        self.assertTrue('boot_time' in
+            stat.statistics_data['Stats'])
+        self.assertTrue('last_update_time' in
+            stat.statistics_data['Stats'])
+        self.assertTrue('report_time' in
+            stat.statistics_data['Stats'])
+        self.assertTrue('timestamp' in
+            stat.statistics_data['Stats'])
+        self.assertEqual(
+            stat.statistics_data['Stats']['lname'],
+            stat.mccs._session.lname)
+        stats_server.shutdown()
+
+    def test_polling2(self):
+        # set invalid statistics
+        boss = self.base.boss.server
+        boss.statistics_data = {'boot_time':1}
+        stats_server = ThreadingServerManager(MyStats)
+        stat = stats_server.server
+        stats_server.run()
+        self.assertEqual(
+            send_command('status', 'Stats'),
+            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+        # check default statistics data of 'Boss'
+        self.assertEqual(
+            stat.statistics_data['Boss'],
+            {'boot_time': self.const_default_datetime})
+        stats_server.shutdown()
 
 class TestOSEnv(unittest.TestCase):
     def test_osenv(self):

+ 81 - 30
src/bin/stats/tests/test_utils.py

@@ -144,11 +144,71 @@ class MockBoss:
   "module_spec": {
     "module_name": "Boss",
     "module_description": "Mock Master process",
-    "config_data": [],
+    "config_data": [
+      {
+        "item_name": "components",
+        "item_type": "named_set",
+        "item_optional": false,
+        "item_default": {
+          "b10-stats": { "address": "Stats", "kind": "dispensable" },
+          "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        },
+        "named_set_item_spec": {
+          "item_name": "component",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": { },
+          "map_item_spec": [
+            {
+              "item_name": "special",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "process",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "kind",
+              "item_optional": false,
+              "item_type": "string",
+              "item_default": "dispensable"
+            },
+            {
+              "item_name": "address",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "params",
+              "item_optional": true,
+              "item_type": "list",
+              "list_item_spec": {
+                "item_name": "param",
+                "item_optional": false,
+                "item_type": "string",
+                "item_default": ""
+              }
+            },
+            {
+              "item_name": "priority",
+              "item_optional": true,
+              "item_type": "integer"
+            }
+          ]
+        }
+      }
+    ],
     "commands": [
       {
-        "command_name": "sendstats",
-        "command_description": "Send data to a statistics module at once",
+        "command_name": "shutdown",
+        "command_description": "Shut down BIND 10",
+        "command_args": []
+      },
+      {
+        "command_name": "ping",
+        "command_description": "Ping the boss process",
         "command_args": []
       },
       {
@@ -185,10 +245,11 @@ class MockBoss:
         self.spec_file.close()
         self.cc_session = self.mccs._session
         self.got_command_name = ''
-        self.pid_list = [[ 9999, "b10-auth"   ],
-                         [ 9998, "b10-auth-2" ],
-                         [ 9997, "b10-auth-3" ],
-                         [ 9996, "b10-auth-4" ]]
+        self.pid_list = [[ 9999, "b10-auth", "Auth" ],
+                         [ 9998, "b10-auth-2", "Auth" ]]
+        self.statistics_data = {
+            'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
+            }
 
     def run(self):
         self.mccs.start()
@@ -209,16 +270,9 @@ class MockBoss:
     def command_handler(self, command, *args, **kwargs):
         self._started.set()
         self.got_command_name = command
-        params = { "owner": "Boss",
-                   "data": {
-                'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
-                }
-                   }
-        if command == 'sendstats':
-            send_command("set", "Stats", params=params, session=self.cc_session)
-            return isc.config.create_answer(0)
-        elif command == 'getstats':
-            return isc.config.create_answer(0, params)
+        sdata = self.statistics_data
+        if command == 'getstats':
+            return isc.config.create_answer(0, sdata)
         elif command == 'show_processes':
             # Return dummy pids
             return isc.config.create_answer(
@@ -232,13 +286,7 @@ class MockAuth:
     "module_name": "Auth",
     "module_description": "Mock Authoritative service",
     "config_data": [],
-    "commands": [
-      {
-        "command_name": "sendstats",
-        "command_description": "Send data to a statistics module at once",
-        "command_args": []
-      }
-    ],
+    "commands": [],
     "statistics": [
       {
         "item_name": "queries.tcp",
@@ -349,12 +397,11 @@ class MockAuth:
 
     def command_handler(self, command, *args, **kwargs):
         self.got_command_name = command
-        if command == 'sendstats':
-            params = { "owner": "Auth",
-                       "data": { 'queries.tcp': self.queries_tcp,
-                                 'queries.udp': self.queries_udp,
-                                 'queries.perzone' : self.queries_per_zone } }
-            return send_command("set", "Stats", params=params, session=self.cc_session)
+        sdata = { 'queries.tcp': self.queries_tcp,
+                  'queries.udp': self.queries_udp,
+                  'queries.perzone' : self.queries_per_zone }
+        if command == 'getstats':
+            return isc.config.create_answer(0, sdata)
         return isc.config.create_answer(1, "Unknown Command")
 
 class MyStats(stats.Stats):
@@ -428,9 +475,13 @@ class BaseModules:
         # MockAuth
         self.auth = ThreadingServerManager(MockAuth)
         self.auth.run()
+        self.auth2 = ThreadingServerManager(MockAuth)
+        self.auth2.run()
+
 
     def shutdown(self):
         # MockAuth
+        self.auth2.shutdown()
         self.auth.shutdown()
         # MockBoss
         self.boss.shutdown()

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

@@ -0,0 +1 @@
+EXTRA_DIST = b10-config.db

+ 14 - 0
src/bin/stats/tests/testdata/b10-config.db

@@ -0,0 +1,14 @@
+{ "version": 2,
+  "Boss": {
+    "components": {
+      "b10-auth": {
+        "kind": "needed",
+        "special": "auth"
+      },
+      "b10-auth-2": {
+        "kind": "needed",
+        "special": "auth"
+      }
+    }
+  }
+}

+ 14 - 0
src/lib/python/isc/bind10/component.py

@@ -371,6 +371,14 @@ class BaseComponent:
         """
         pass
 
+    def address(self):
+        """
+        Provides the name of the address used on the message bus
+
+        You need to provide this method in a concrete implementation.
+        """
+        pass
+
 class Component(BaseComponent):
     """
     The most common implementation of a component. It can be used either
@@ -454,6 +462,12 @@ class Component(BaseComponent):
             else:
                 self._procinfo.process.terminate()
 
+    def address(self):
+        """
+        Returns the name of the address used on the message bus
+        """
+        return self._address
+
 class Configurator:
     """
     This thing keeps track of configuration changes and starts and stops

+ 7 - 0
src/lib/python/isc/bind10/tests/component_test.py

@@ -164,6 +164,13 @@ class ComponentTests(BossUtils, unittest.TestCase):
         component = self.__create_component('core')
         self.assertEqual('No process', component.name())
 
+    def test_address(self):
+        """
+        Test the address provides whatever we passed to the constructor as process.
+        """
+        component = self.__create_component('core')
+        self.assertEqual("homeless", component.address())
+
     def test_guts(self):
         """
         Test the correct data are stored inside the component.

+ 3 - 0
tests/system/bindctl/nsx1/b10-config.db.template.in

@@ -23,5 +23,8 @@
             "debuglevel": 99
         }
      ]
+ },
+ "Stats": {
+   "poll-interval": 1
  }
 }

+ 44 - 19
tests/system/bindctl/tests.sh

@@ -62,10 +62,15 @@ echo 'Stats show
 cnt_value1=`expr $cnt_value1 + 0`
 cnt_value2=`expr $cnt_value2 + 1`
 cnt_value3=`expr $cnt_value1 + $cnt_value2`
-grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
+# Further changes of Boss(#2137) and Auth(#2138) depends on this
+# change(#2136). So statistics tests in this system test make no
+# sense. Following statistics tests are disabled until codes of both
+# #2137 and #2138 are merged.
+#grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+#if [ $status != 0 ]; then echo "I:failed"; fi
+echo "I:skipped"
 n=`expr $n + 1`
 
 echo "I:Stopping b10-auth and checking that ($n)"
@@ -97,14 +102,23 @@ sleep 2
 echo 'Stats show
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# The statistics counters should have been reset while stop/start.
-cnt_value1=0
-cnt_value2=1
+# The statistics counters can not be reset even after auth
+# restarts. Because stats preserves the query counts which the dying
+# auth sent. Then it cumulates them and new counts which the living
+# auth sends. This note assumes that the issue would have been
+# resolved : "#1941 stats lossage (multiple auth servers)".
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
 cnt_value3=`expr $cnt_value1 + $cnt_value2`
-grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
+# Further changes of Boss(#2137) and Auth(#2138) depends on this
+# change(#2136). So statistics tests in this system test make no
+# sense. Following statistics tests are disabled until codes of both
+# #2137 and #2138 are merged.
+#grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+#if [ $status != 0 ]; then echo "I:failed"; fi
+echo "I:skipped"
 n=`expr $n + 1`
 
 echo "I:Changing the data source from sqlite3 to in-memory ($n)"
@@ -129,10 +143,15 @@ echo 'Stats show
 cnt_value1=`expr $cnt_value1 + 0`
 cnt_value2=`expr $cnt_value2 + 1`
 cnt_value3=`expr $cnt_value1 + $cnt_value2`
-grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
-grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
+# Further changes of Boss(#2137) and Auth(#2138) depends on this
+# change(#2136). So statistics tests in this system test make no
+# sense. Following statistics tests are disabled until codes of both
+# #2137 and #2138 are merged.
+#grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+#grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+#if [ $status != 0 ]; then echo "I:failed"; fi
+echo "I:skipped"
 n=`expr $n + 1`
 
 echo "I:Starting more b10-auths and checking that ($n)"
@@ -163,10 +182,16 @@ do
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
     # The statistics counters should keep being consistent even while
     # multiple b10-auths are running.
-    grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
-    grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
-    grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
-    if [ $status != 0 ]; then echo "I:failed "; break ; fi
+
+    # Further changes of Boss(#2137) and Auth(#2138) depends on this
+    # change(#2136). So statistics tests in this system test make no
+    # sense. Following statistics tests are disabled until codes of both
+    # #2137 and #2138 are merged.
+    #grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+    #grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+    #grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+    #if [ $status != 0 ]; then echo "I:failed "; break ; fi
+    echo "I:skipped"
 done
 n=`expr $n + 1`