Browse Source

[master] Merge branch 'trac2225_xfrout'

Naoki Kambe 12 years ago
parent
commit
6df6055468

+ 1 - 0
src/bin/xfrin/tests/Makefile.am

@@ -28,5 +28,6 @@ endif
 	TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
 	TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
 	B10_FROM_BUILD=$(abs_top_builddir) \
+	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 9 - 9
src/bin/xfrin/xfrin.py.in

@@ -48,17 +48,17 @@ DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
 
 isc.util.process.rename()
 
-# If B10_FROM_BUILD is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
+# If B10_FROM_BUILD or B10_FROM_SOURCE is set in the environment, we
+# use data files from a directory relative to that, otherwise we use
+# the ones installed on the system
+SPECFILE_PATH = "@datadir@/@PACKAGE@"\
+    .replace("${datarootdir}", "@datarootdir@")\
+    .replace("${prefix}", "@prefix@")
+AUTH_SPECFILE_PATH = SPECFILE_PATH
+if "B10_FROM_SOURCE" in os.environ:
+    SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/xfrin"
 if "B10_FROM_BUILD" in os.environ:
-    SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrin"
     AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
-else:
-    PREFIX = "@prefix@"
-    DATAROOTDIR = "@datarootdir@"
-    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    AUTH_SPECFILE_PATH = SPECFILE_PATH
 SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
 AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
 

+ 130 - 29
src/bin/xfrout/b10-xfrout.xml

@@ -164,53 +164,154 @@
     <variablelist>
 
       <varlistentry>
-        <term>notifyoutv4</term>
+        <term>zones</term>
         <listitem><simpara>
-         Number of IPv4 notifies per zone name sent out from Xfrout
-        </simpara></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>notifyoutv6</term>
-        <listitem><simpara>
-         Number of IPv6 notifies per zone name sent out from Xfrout
-        </simpara></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>xfrrej</term>
-        <listitem><simpara>
-         Number of XFR requests per zone name rejected by Xfrout
-        </simpara></listitem>
-      </varlistentry>
+          A directory name of per-zone statistics
+          </simpara>
+          <variablelist>
+
+            <varlistentry>
+              <term><replaceable>zonename</replaceable></term>
+              <listitem><simpara>
+                A actual zone name or special zone name <quote>_SERVER_</quote>
+                representing an entire server
+                </simpara>
+                <variablelist>
+
+                  <varlistentry>
+                    <term>notifyoutv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 notifies per zone name sent out from Xfrout
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>notifyoutv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 notifies per zone name sent out from Xfrout
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrrej</term>
+                    <listitem><simpara>
+                      Number of XFR requests per zone name rejected by Xfrout
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrreqdone</term>
+                    <listitem><simpara>
+                      Number of requested zone transfers per zone name completed
+                    </simpara></listitem>
+                  </varlistentry>
+
+                </variablelist>
+              </listitem>
+            </varlistentry><!-- end of zonename -->
+
+          </variablelist>
+        </listitem>
+      </varlistentry><!-- end of zones -->
 
       <varlistentry>
-        <term>xfrreqdone</term>
+        <term>ixfr_running</term>
         <listitem><simpara>
-         Number of requested zone transfers per zone name completed
+          Number of IXFRs in progress
         </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term>ixfr_running</term>
+        <term>axfr_running</term>
         <listitem><simpara>
-         Number of IXFRs in progress
+          Number of AXFRs in progress
         </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term>axfr_running</term>
+        <term>socket</term>
         <listitem><simpara>
-         Number of AXFRs in progress
-        </simpara></listitem>
-      </varlistentry>
+          A directory name of socket statistics
+          </simpara>
+          <variablelist>
+
+            <varlistentry>
+              <term>unixdomain</term>
+              <listitem><simpara>
+                A directory name of UNIX domain statistics
+                </simpara>
+                <variablelist>
+
+                  <varlistentry>
+                    <term>open</term>
+                    <listitem><simpara>
+                      UNIX domain sockets opened successfully
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>openfail</term>
+                    <listitem><simpara>
+                      UNIX domain sockets open failures
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>close</term>
+                    <listitem><simpara>
+                      UNIX domain sockets closed
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>bindfail</term>
+                    <listitem><simpara>
+                      UNIX domain sockets bind failures
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>acceptfail</term>
+                    <listitem><simpara>
+                      UNIX domain sockets incoming accept failures
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>accept</term>
+                    <listitem><simpara>
+                      UNIX domain sockets incoming connections successfully accepted
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>senderr</term>
+                    <listitem><simpara>
+                      UNIX domain sockets send errors
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>recverr</term>
+                    <listitem><simpara>
+                      UNIX domain sockets receive errors
+                    </simpara></listitem>
+                  </varlistentry>
+
+                </variablelist>
+              </listitem>
+            </varlistentry><!-- end of unixdomain -->
+
+          </variablelist>
+        </listitem>
+      </varlistentry><!-- end of socket -->
 
     </variablelist>
 
     <para>
-      In per-zone counters the special zone name '_SERVER_' exists. It doesn't
-      mean a specific zone. It represents an entire server and its value means
-      a total count of all zones.
+      In per-zone counters the special zone name <quote>_SERVER_</quote> exists.
+      It doesn't mean a specific zone. It represents an entire server and its
+      value means a total count of all zones.
     </para>
 
   </refsect1>

+ 173 - 196
src/bin/xfrout/tests/xfrout_test.py.in

@@ -270,7 +270,6 @@ class TestXfroutSessionBase(unittest.TestCase):
 
     def setUp(self):
         self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
-        self.setup_counters()
         self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
                                        TSIGKeyRing(),
                                        (socket.AF_INET, socket.SOCK_STREAM,
@@ -278,8 +277,7 @@ class TestXfroutSessionBase(unittest.TestCase):
                                        # When not testing ACLs, simply accept
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
-                                       {},
-                                       **self._counters)
+                                       {})
         self.set_request_type(RRType.AXFR) # test AXFR by default
         self.mdata = self.create_request_data()
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
@@ -287,52 +285,13 @@ class TestXfroutSessionBase(unittest.TestCase):
         # original is used elsewhere.
         self.orig_get_rrset_len = xfrout.get_rrset_len
 
-    def setup_counters(self):
-        self._statistics_data = {
-            'zones' : {
-                TEST_ZONE_NAME_STR : {
-                    'xfrrej': 0,
-                    'xfrreqdone': 0
-                    }
-                },
-            'axfr_started': 0,
-            'ixfr_started': 0,
-            'axfr_ended': 0,
-            'ixfr_ended': 0
-            }
-        def _counter_xfrrej(zone_name):
-            self._statistics_data['zones'][zone_name]['xfrrej'] += 1
-        def _counter_xfrreqdone(zone_name):
-            self._statistics_data['zones'][zone_name]['xfrreqdone'] += 1
-        def _inc_ixfr_running():
-            self._statistics_data['ixfr_started'] += 1
-        def _dec_ixfr_running():
-            self._statistics_data['ixfr_ended'] += 1
-        def _inc_axfr_running():
-            self._statistics_data['axfr_started'] += 1
-        def _dec_axfr_running():
-            self._statistics_data['axfr_ended'] += 1
-        self._counters = {
-            'counter_xfrrej': _counter_xfrrej,
-            'counter_xfrreqdone': _counter_xfrreqdone,
-            'inc_ixfr_running': _inc_ixfr_running,
-            'dec_ixfr_running': _dec_ixfr_running,
-            'inc_axfr_running': _inc_axfr_running,
-            'dec_axfr_running': _dec_axfr_running
-            }
-
-    def get_counter(self, name):
-        if name.find('ixfr_') == 0 or name.find('axfr_') == 0:
-            return self._statistics_data[name]
-        else:
-            return \
-                self._statistics_data['zones'][TEST_ZONE_NAME_STR][name]
-
     def tearDown(self):
         xfrout.get_rrset_len = self.orig_get_rrset_len
         # transfer_counter must be always be reset no matter happens within
         # the XfroutSession object.  We check the condition here.
         self.assertEqual(0, self.xfrsess._server.transfer_counter)
+        # clear statistics counters
+        self.xfrsess._counters.clear_all()
 
 class TestXfroutSession(TestXfroutSessionBase):
     def test_quota_error(self):
@@ -420,7 +379,9 @@ class TestXfroutSession(TestXfroutSessionBase):
             }
         ]))
         # check the 'xfrrej' counter initially
-        self.assertEqual(self.get_counter('xfrrej'), 0)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'zones',
+                          TEST_ZONE_NAME_STR, 'xfrrej')
         # Localhost (the default in this test) is accepted
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "NOERROR")
@@ -435,7 +396,8 @@ class TestXfroutSession(TestXfroutSessionBase):
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 1)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 1)
 
         # TSIG signed request
         request_data = self.create_request_data(with_tsig=True)
@@ -465,7 +427,8 @@ class TestXfroutSession(TestXfroutSessionBase):
         [rcode, msg] = self.xfrsess._parse_query_message(request_data)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 2)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 2)
 
         # ACL using TSIG: no TSIG; should be rejected
         acl_setter(isc.acl.dns.REQUEST_LOADER.load([
@@ -474,7 +437,8 @@ class TestXfroutSession(TestXfroutSessionBase):
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 3)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 3)
 
         #
         # ACL using IP + TSIG: both should match
@@ -495,21 +459,24 @@ class TestXfroutSession(TestXfroutSessionBase):
         [rcode, msg] = self.xfrsess._parse_query_message(request_data)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 4)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 4)
         # Address matches, but TSIG doesn't (not included)
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
                                 ('192.0.2.1', 12345))
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 5)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 5)
         # Neither address nor TSIG matches
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
                                 ('192.0.2.2', 12345))
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
-        self.assertEqual(self.get_counter('xfrrej'), 6)
+        self.assertEqual(self.xfrsess._counters.get(
+                'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 6)
 
     def test_transfer_acl(self):
         # ACL checks only with the default ACL
@@ -517,14 +484,6 @@ class TestXfroutSession(TestXfroutSessionBase):
             self.xfrsess._acl = acl
         self.check_transfer_acl(acl_setter)
 
-    def test_transfer_acl_with_notcallable_xfrrej(self):
-        # ACL checks only with the default ACL and not callable xfrrej
-        # counter
-        def acl_setter(acl):
-            self.xfrsess._acl = acl
-        self.xfrsess._counter_xfrrej = 'NOT CALLABLE'
-        self.assertRaises(TypeError,
-                          self.check_transfer_acl, acl_setter)
 
     def test_transfer_zoneacl(self):
         # ACL check with a per zone ACL + default ACL.  The per zone ACL
@@ -889,21 +848,25 @@ class TestXfroutSession(TestXfroutSessionBase):
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.assertEqual(self.sock.read_msg().get_rcode(), Rcode.SERVFAIL)
 
-    def test_dns_xfrout_start_noerror(self):
+    def test_dns_xfrout_start_unixsocketsenderr(self):
+        """This test is for a counter senderr of unixsocket."""
+
         def noerror(msg, name, rrclass):
             return Rcode.NOERROR
         self.xfrsess._xfrout_setup = noerror
 
         def myreply(msg, sock):
-            self.sock.send(b"success")
+            raise Exception
 
-        self.assertEqual(self.get_counter('xfrreqdone'), 0)
         self.xfrsess._reply_xfrout_query = myreply
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get,
+                          'socket', 'unixdomain', 'senderr')
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
-        self.assertEqual(self.sock.readsent(), b"success")
-        self.assertGreater(self.get_counter('xfrreqdone'), 0)
+        self.assertEqual(self.xfrsess._counters.get(
+                'socket', 'unixdomain', 'senderr'), 1)
 
-    def test_dns_xfrout_start_with_notcallable_xfrreqdone(self):
+    def test_dns_xfrout_start_noerror(self):
         def noerror(msg, name, rrclass):
             return Rcode.NOERROR
         self.xfrsess._xfrout_setup = noerror
@@ -911,11 +874,14 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
             self.sock.send(b"success")
 
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get,
+                          'zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
         self.xfrsess._reply_xfrout_query = myreply
-        self.xfrsess._counter_xfrreqdone = 'NOT CALLABLE'
-        self.assertRaises(TypeError,
-                          self.xfrsess.dns_xfrout_start, self.sock,
-                          self.mdata)
+        self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
+        self.assertEqual(self.sock.readsent(), b"success")
+        self.assertGreater(self.xfrsess._counters.get(
+            'zones', TEST_ZONE_NAME_STR, 'xfrreqdone'), 0)
 
     def test_reply_xfrout_query_axfr(self):
         self.xfrsess._soa = self.soa_rrset
@@ -1171,20 +1137,17 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
 
     def test_axfr_normal_session(self):
-        self.assertEqual(self.get_counter('axfr_started'), 0)
-        self.assertEqual(self.get_counter('axfr_ended'), 0)
-        self.assertEqual(self.get_counter('ixfr_started'), 0)
-        self.assertEqual(self.get_counter('ixfr_ended'), 0)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'axfr_running')
         XfroutSession._handle(self.xfrsess)
         response = self.sock.read_msg(Message.PRESERVE_ORDER);
         self.assertEqual(Rcode.NOERROR, response.get_rcode())
         self.check_axfr_stream(response)
         self.assertEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertNotEqual(self.xfrsess._request_type, RRType.IXFR)
-        self.assertEqual(self.get_counter('axfr_started'), 1)
-        self.assertEqual(self.get_counter('axfr_ended'), 1)
-        self.assertEqual(self.get_counter('ixfr_started'), 0)
-        self.assertEqual(self.get_counter('ixfr_ended'), 0)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'ixfr_running')
+        self.assertEqual(self.xfrsess._counters.get('axfr_running'), 0)
 
     def test_ixfr_to_axfr(self):
         self.xfrsess._request_data = \
@@ -1203,10 +1166,10 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
         # two beginning and trailing SOAs.
         self.xfrsess._request_data = \
             self.create_request_data(ixfr=IXFR_OK_VERSION)
-        self.assertEqual(self.get_counter('axfr_started'), 0)
-        self.assertEqual(self.get_counter('axfr_ended'), 0)
-        self.assertEqual(self.get_counter('ixfr_started'), 0)
-        self.assertEqual(self.get_counter('ixfr_ended'), 0)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'axfr_running')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'ixfr_running')
         XfroutSession._handle(self.xfrsess)
         response = self.sock.read_msg(Message.PRESERVE_ORDER)
         actual_records = response.get_section(Message.SECTION_ANSWER)
@@ -1224,10 +1187,9 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
         self.assertNotEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertEqual(self.xfrsess._request_type, RRType.IXFR)
-        self.assertEqual(self.get_counter('axfr_started'), 0)
-        self.assertEqual(self.get_counter('axfr_ended'), 0)
-        self.assertEqual(self.get_counter('ixfr_started'), 1)
-        self.assertEqual(self.get_counter('ixfr_ended'), 1)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.xfrsess._counters.get, 'axfr_running')
+        self.assertEqual(self.xfrsess._counters.get('ixfr_running'), 0)
 
     def ixfr_soa_only_common_checks(self, request_serial):
         self.xfrsess._request_data = \
@@ -1255,7 +1217,7 @@ class MyUnixSockServer(UnixSockServer):
         self._common_init()
         self._cc = MyCCSession()
         self.update_config_data(self._cc.get_full_config())
-        self._counters = {}
+        self._counters = xfrout.Counters(xfrout.SPECFILE_LOCATION)
 
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
@@ -1561,6 +1523,130 @@ class TestUnixSockServer(unittest.TestCase):
         self.unix._select_loop(self.__select_return_redable[0])
         self.assertEqual(1, self.__select_count)
 
+class DummyBaseSocketserver():
+    def shutdown(self): pass
+    def send(self, arg): pass
+    def get_request(self): pass
+
+class DummySocketserver(DummyBaseSocketserver):
+    def __init__(self, *args): pass
+
+class DummySocketserverException(DummyBaseSocketserver):
+    def __init__(self, *args): raise Exception
+
+class TestUnixSockServerForCounter(unittest.TestCase):
+
+    def setUp(self):
+        ( self.orig_remove_unused_sock_file,
+          self.orig_update_config_data,
+          self.orig_socket_socketpair,
+          self.orig_NoPollMixIn,
+          self.orig_ThreadingUnixStreamServer,
+          self.orig_process_request,
+          self.orig_select ) = \
+         ( UnixSockServer._remove_unused_sock_file,
+           UnixSockServer.update_config_data,
+           xfrout.socket.socketpair,
+           xfrout.socketserver_mixin.NoPollMixIn,
+           xfrout.ThreadingUnixStreamServer,
+           UnixSockServer.process_request,
+           xfrout.select.select )
+        UnixSockServer._remove_unused_sock_file = lambda x,y: None
+        UnixSockServer.update_config_data = lambda x,y: None
+        xfrout.socket.socketpair = \
+            lambda : (DummySocketserver(), DummySocketserver())
+        xfrout.socketserver_mixin.NoPollMixIn = DummySocketserver
+        xfrout.ThreadingUnixStreamServer = DummySocketserver
+        xfrout.super = lambda : DummySocketserver()
+        xfrout.select.select = lambda x,y,z: ([None],[None],[None])
+        self.unix = UnixSockServer(None, None, threading.Event(), None, None)
+
+    def tearDown(self):
+        ( UnixSockServer._remove_unused_sock_file,
+          UnixSockServer.update_config_data,
+          xfrout.socket.socketpair,
+          xfrout.socketserver_mixin.NoPollMixIn,
+          xfrout.ThreadingUnixStreamServer,
+          xfrout.super,
+          UnixSockServer.process_request,
+          xfrout.select.select ) = \
+          ( self.orig_remove_unused_sock_file,
+            self.orig_update_config_data,
+            self.orig_socket_socketpair,
+            self.orig_NoPollMixIn,
+            self.orig_ThreadingUnixStreamServer,
+            super,
+            self.orig_process_request,
+            self.orig_select )
+        self.unix._counters.clear_all()
+
+    def test_open(self):
+        # open
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'open'), 1)
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket', 'unixdomain', 'openfail')
+        xfrout.ThreadingUnixStreamServer = DummySocketserverException
+        try:
+            self.unix = UnixSockServer(None, None, None, None, None)
+        except Exception:
+            pass
+        else:
+            self.fail("an exception should be raised")
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'open'), 1)
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'openfail'), 1)
+
+    def test_close(self):
+        # close
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket','unixdomain', 'close')
+        self.unix.shutdown()
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'close'), 1)
+
+    def test_bindfail(self):
+        # bindfail
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket', 'unixdomain', 'bindfail')
+        self.assertRaises(Exception, self.unix.server_bind)
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'bindfail'), 1)
+
+    def test_accept(self):
+        # accept
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket', 'unixdomain', 'accept')
+        self.unix.get_request()
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'accept'), 1)
+        # acceptfail
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket', 'unixdomain', 'acceptfail')
+        xfrout.super = lambda : DummyClassException()
+        self.unix = UnixSockServer(None, None, None, None, None)
+        self.assertRaises(Exception, self.unix.get_request)
+        self.assertEqual(
+            self.unix._counters.get('socket', 'unixdomain', 'acceptfail'), 1)
+
+    def test_recverr(self):
+        # recverr
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.unix._counters.get,
+                          'socket', 'unixdomain', 'recverr')
+        def raise_socketerror(x,y):
+            raise socket.error
+        self.unix.process_request = raise_socketerror
+        self.unix._select_loop(None)
+        self.assertEqual(self.unix._counters.get(
+                'socket', 'unixdomain', 'recverr'), 1)
+
 class TestInitialization(unittest.TestCase):
     def setEnv(self, name, value):
         if value is None:
@@ -1609,116 +1695,7 @@ class MyXfroutServer(XfroutServer):
         self._wait_for_threads = lambda : None
         self._cc.get_module_spec = lambda:\
             isc.config.module_spec_from_file(xfrout.SPECFILE_LOCATION)
-        # setup an XfroutCount object
-        self._counter = XfroutCounter(
-            self._cc.get_module_spec().get_statistics_spec())
-
-class TestXfroutCounter(unittest.TestCase):
-    def setUp(self):
-        self._module_spec = isc.config.module_spec_from_file(\
-            xfrout.SPECFILE_LOCATION)
-        statistics_spec = self._module_spec.get_statistics_spec()
-        self.xfrout_counter = XfroutCounter(statistics_spec)
-        self._counters = isc.config.spec_name_list(\
-            isc.config.find_spec_part(\
-                statistics_spec, XfroutCounter.perzone_prefix)\
-                ['named_set_item_spec']['map_item_spec'])
-        self._started = threading.Event()
-        self._number = 3 # number of the threads
-        self._cycle = 10000 # number of counting per thread
-
-    def test_get_default_statistics_data(self):
-        self.assertTrue(\
-            self._module_spec.validate_statistics(\
-                True,
-                self.xfrout_counter._get_default_statistics_data(),
-                )
-            )
-
-    def setup_incrementer(self, incrementer, *args):
-        self._started.wait()
-        for i in range(self._cycle): incrementer(*args)
-
-    def start_incrementer(self, incrementer, *args):
-        threads = []
-        for i in range(self._number):
-            threads.append(threading.Thread(\
-                    target=self.setup_incrementer, \
-                        args=(incrementer,) + args \
-                        ))
-        for th in threads: th.start()
-        self._started.set()
-        for th in threads: th.join()
-
-    def get_count(self, zone_name, counter_name):
-        return isc.cc.data.find(\
-            self.xfrout_counter.get_statistics(),\
-                '%s/%s/%s' % (XfroutCounter.perzone_prefix,\
-                                  zone_name, counter_name))
-
-    def test_incdecrementers(self):
-        # for per-zone counters
-        result = { XfroutCounter.entire_server: {},
-                   TEST_ZONE_NAME_STR: {} }
-        for counter_name in self._counters:
-            cntrs_xfrss = \
-                self.xfrout_counter.get_counters_for_xfroutsession()
-            cntrs_notfy = \
-                self.xfrout_counter.get_counters_for_notifyout()
-            cnt_name = 'counter_%s' % counter_name
-            incrementer = None
-            if cnt_name in cntrs_xfrss:
-                incrementer = cntrs_xfrss[cnt_name]
-            else:
-                incrementer = cntrs_notfy[cnt_name]
-            self.start_incrementer(incrementer, TEST_ZONE_NAME_STR)
-            self.assertEqual(self.get_count(\
-                        TEST_ZONE_NAME_STR, counter_name), \
-                                 self._number * self._cycle)
-            self.assertEqual(self.get_count(\
-                    XfroutCounter.entire_server, counter_name), \
-                                 self._number * self._cycle)
-            result[XfroutCounter.entire_server][counter_name] = \
-                result[TEST_ZONE_NAME_STR][counter_name] = \
-                self._number * self._cycle
-        statistics_data = {XfroutCounter.perzone_prefix: result}
-
-        # for {a|i}xfrrunning counters
-        for counter_name in self.xfrout_counter._xfrrunning_names:
-            incrementer = \
-                dict(self.xfrout_counter.get_counters_for_xfroutsession(), \
-                         **self.xfrout_counter.get_counters_for_notifyout())\
-                         ['inc_%s' % counter_name]
-            self.start_incrementer(incrementer)
-            self.assertEqual(
-                self.xfrout_counter.get_statistics()[counter_name],
-                self._number * self._cycle
-                )
-            decrementer = \
-                dict(self.xfrout_counter.get_counters_for_xfroutsession(), \
-                         **self.xfrout_counter.get_counters_for_notifyout())\
-                         ['dec_%s' % counter_name]
-            self.start_incrementer(decrementer)
-            self.assertEqual(
-                self.xfrout_counter.get_statistics()[counter_name],
-                0)
-            statistics_data[counter_name] = 0
-        self.assertEqual(
-            self.xfrout_counter.get_statistics(),
-            statistics_data)
-        self.assertTrue(\
-            self._module_spec.validate_statistics(\
-                True, statistics_data
-                )
-            )
-
-    def test_add_perzone_counter(self):
-        for counter_name in self._counters:
-            self.assertRaises(isc.cc.data.DataNotFoundError,\
-                                  self.get_count, TEST_ZONE_NAME_STR, counter_name)
-        self.xfrout_counter._add_perzone_counter(TEST_ZONE_NAME_STR)
-        for counter_name in self._counters:
-            self.assertEqual(self.get_count(TEST_ZONE_NAME_STR, counter_name), 0)
+        self._counters = xfrout.Counters(xfrout.SPECFILE_LOCATION)
 
 class TestXfroutServer(unittest.TestCase):
     def setUp(self):

+ 63 - 198
src/bin/xfrout/xfrout.py.in

@@ -27,6 +27,7 @@ from socketserver import *
 import os
 from isc.config.ccsession import *
 from isc.cc import SessionError, SessionTimeout
+from isc.statistics import Counters
 from isc.notify import notify_out
 import isc.util.process
 import socket
@@ -153,8 +154,7 @@ def get_soa_serial(soa_rdata):
 
 class XfroutSession():
     def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
-                 default_acl, zone_config, client_class=DataSourceClient,
-                 **counters):
+                 default_acl, zone_config, client_class=DataSourceClient):
         self._sock_fd = sock_fd
         self._request_data = request_data
         self._server = server
@@ -169,13 +169,9 @@ class XfroutSession():
         self.ClientClass = client_class # parameterize this for testing
         self._soa = None # will be set in _xfrout_setup or in tests
         self._jnl_reader = None # will be set to a reader for IXFR
-        # Extract counter handler from the `counters` argument and add
-        # it to the class attribute of the name whose prefix is
-        # '_counter_' '_inc_' or '_dec_'
-        for (k, v) in counters.items():
-            if k.find('counter_') == 0 or k.find('inc_') == 0 \
-                    or k.find('dec_') == 0:
-                setattr(self, "_%s" % k, v)
+        # Creation of self.counters should be done before of
+        # invoking self._handle()
+        self._counters = Counters(SPECFILE_LOCATION)
         self._handle()
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -279,7 +275,7 @@ class XfroutSession():
             return None, None
         elif acl_result == REJECT:
             # count rejected Xfr request by each zone name
-            self._counter_xfrrej(zone_name.to_text())
+            self._counters.inc('zones', zone_name.to_text(), 'xfrrej')
             logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
                          self._request_type, format_addrinfo(self._remote),
                          format_zone_str(zone_name, zone_class))
@@ -531,23 +527,25 @@ class XfroutSession():
         try:
             # increment Xfr starts by RRType
             if self._request_type == RRType.AXFR:
-                self._inc_axfr_running()
+                self._counters.inc('axfr_running')
             else:
-                self._inc_ixfr_running()
+                self._counters.inc('ixfr_running')
             logger.info(XFROUT_XFR_TRANSFER_STARTED, self._request_typestr,
                         format_addrinfo(self._remote), zone_str)
             self._reply_xfrout_query(msg, sock_fd)
         except Exception as err:
+            # count unixsockets send errors
+            self._counters.inc('socket', 'unixdomain', 'senderr')
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
                     format_addrinfo(self._remote), zone_str, err)
         finally:
             # decrement Xfr starts by RRType
             if self._request_type == RRType.AXFR:
-                self._dec_axfr_running()
+                self._counters.dec('axfr_running')
             else:
-                self._dec_ixfr_running()
+                self._counters.dec('ixfr_running')
         # count done Xfr requests by each zone name
-        self._counter_xfrreqdone(zone_name.to_text())
+        self._counters.inc('zones', zone_name.to_text(), 'xfrreqdone')
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
                     format_addrinfo(self._remote), zone_str)
 
@@ -657,18 +655,53 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
     '''The unix domain socket server which accept xfr query sent from auth server.'''
 
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
-                 cc, **counters):
+                 cc):
         self._remove_unused_sock_file(sock_file)
         self._sock_file = sock_file
         socketserver_mixin.NoPollMixIn.__init__(self)
-        ThreadingUnixStreamServer.__init__(self, sock_file, handle_class)
+        self._counters = Counters(SPECFILE_LOCATION)
+        try:
+            ThreadingUnixStreamServer.__init__(self, sock_file, \
+                                                   handle_class)
+        except:
+            self._counters.inc('socket', 'unixdomain', 'openfail')
+            raise
+        else:
+            self._counters.inc('socket', 'unixdomain', 'open')
         self._shutdown_event = shutdown_event
         self._write_sock, self._read_sock = socket.socketpair()
         self._common_init()
         self._cc = cc
         self.update_config_data(config_data)
-        # handlers for statistics use
-        self._counters = counters
+
+    def server_bind(self):
+        """server_bind() overridden for counting unix domain sockets
+        bind() failures
+        """
+        try:
+            # call the server_bind() of class
+            # ThreadingUnixStreamServer
+            return super().server_bind()
+        except:
+            # count bind failed unixsockets
+            self._counters.inc('socket', 'unixdomain', 'bindfail')
+            raise
+
+    def get_request(self):
+        """get_request() overridden for counting unix domain sockets
+        accept() failures and success
+        """
+        try:
+            # call the get_request() of class
+            # ThreadingUnixStreamServer
+            ret = super().get_request()
+            # count successfully accepted unixsockets
+            self._counters.inc('socket', 'unixdomain', 'accept')
+            return ret
+        except:
+            # count failed accepted unixsockets
+            self._counters.inc('socket', 'unixdomain', 'acceptfail')
+            raise
 
     def _common_init(self):
         '''Initialization shared with the mock server class used for tests'''
@@ -736,6 +769,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
                 if not self.process_request(request_sock):
                     break
             except Exception as pre:
+                # count unixsockets receive errors
+                self._counters.inc('socket', 'unixdomain', 'recverr')
                 logger.error(XFROUT_PROCESS_REQUEST_ERROR, pre)
                 break
 
@@ -823,8 +858,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._lock.release()
         self.RequestHandlerClass(sock_fd, request_data, self,
                                  isc.server_common.tsig_keyring.get_keyring(),
-                                 self._guess_remote(sock_fd), acl, zone_config,
-                                 **self._counters)
+                                 self._guess_remote(sock_fd), acl, zone_config)
 
     def _remove_unused_sock_file(self, sock_file):
         '''Try to remove the socket file. If the file is being used
@@ -861,6 +895,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
     def shutdown(self):
         self._write_sock.send(b"shutdown") #terminate the xfrout session thread
         super().shutdown() # call the shutdown() of class socketserver_mixin.NoPollMixIn
+        # count closed unixsockets
+        self._counters.inc('socket', 'unixdomain', 'close')
         try:
             os.unlink(self._sock_file)
         except Exception as e:
@@ -952,172 +988,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._transfers_counter -= 1
         self._lock.release()
 
-class XfroutCounter:
-    """A class for handling all statistics counters of Xfrout.  In
-    this class, the structure of per-zone counters is assumed to be
-    like this:
-        zones/example.com./notifyoutv4
-        zones/example.com./notifyoutv6
-        zones/example.com./xfrrej
-        zones/example.com./xfrreqdone
-        ixfr_running
-        axfr_running
-    """
-    # '_SERVER_' is a special zone name representing an entire
-    # count. It doesn't mean a specific zone, but it means an
-    # entire count in the server.
-    entire_server = '_SERVER_'
-    # zone names are contained under this dirname in the spec file.
-    perzone_prefix = 'zones'
-    def __init__(self, statistics_spec):
-        self._statistics_spec = statistics_spec
-        # holding statistics data for Xfrout module
-        self._statistics_data = {}
-        self._counters_for_xfroutsession = {}
-        self._counters_for_notifyout = {}
-        self._xfrrunning_names = [
-            n for n in isc.config.spec_name_list\
-                (self._statistics_spec) \
-                if n.find('xfr_running') == 1 ]
-        self._lock = threading.RLock()
-        self._create_perzone_incrementers()
-        self._create_xfrrunning_incdecrementers()
-
-    def get_statistics(self):
-        """Calculates an entire server counts, and returns statistics
-        data format to send out the stats module including each
-        counter. If there is no counts, then it returns an empty
-        dictionary. Locks the thread because it is considered to be
-        invoked by a multi-threading caller."""
-        # If self._statistics_data contains nothing of zone name, it
-        # returns an empty dict.
-        if len(self._statistics_data) == 0: return {}
-        # for per-zone counter
-        zones = {}
-        zones = self._statistics_data[self.perzone_prefix]
-        # Start calculation for '_SERVER_' counts
-        attrs = self._get_default_statistics_data()[self.perzone_prefix][self.entire_server]
-        statistics_data = {self.perzone_prefix: {}}
-        for attr in attrs:
-            sum_ = 0
-            for name in zones:
-                if name == self.entire_server: continue
-                if attr in zones[name]:
-                    if  name not in statistics_data[self.perzone_prefix]:
-                        statistics_data[self.perzone_prefix][name] = {}
-                    statistics_data[self.perzone_prefix][name].update(
-                        {attr: zones[name][attr]}
-                        )
-                    sum_ += zones[name][attr]
-            if  sum_ > 0:
-                if self.entire_server not in statistics_data[self.perzone_prefix]:
-                    statistics_data[self.perzone_prefix][self.entire_server] = {}
-                statistics_data[self.perzone_prefix][self.entire_server]\
-                    .update({attr:sum_})
-        # for xfrrunning incrementer/decrementer
-        for name in self._xfrrunning_names:
-            if name in self._statistics_data:
-                statistics_data[name] = self._statistics_data[name]
-        return statistics_data
-
-    def _get_default_statistics_data(self):
-        """Returns default statistics data from the spec file"""
-        statistics_data = {}
-        for id_ in isc.config.spec_name_list(self._statistics_spec):
-            spec = isc.config.find_spec_part(self._statistics_spec, id_)
-            statistics_data.update({id_: spec['item_default']})
-        return statistics_data
-
-    def _create_perzone_incrementers(self):
-        """Creates increment method of each per-zone counter based on
-        the spec file. Incrementer can be accessed by name
-        "inc_${item_name}".Incrementers are passed to the
-        XfroutSession and NotifyOut class as counter handlers."""
-        # add a new element under the named_set item for the zone
-        zones_spec = isc.config.find_spec_part(
-            self._statistics_spec, self.perzone_prefix)
-        item_list =  isc.config.spec_name_list(\
-            zones_spec['named_set_item_spec']['map_item_spec'])
-        # can be accessed by the name 'inc_xxx'
-        for item in item_list:
-            def __perzone_incrementer(zone_name, counter_name=item, step=1):
-                """A per-zone incrementer for counter_name. Locks the thread
-                because it is considered to be invoked by a multi-threading
-                caller."""
-                with self._lock:
-                    self._add_perzone_counter(zone_name)
-                    self._statistics_data[self.perzone_prefix][zone_name][counter_name] += step
-            if 'notifyout' in item:
-                self._counters_for_notifyout['counter_%s' % item] \
-                    = __perzone_incrementer
-            else:
-                self._counters_for_xfroutsession['counter_%s' % item] \
-                    = __perzone_incrementer
-
-    def _create_xfrrunning_incdecrementers(self):
-        """Creates increment/decrement method of (a|i)xfr_running
-        based on the spec file. Incrementer can be accessed by name
-        "inc_${item_name}". Decrementer can be accessed by name
-        "dec_${item_name}". Both of them are passed to the
-        XfroutSession as counter handlers."""
-        # can be accessed by the name 'inc_xxx' or 'dec_xxx'
-        for item in self._xfrrunning_names:
-            def __xfrrunning_incrementer(counter_name=item, step=1):
-                """A incrementer for axfr or ixfr running. Locks the thread
-                because it is considered to be invoked by a multi-threading
-                caller."""
-                with self._lock:
-                    self._add_xfrrunning_counter(counter_name)
-                    self._statistics_data[counter_name] += step
-            def __xfrrunning_decrementer(counter_name=item, step=-1):
-                """A decrementer for axfr or ixfr running. Locks the thread
-                because it is considered to be invoked by a multi-threading
-                caller."""
-                with self._lock:
-                    self._statistics_data[counter_name] += step
-            self._counters_for_xfroutsession['inc_%s' % item] \
-                = __xfrrunning_incrementer
-            self._counters_for_xfroutsession['dec_%s' % item] \
-                = __xfrrunning_decrementer
-
-    def get_counters_for_xfroutsession(self):
-        """Returns counters, incrementers, and decrementers to be
-        passed to XfroutSession/UnixSockServer class."""
-        return self._counters_for_xfroutsession
-
-    def get_counters_for_notifyout(self):
-        """Returns counters handlers to be passed to NotifyOut
-        class."""
-        return self._counters_for_notifyout
-
-    def _add_perzone_counter(self, zone):
-        """Adds a named_set-type counter for each zone name."""
-        try:
-            self._statistics_data[self.perzone_prefix][zone]
-        except KeyError:
-            # add a new element under the named_set item for the zone
-            map_spec = isc.config.find_spec_part(
-                self._statistics_spec, '%s/%s' % \
-                    (self.perzone_prefix, zone))['map_item_spec']
-            id_list =  isc.config.spec_name_list(map_spec)
-            for id_ in id_list:
-                spec = isc.config.find_spec_part(map_spec, id_)
-                isc.cc.data.set(self._statistics_data,
-                                '%s/%s/%s' % \
-                                    (self.perzone_prefix, zone, id_),
-                                spec['item_default'])
-
-    def _add_xfrrunning_counter(self, counter_name):
-        """Adds a counter for counting (a|i)xfr_running"""
-        try:
-            self._statistics_data[counter_name]
-        except KeyError:
-            # examines the names of xfer running
-            for n in self._xfrrunning_names:
-                spec = isc.config.find_spec_part(self._statistics_spec, n)
-                isc.cc.data.set(self._statistics_data, n, \
-                                    spec['item_default'])
-
 class XfroutServer:
     def __init__(self):
         self._unix_socket_server = None
@@ -1125,13 +995,12 @@ class XfroutServer:
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
-        self._counter = XfroutCounter(
-            self._cc.get_module_spec().get_statistics_spec())
         self._cc.start()
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
         self._start_xfr_query_listener()
         self._start_notifier()
+        self._counters = Counters(SPECFILE_LOCATION)
 
     def _start_xfr_query_listener(self):
         '''Start a new thread to accept xfr query. '''
@@ -1140,18 +1009,13 @@ class XfroutServer:
             XfroutSession,
             self._shutdown_event,
             self._config_data,
-            self._cc,
-            **self._counter.get_counters_for_xfroutsession()
-            )
+            self._cc)
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
 
     def _start_notifier(self):
         datasrc = self._unix_socket_server.get_db_file()
-        self._notifier = notify_out.NotifyOut(
-            datasrc,
-            **self._counter.get_counters_for_notifyout()
-            )
+        self._notifier = notify_out.NotifyOut(datasrc)
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:
                 self._notifier.add_slave(slave['address'], slave['port'])
@@ -1232,9 +1096,10 @@ class XfroutServer:
             # The log level is here set to debug in order to avoid
             # that a log becomes too verbose. Because the b10-stats
             # daemon is periodically asking to the b10-xfrout daemon.
+            answer = create_answer(0, self._counters.get_statistics())
             logger.debug(DBG_XFROUT_TRACE, \
-                             XFROUT_RECEIVED_GETSTATS_COMMAND)
-            answer = create_answer(0, self._counter.get_statistics())
+                             XFROUT_RECEIVED_GETSTATS_COMMAND, \
+                             str(answer))
 
         else:
             answer = create_answer(1, "Unknown command:" + str(cmd))

+ 106 - 2
src/bin/xfrout/xfrout.spec.pre.in

@@ -129,14 +129,14 @@
             }
           },
           "item_title": "Zone names",
-          "item_description": "Zone names for Xfrout statistics",
+          "item_description": "A directory name of per-zone statistics",
           "named_set_item_spec": {
             "item_name": "zonename",
             "item_type": "map",
             "item_optional": false,
             "item_default": {},
             "item_title": "Zone name",
-            "item_description": "Zone name for Xfrout statistics",
+            "item_description": "A actual zone name or special zone name _SERVER_ representing an entire server",
             "map_item_spec": [
               {
                 "item_name": "notifyoutv4",
@@ -188,6 +188,110 @@
           "item_default": 0,
           "item_title": "AXFR running",
           "item_description": "Number of AXFRs in progress"
+        },
+        {
+          "item_name": "socket",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {
+            "unixdomain": {
+              "open": 0,
+              "openfail": 0,
+              "close": 0,
+              "bindfail": 0,
+              "acceptfail": 0,
+              "accept": 0,
+              "senderr": 0,
+              "recverr": 0
+            }
+          },
+          "item_title": "Socket",
+          "item_description": "A directory name of socket statistics",
+          "map_item_spec": [
+            {
+              "item_name": "unixdomain",
+              "item_type": "map",
+              "item_optional": false,
+              "item_default": {
+                "open": 0,
+                "openfail": 0,
+                "close": 0,
+                "bindfail": 0,
+                "acceptfail": 0,
+                "accept": 0,
+                "senderr": 0,
+                "recverr": 0
+              },
+              "item_title": "UNIX domain",
+              "item_description": "A directory name of UNIX domain statistics",
+              "map_item_spec": [
+                {
+                  "item_name": "open",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Open",
+                  "item_description": "UNIX domain sockets opened successfully"
+                },
+                {
+                  "item_name": "openfail",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Open failures",
+                  "item_description": "UNIX domain sockets open failures"
+                },
+                {
+                  "item_name": "close",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Close",
+                  "item_description": "UNIX domain sockets closed"
+                },
+                {
+                  "item_name": "bindfail",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Bind failures",
+                  "item_description": "UNIX domain sockets bind failures"
+                },
+                {
+                  "item_name": "acceptfail",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Accept failures",
+                  "item_description": "UNIX domain sockets incoming accept failures"
+                },
+                {
+                  "item_name": "accept",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Accept",
+                  "item_description": "UNIX domain sockets incoming connections successfully accepted"
+                },
+                {
+                  "item_name": "senderr",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Send errors",
+                  "item_description": "UNIX domain sockets send errors"
+                },
+                {
+                  "item_name": "recverr",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 0,
+                  "item_title": "Receive errors",
+                  "item_description": "UNIX domain sockets receive errors"
+                }
+              ]
+            }
+          ]
         }
       ]
   }

+ 1 - 1
src/bin/xfrout/xfrout_messages.mes

@@ -147,7 +147,7 @@ given host. This is because of ACLs.  The %2 represents the IP
 address and port of the peer requesting the transfer, and the %3
 represents the zone name and class.
 
-% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data
+% XFROUT_RECEIVED_GETSTATS_COMMAND received command to send statistics data: %1
 The xfrout daemon received a command on the command channel that
 statistics data should be sent to the stats daemon.
 

+ 10 - 15
src/lib/python/isc/notify/notify_out.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010-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
@@ -25,6 +25,7 @@ from isc.datasrc import DataSourceClient
 from isc.net import addr
 import isc
 from isc.log_messages.notify_out_messages import *
+from isc.statistics import Counters
 
 logger = isc.log.Logger("notify_out")
 
@@ -127,8 +128,7 @@ class NotifyOut:
     notify message to its slaves). notify service can be started by
     calling  dispatcher(), and it can be stopped by calling shutdown()
     in another thread. '''
-    def __init__(self, datasrc_file, counter_handler=None, verbose=True,
-                 counter_notifyoutv4=None, counter_notifyoutv6=None):
+    def __init__(self, datasrc_file, verbose=True):
         self._notify_infos = {} # key is (zone_name, zone_class)
         self._waiting_zones = []
         self._notifying_zones = []
@@ -143,10 +143,7 @@ class NotifyOut:
         # Use nonblock event to eliminate busy loop
         # If there are no notifying zones, clear the event bit and wait.
         self._nonblock_event = threading.Event()
-        # Set counter handlers for counting notifies. An argument is
-        # required for zone name.
-        self._counter_notifyoutv4 = counter_notifyoutv4
-        self._counter_notifyoutv6 = counter_notifyoutv6
+        self._counters = Counters()
 
     def _init_notify_out(self, datasrc_file):
         '''Get all the zones name and its notify target's address.
@@ -484,14 +481,12 @@ class NotifyOut:
             sock = zone_notify_info.create_socket(addrinfo[0])
             sock.sendto(render.get_data(), 0, addrinfo)
             # count notifying by IPv4 or IPv6 for statistics
-            if zone_notify_info.get_socket().family \
-                    == socket.AF_INET \
-                    and self._counter_notifyoutv4 is not None:
-                self._counter_notifyoutv4(zone_notify_info.zone_name)
-            elif zone_notify_info.get_socket().family \
-                    == socket.AF_INET6 \
-                    and self._counter_notifyoutv6 is not None:
-                self._counter_notifyoutv6(zone_notify_info.zone_name)
+            if zone_notify_info.get_socket().family == socket.AF_INET:
+                self._counters.inc('zones', zone_notify_info.zone_name,
+                                  'notifyoutv4')
+            elif zone_notify_info.get_socket().family == socket.AF_INET6:
+                self._counters.inc('zones', zone_notify_info.zone_name,
+                                  'notifyoutv6')
             logger.info(NOTIFY_OUT_SENDING_NOTIFY, addrinfo[0],
                         addrinfo[1])
         except (socket.error, addr.InvalidAddress) as err:

+ 43 - 56
src/lib/python/isc/notify/tests/notify_out_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2010-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
@@ -96,13 +96,7 @@ class TestZoneNotifyInfo(unittest.TestCase):
 class TestNotifyOut(unittest.TestCase):
     def setUp(self):
         self._db_file = TESTDATA_SRCDIR + '/test.sqlite3'
-        self._notifiedv4_zone_name = None
-        def _dummy_counter_notifyoutv4(z): self._notifiedv4_zone_name = z
-        self._notifiedv6_zone_name = None
-        def _dummy_counter_notifyoutv6(z): self._notifiedv6_zone_name = z
-        self._notify = notify_out.NotifyOut(self._db_file,
-                                            counter_notifyoutv4=_dummy_counter_notifyoutv4,
-                                            counter_notifyoutv6=_dummy_counter_notifyoutv6)
+        self._notify = notify_out.NotifyOut(self._db_file)
         self._notify._notify_infos[('example.com.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
         self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
         self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
@@ -117,6 +111,9 @@ class TestNotifyOut(unittest.TestCase):
         com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
         com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
 
+    def tearDown(self):
+        self._notify._counters.clear_all()
+
     def test_send_notify(self):
         notify_out._MAX_NOTIFY_NUM = 2
 
@@ -268,77 +265,67 @@ class TestNotifyOut(unittest.TestCase):
 
     def test_send_notify_message_udp_ipv4(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
+
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv4')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv6')
+
         example_com_info.prepare_notify_out()
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('192.0.2.1', 53))
         self.assertTrue(ret)
         self.assertEqual(socket.AF_INET, example_com_info.sock_family)
-        self.assertEqual(self._notifiedv4_zone_name, 'example.net.')
-        self.assertIsNone(self._notifiedv6_zone_name)
+        self.assertEqual(self._notify._counters.get(
+                'zones', 'example.net.', 'notifyoutv4'), 1)
+        self.assertEqual(self._notify._counters.get(
+                'zones', 'example.net.', 'notifyoutv6'), 0)
 
     def test_send_notify_message_udp_ipv6(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
+
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv4')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv6')
+
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('2001:db8::53', 53))
         self.assertTrue(ret)
         self.assertEqual(socket.AF_INET6, example_com_info.sock_family)
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertEqual(self._notifiedv6_zone_name, 'example.net.')
-
-    def test_send_notify_message_udp_ipv4_with_nonetype_notifyoutv4(self):
-        example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
-        example_com_info.prepare_notify_out()
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
-        self._notify._counter_notifyoutv4 = None
-        self._notify._send_notify_message_udp(example_com_info,
-                                              ('192.0.2.1', 53))
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
-
-    def test_send_notify_message_udp_ipv4_with_notcallable_notifyoutv4(self):
-        example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
-        example_com_info.prepare_notify_out()
-        self._notify._counter_notifyoutv4 = 'NOT CALLABLE'
-        self.assertRaises(TypeError,
-                          self._notify._send_notify_message_udp,
-                          example_com_info, ('192.0.2.1', 53))
-
-    def test_send_notify_message_udp_ipv6_with_nonetype_notifyoutv6(self):
-        example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
-        self._notify._counter_notifyoutv6 = None
-        self._notify._send_notify_message_udp(example_com_info,
-                                              ('2001:db8::53', 53))
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
-
-    def test_send_notify_message_udp_ipv6_with_notcallable_notifyoutv6(self):
-        example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
-        self._notify._counter_notifyoutv6 = 'NOT CALLABLE'
-        self.assertRaises(TypeError,
-                          self._notify._send_notify_message_udp,
-                          example_com_info, ('2001:db8::53', 53))
+        self.assertEqual(self._notify._counters.get(
+                'zones', 'example.net.', 'notifyoutv4'), 0)
+        self.assertEqual(self._notify._counters.get(
+                'zones', 'example.net.', 'notifyoutv6'), 1)
 
     def test_send_notify_message_with_bogus_address(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
 
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv4')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv6')
+
         # As long as the underlying data source validates RDATA this shouldn't
         # happen, but right now it's not actually the case.  Even if the
         # data source does its job, it's prudent to confirm the behavior for
         # an unexpected case.
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('invalid', 53))
         self.assertFalse(ret)
-        self.assertIsNone(self._notifiedv4_zone_name)
-        self.assertIsNone(self._notifiedv6_zone_name)
+
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv4')
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self._notify._counters.get,
+                          'zones', 'example.net.', 'notifyoutv4')
 
     def test_zone_notify_handler(self):
         old_send_msg = self._notify._send_notify_message_udp

+ 94 - 2
tests/lettuce/features/xfrin_notify_handling.feature

@@ -34,11 +34,19 @@ Feature: Xfrin incoming notify handling
 
     When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
     Then the statistics counter ixfr_running should be 0
-    Then the statistics counter ixfr_running should be 0
 
     When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
     Then the statistics counter axfr_running should be 0
-    Then the statistics counter axfr_running should be 0
+
+    When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter open should be between 0 and 1
+    Then the statistics counter openfail should be 0
+    Then the statistics counter close should be 0
+    Then the statistics counter bindfail should be 0
+    Then the statistics counter acceptfail should be 0
+    Then the statistics counter accept should be 0
+    Then the statistics counter senderr should be 0
+    Then the statistics counter recverr should be 0
 
     When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
@@ -73,6 +81,16 @@ Feature: Xfrin incoming notify handling
     Then the statistics counter xfrreqdone for the zone _SERVER_ should be 1
     Then the statistics counter xfrreqdone for the zone example.org. should be 1
 
+    When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter open should be 1
+    Then the statistics counter openfail should be 0
+    Then the statistics counter close should be 0
+    Then the statistics counter bindfail should be 0
+    Then the statistics counter acceptfail should be 0
+    Then the statistics counter accept should be 1
+    Then the statistics counter senderr should be 0
+    Then the statistics counter recverr should be 0
+
     #
     # Test for Xfr request rejected
     #
@@ -107,6 +125,22 @@ Feature: Xfrin incoming notify handling
     Then the statistics counter xfrrej for the zone _SERVER_ should be 0
     Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
 
+    When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter ixfr_running should be 0
+
+    When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter axfr_running should be 0
+
+    When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter open should be between 0 and 1
+    Then the statistics counter openfail should be 0
+    Then the statistics counter close should be 0
+    Then the statistics counter bindfail should be 0
+    Then the statistics counter acceptfail should be 0
+    Then the statistics counter accept should be 0
+    Then the statistics counter senderr should be 0
+    Then the statistics counter recverr should be 0
+
     #
     # set transfer_acl rejection
     # Local xfr requests from Xfrin module would be rejected here.
@@ -152,3 +186,61 @@ Feature: Xfrin incoming notify handling
     Then the statistics counter xfrrej for the zone example.org. should be greater than 0
     Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
     Then the statistics counter xfrreqdone for the zone example.org. should be 0
+
+    When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter open should be 1
+    Then the statistics counter openfail should be 0
+    Then the statistics counter close should be 0
+    Then the statistics counter bindfail should be 0
+    Then the statistics counter acceptfail should be 0
+    Then the statistics counter accept should be 1
+    Then the statistics counter senderr should be 0
+    Then the statistics counter recverr should be 0
+
+    #
+    # Test for unreachable slave
+    #
+    Scenario: Handle incoming notify (unreachable slave)
+    Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+    And wait for master stderr message STATS_STARTING
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
+    Then wait for new master stderr message NOTIFY_OUT_TIMEOUT
+
+    #
+    # Test1 for Xfrout statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+    wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    last bindctl output should not contain "error"
+    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
+    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
+    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be greater than 0
+    Then the statistics counter notifyoutv6 for the zone example.org. should be greater than 0
+    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
+    Then the statistics counter xfrrej for the zone example.org. should be 0
+    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
+    Then the statistics counter xfrreqdone for the zone example.org. should be 0
+
+    When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+    Then the statistics counter open should be 1
+    Then the statistics counter openfail should be 0
+    Then the statistics counter close should be 0
+    Then the statistics counter bindfail should be 0
+    Then the statistics counter acceptfail should be 0
+    Then the statistics counter accept should be 0
+    Then the statistics counter senderr should be 0
+    Then the statistics counter recverr should be 0