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/ \
 	TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
 	TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
 	TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
 	B10_FROM_BUILD=$(abs_top_builddir) \
 	B10_FROM_BUILD=$(abs_top_builddir) \
+	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

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

@@ -48,17 +48,17 @@ DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
 
 
 isc.util.process.rename()
 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:
 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"
     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"
 SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
 AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
 AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
 
 

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

@@ -164,53 +164,154 @@
     <variablelist>
     <variablelist>
 
 
       <varlistentry>
       <varlistentry>
-        <term>notifyoutv4</term>
+        <term>zones</term>
         <listitem><simpara>
         <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>
       <varlistentry>
-        <term>xfrreqdone</term>
+        <term>ixfr_running</term>
         <listitem><simpara>
         <listitem><simpara>
-         Number of requested zone transfers per zone name completed
+          Number of IXFRs in progress
         </simpara></listitem>
         </simpara></listitem>
       </varlistentry>
       </varlistentry>
 
 
       <varlistentry>
       <varlistentry>
-        <term>ixfr_running</term>
+        <term>axfr_running</term>
         <listitem><simpara>
         <listitem><simpara>
-         Number of IXFRs in progress
+          Number of AXFRs in progress
         </simpara></listitem>
         </simpara></listitem>
       </varlistentry>
       </varlistentry>
 
 
       <varlistentry>
       <varlistentry>
-        <term>axfr_running</term>
+        <term>socket</term>
         <listitem><simpara>
         <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>
     </variablelist>
 
 
     <para>
     <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>
     </para>
 
 
   </refsect1>
   </refsect1>

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

@@ -270,7 +270,6 @@ class TestXfroutSessionBase(unittest.TestCase):
 
 
     def setUp(self):
     def setUp(self):
         self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
         self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
-        self.setup_counters()
         self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
         self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
                                        TSIGKeyRing(),
                                        TSIGKeyRing(),
                                        (socket.AF_INET, socket.SOCK_STREAM,
                                        (socket.AF_INET, socket.SOCK_STREAM,
@@ -278,8 +277,7 @@ class TestXfroutSessionBase(unittest.TestCase):
                                        # When not testing ACLs, simply accept
                                        # When not testing ACLs, simply accept
                                        isc.acl.dns.REQUEST_LOADER.load(
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
                                            [{"action": "ACCEPT"}]),
-                                       {},
-                                       **self._counters)
+                                       {})
         self.set_request_type(RRType.AXFR) # test AXFR by default
         self.set_request_type(RRType.AXFR) # test AXFR by default
         self.mdata = self.create_request_data()
         self.mdata = self.create_request_data()
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
@@ -287,52 +285,13 @@ class TestXfroutSessionBase(unittest.TestCase):
         # original is used elsewhere.
         # original is used elsewhere.
         self.orig_get_rrset_len = xfrout.get_rrset_len
         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):
     def tearDown(self):
         xfrout.get_rrset_len = self.orig_get_rrset_len
         xfrout.get_rrset_len = self.orig_get_rrset_len
         # transfer_counter must be always be reset no matter happens within
         # transfer_counter must be always be reset no matter happens within
         # the XfroutSession object.  We check the condition here.
         # the XfroutSession object.  We check the condition here.
         self.assertEqual(0, self.xfrsess._server.transfer_counter)
         self.assertEqual(0, self.xfrsess._server.transfer_counter)
+        # clear statistics counters
+        self.xfrsess._counters.clear_all()
 
 
 class TestXfroutSession(TestXfroutSessionBase):
 class TestXfroutSession(TestXfroutSessionBase):
     def test_quota_error(self):
     def test_quota_error(self):
@@ -420,7 +379,9 @@ class TestXfroutSession(TestXfroutSessionBase):
             }
             }
         ]))
         ]))
         # check the 'xfrrej' counter initially
         # 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
         # Localhost (the default in this test) is accepted
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "NOERROR")
         self.assertEqual(rcode.to_text(), "NOERROR")
@@ -435,7 +396,8 @@ class TestXfroutSession(TestXfroutSessionBase):
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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
         # TSIG signed request
         request_data = self.create_request_data(with_tsig=True)
         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)
         [rcode, msg] = self.xfrsess._parse_query_message(request_data)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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 using TSIG: no TSIG; should be rejected
         acl_setter(isc.acl.dns.REQUEST_LOADER.load([
         acl_setter(isc.acl.dns.REQUEST_LOADER.load([
@@ -474,7 +437,8 @@ class TestXfroutSession(TestXfroutSessionBase):
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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
         # ACL using IP + TSIG: both should match
@@ -495,21 +459,24 @@ class TestXfroutSession(TestXfroutSessionBase):
         [rcode, msg] = self.xfrsess._parse_query_message(request_data)
         [rcode, msg] = self.xfrsess._parse_query_message(request_data)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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)
         # Address matches, but TSIG doesn't (not included)
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
                                 ('192.0.2.1', 12345))
                                 ('192.0.2.1', 12345))
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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
         # Neither address nor TSIG matches
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
         self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
                                 ('192.0.2.2', 12345))
                                 ('192.0.2.2', 12345))
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "REFUSED")
         self.assertEqual(rcode.to_text(), "REFUSED")
         # check the 'xfrrej' counter after incrementing
         # 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):
     def test_transfer_acl(self):
         # ACL checks only with the default ACL
         # ACL checks only with the default ACL
@@ -517,14 +484,6 @@ class TestXfroutSession(TestXfroutSessionBase):
             self.xfrsess._acl = acl
             self.xfrsess._acl = acl
         self.check_transfer_acl(acl_setter)
         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):
     def test_transfer_zoneacl(self):
         # ACL check with a per zone ACL + default ACL.  The per zone ACL
         # 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.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.assertEqual(self.sock.read_msg().get_rcode(), Rcode.SERVFAIL)
         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):
         def noerror(msg, name, rrclass):
             return Rcode.NOERROR
             return Rcode.NOERROR
         self.xfrsess._xfrout_setup = noerror
         self.xfrsess._xfrout_setup = noerror
 
 
         def myreply(msg, sock):
         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.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.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):
         def noerror(msg, name, rrclass):
             return Rcode.NOERROR
             return Rcode.NOERROR
         self.xfrsess._xfrout_setup = noerror
         self.xfrsess._xfrout_setup = noerror
@@ -911,11 +874,14 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
         def myreply(msg, sock):
             self.sock.send(b"success")
             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._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):
     def test_reply_xfrout_query_axfr(self):
         self.xfrsess._soa = self.soa_rrset
         self.xfrsess._soa = self.soa_rrset
@@ -1171,20 +1137,17 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
 
 
     def test_axfr_normal_session(self):
     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)
         XfroutSession._handle(self.xfrsess)
         response = self.sock.read_msg(Message.PRESERVE_ORDER);
         response = self.sock.read_msg(Message.PRESERVE_ORDER);
         self.assertEqual(Rcode.NOERROR, response.get_rcode())
         self.assertEqual(Rcode.NOERROR, response.get_rcode())
         self.check_axfr_stream(response)
         self.check_axfr_stream(response)
         self.assertEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertNotEqual(self.xfrsess._request_type, RRType.IXFR)
         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):
     def test_ixfr_to_axfr(self):
         self.xfrsess._request_data = \
         self.xfrsess._request_data = \
@@ -1203,10 +1166,10 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
         # two beginning and trailing SOAs.
         # two beginning and trailing SOAs.
         self.xfrsess._request_data = \
         self.xfrsess._request_data = \
             self.create_request_data(ixfr=IXFR_OK_VERSION)
             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)
         XfroutSession._handle(self.xfrsess)
         response = self.sock.read_msg(Message.PRESERVE_ORDER)
         response = self.sock.read_msg(Message.PRESERVE_ORDER)
         actual_records = response.get_section(Message.SECTION_ANSWER)
         actual_records = response.get_section(Message.SECTION_ANSWER)
@@ -1224,10 +1187,9 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
             self.assertTrue(rrsets_equal(expected_rr, actual_rr))
         self.assertNotEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertNotEqual(self.xfrsess._request_type, RRType.AXFR)
         self.assertEqual(self.xfrsess._request_type, RRType.IXFR)
         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):
     def ixfr_soa_only_common_checks(self, request_serial):
         self.xfrsess._request_data = \
         self.xfrsess._request_data = \
@@ -1255,7 +1217,7 @@ class MyUnixSockServer(UnixSockServer):
         self._common_init()
         self._common_init()
         self._cc = MyCCSession()
         self._cc = MyCCSession()
         self.update_config_data(self._cc.get_full_config())
         self.update_config_data(self._cc.get_full_config())
-        self._counters = {}
+        self._counters = xfrout.Counters(xfrout.SPECFILE_LOCATION)
 
 
 class TestUnixSockServer(unittest.TestCase):
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
     def setUp(self):
@@ -1561,6 +1523,130 @@ class TestUnixSockServer(unittest.TestCase):
         self.unix._select_loop(self.__select_return_redable[0])
         self.unix._select_loop(self.__select_return_redable[0])
         self.assertEqual(1, self.__select_count)
         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):
 class TestInitialization(unittest.TestCase):
     def setEnv(self, name, value):
     def setEnv(self, name, value):
         if value is None:
         if value is None:
@@ -1609,116 +1695,7 @@ class MyXfroutServer(XfroutServer):
         self._wait_for_threads = lambda : None
         self._wait_for_threads = lambda : None
         self._cc.get_module_spec = lambda:\
         self._cc.get_module_spec = lambda:\
             isc.config.module_spec_from_file(xfrout.SPECFILE_LOCATION)
             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):
 class TestXfroutServer(unittest.TestCase):
     def setUp(self):
     def setUp(self):

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

@@ -27,6 +27,7 @@ from socketserver import *
 import os
 import os
 from isc.config.ccsession import *
 from isc.config.ccsession import *
 from isc.cc import SessionError, SessionTimeout
 from isc.cc import SessionError, SessionTimeout
+from isc.statistics import Counters
 from isc.notify import notify_out
 from isc.notify import notify_out
 import isc.util.process
 import isc.util.process
 import socket
 import socket
@@ -153,8 +154,7 @@ def get_soa_serial(soa_rdata):
 
 
 class XfroutSession():
 class XfroutSession():
     def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
     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._sock_fd = sock_fd
         self._request_data = request_data
         self._request_data = request_data
         self._server = server
         self._server = server
@@ -169,13 +169,9 @@ class XfroutSession():
         self.ClientClass = client_class # parameterize this for testing
         self.ClientClass = client_class # parameterize this for testing
         self._soa = None # will be set in _xfrout_setup or in tests
         self._soa = None # will be set in _xfrout_setup or in tests
         self._jnl_reader = None # will be set to a reader for IXFR
         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()
         self._handle()
 
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -279,7 +275,7 @@ class XfroutSession():
             return None, None
             return None, None
         elif acl_result == REJECT:
         elif acl_result == REJECT:
             # count rejected Xfr request by each zone name
             # 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,
             logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
                          self._request_type, format_addrinfo(self._remote),
                          self._request_type, format_addrinfo(self._remote),
                          format_zone_str(zone_name, zone_class))
                          format_zone_str(zone_name, zone_class))
@@ -531,23 +527,25 @@ class XfroutSession():
         try:
         try:
             # increment Xfr starts by RRType
             # increment Xfr starts by RRType
             if self._request_type == RRType.AXFR:
             if self._request_type == RRType.AXFR:
-                self._inc_axfr_running()
+                self._counters.inc('axfr_running')
             else:
             else:
-                self._inc_ixfr_running()
+                self._counters.inc('ixfr_running')
             logger.info(XFROUT_XFR_TRANSFER_STARTED, self._request_typestr,
             logger.info(XFROUT_XFR_TRANSFER_STARTED, self._request_typestr,
                         format_addrinfo(self._remote), zone_str)
                         format_addrinfo(self._remote), zone_str)
             self._reply_xfrout_query(msg, sock_fd)
             self._reply_xfrout_query(msg, sock_fd)
         except Exception as err:
         except Exception as err:
+            # count unixsockets send errors
+            self._counters.inc('socket', 'unixdomain', 'senderr')
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
                     format_addrinfo(self._remote), zone_str, err)
                     format_addrinfo(self._remote), zone_str, err)
         finally:
         finally:
             # decrement Xfr starts by RRType
             # decrement Xfr starts by RRType
             if self._request_type == RRType.AXFR:
             if self._request_type == RRType.AXFR:
-                self._dec_axfr_running()
+                self._counters.dec('axfr_running')
             else:
             else:
-                self._dec_ixfr_running()
+                self._counters.dec('ixfr_running')
         # count done Xfr requests by each zone name
         # 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,
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
                     format_addrinfo(self._remote), zone_str)
                     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.'''
     '''The unix domain socket server which accept xfr query sent from auth server.'''
 
 
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
-                 cc, **counters):
+                 cc):
         self._remove_unused_sock_file(sock_file)
         self._remove_unused_sock_file(sock_file)
         self._sock_file = sock_file
         self._sock_file = sock_file
         socketserver_mixin.NoPollMixIn.__init__(self)
         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._shutdown_event = shutdown_event
         self._write_sock, self._read_sock = socket.socketpair()
         self._write_sock, self._read_sock = socket.socketpair()
         self._common_init()
         self._common_init()
         self._cc = cc
         self._cc = cc
         self.update_config_data(config_data)
         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):
     def _common_init(self):
         '''Initialization shared with the mock server class used for tests'''
         '''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):
                 if not self.process_request(request_sock):
                     break
                     break
             except Exception as pre:
             except Exception as pre:
+                # count unixsockets receive errors
+                self._counters.inc('socket', 'unixdomain', 'recverr')
                 logger.error(XFROUT_PROCESS_REQUEST_ERROR, pre)
                 logger.error(XFROUT_PROCESS_REQUEST_ERROR, pre)
                 break
                 break
 
 
@@ -823,8 +858,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._lock.release()
         self._lock.release()
         self.RequestHandlerClass(sock_fd, request_data, self,
         self.RequestHandlerClass(sock_fd, request_data, self,
                                  isc.server_common.tsig_keyring.get_keyring(),
                                  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):
     def _remove_unused_sock_file(self, sock_file):
         '''Try to remove the socket file. If the file is being used
         '''Try to remove the socket file. If the file is being used
@@ -861,6 +895,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
     def shutdown(self):
     def shutdown(self):
         self._write_sock.send(b"shutdown") #terminate the xfrout session thread
         self._write_sock.send(b"shutdown") #terminate the xfrout session thread
         super().shutdown() # call the shutdown() of class socketserver_mixin.NoPollMixIn
         super().shutdown() # call the shutdown() of class socketserver_mixin.NoPollMixIn
+        # count closed unixsockets
+        self._counters.inc('socket', 'unixdomain', 'close')
         try:
         try:
             os.unlink(self._sock_file)
             os.unlink(self._sock_file)
         except Exception as e:
         except Exception as e:
@@ -952,172 +988,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._transfers_counter -= 1
         self._transfers_counter -= 1
         self._lock.release()
         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:
 class XfroutServer:
     def __init__(self):
     def __init__(self):
         self._unix_socket_server = None
         self._unix_socket_server = None
@@ -1125,13 +995,12 @@ class XfroutServer:
         self._shutdown_event = threading.Event()
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
         self._config_data = self._cc.get_full_config()
-        self._counter = XfroutCounter(
-            self._cc.get_module_spec().get_statistics_spec())
         self._cc.start()
         self._cc.start()
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
         self._start_xfr_query_listener()
         self._start_xfr_query_listener()
         self._start_notifier()
         self._start_notifier()
+        self._counters = Counters(SPECFILE_LOCATION)
 
 
     def _start_xfr_query_listener(self):
     def _start_xfr_query_listener(self):
         '''Start a new thread to accept xfr query. '''
         '''Start a new thread to accept xfr query. '''
@@ -1140,18 +1009,13 @@ class XfroutServer:
             XfroutSession,
             XfroutSession,
             self._shutdown_event,
             self._shutdown_event,
             self._config_data,
             self._config_data,
-            self._cc,
-            **self._counter.get_counters_for_xfroutsession()
-            )
+            self._cc)
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
         listener.start()
 
 
     def _start_notifier(self):
     def _start_notifier(self):
         datasrc = self._unix_socket_server.get_db_file()
         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:
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:
             for slave in self._config_data['also_notify']:
                 self._notifier.add_slave(slave['address'], slave['port'])
                 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
             # The log level is here set to debug in order to avoid
             # that a log becomes too verbose. Because the b10-stats
             # that a log becomes too verbose. Because the b10-stats
             # daemon is periodically asking to the b10-xfrout daemon.
             # daemon is periodically asking to the b10-xfrout daemon.
+            answer = create_answer(0, self._counters.get_statistics())
             logger.debug(DBG_XFROUT_TRACE, \
             logger.debug(DBG_XFROUT_TRACE, \
-                             XFROUT_RECEIVED_GETSTATS_COMMAND)
-            answer = create_answer(0, self._counter.get_statistics())
+                             XFROUT_RECEIVED_GETSTATS_COMMAND, \
+                             str(answer))
 
 
         else:
         else:
             answer = create_answer(1, "Unknown command:" + str(cmd))
             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_title": "Zone names",
-          "item_description": "Zone names for Xfrout statistics",
+          "item_description": "A directory name of per-zone statistics",
           "named_set_item_spec": {
           "named_set_item_spec": {
             "item_name": "zonename",
             "item_name": "zonename",
             "item_type": "map",
             "item_type": "map",
             "item_optional": false,
             "item_optional": false,
             "item_default": {},
             "item_default": {},
             "item_title": "Zone name",
             "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": [
             "map_item_spec": [
               {
               {
                 "item_name": "notifyoutv4",
                 "item_name": "notifyoutv4",
@@ -188,6 +188,110 @@
           "item_default": 0,
           "item_default": 0,
           "item_title": "AXFR running",
           "item_title": "AXFR running",
           "item_description": "Number of AXFRs in progress"
           "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
 address and port of the peer requesting the transfer, and the %3
 represents the zone name and class.
 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
 The xfrout daemon received a command on the command channel that
 statistics data should be sent to the stats daemon.
 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
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # 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
 from isc.net import addr
 import isc
 import isc
 from isc.log_messages.notify_out_messages import *
 from isc.log_messages.notify_out_messages import *
+from isc.statistics import Counters
 
 
 logger = isc.log.Logger("notify_out")
 logger = isc.log.Logger("notify_out")
 
 
@@ -127,8 +128,7 @@ class NotifyOut:
     notify message to its slaves). notify service can be started by
     notify message to its slaves). notify service can be started by
     calling  dispatcher(), and it can be stopped by calling shutdown()
     calling  dispatcher(), and it can be stopped by calling shutdown()
     in another thread. '''
     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._notify_infos = {} # key is (zone_name, zone_class)
         self._waiting_zones = []
         self._waiting_zones = []
         self._notifying_zones = []
         self._notifying_zones = []
@@ -143,10 +143,7 @@ class NotifyOut:
         # Use nonblock event to eliminate busy loop
         # Use nonblock event to eliminate busy loop
         # If there are no notifying zones, clear the event bit and wait.
         # If there are no notifying zones, clear the event bit and wait.
         self._nonblock_event = threading.Event()
         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):
     def _init_notify_out(self, datasrc_file):
         '''Get all the zones name and its notify target's address.
         '''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 = zone_notify_info.create_socket(addrinfo[0])
             sock.sendto(render.get_data(), 0, addrinfo)
             sock.sendto(render.get_data(), 0, addrinfo)
             # count notifying by IPv4 or IPv6 for statistics
             # 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],
             logger.info(NOTIFY_OUT_SENDING_NOTIFY, addrinfo[0],
                         addrinfo[1])
                         addrinfo[1])
         except (socket.error, addr.InvalidAddress) as err:
         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
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # purpose with or without fee is hereby granted, provided that the above
@@ -96,13 +96,7 @@ class TestZoneNotifyInfo(unittest.TestCase):
 class TestNotifyOut(unittest.TestCase):
 class TestNotifyOut(unittest.TestCase):
     def setUp(self):
     def setUp(self):
         self._db_file = TESTDATA_SRCDIR + '/test.sqlite3'
         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.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
         self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
         self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
         self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
         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 = self._notify._notify_infos[('example.com.', 'CH')]
         com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
         com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
 
 
+    def tearDown(self):
+        self._notify._counters.clear_all()
+
     def test_send_notify(self):
     def test_send_notify(self):
         notify_out._MAX_NOTIFY_NUM = 2
         notify_out._MAX_NOTIFY_NUM = 2
 
 
@@ -268,77 +265,67 @@ class TestNotifyOut(unittest.TestCase):
 
 
     def test_send_notify_message_udp_ipv4(self):
     def test_send_notify_message_udp_ipv4(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
         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()
         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,
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('192.0.2.1', 53))
                                                     ('192.0.2.1', 53))
         self.assertTrue(ret)
         self.assertTrue(ret)
         self.assertEqual(socket.AF_INET, example_com_info.sock_family)
         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):
     def test_send_notify_message_udp_ipv6(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
         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,
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('2001:db8::53', 53))
                                                     ('2001:db8::53', 53))
         self.assertTrue(ret)
         self.assertTrue(ret)
         self.assertEqual(socket.AF_INET6, example_com_info.sock_family)
         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):
     def test_send_notify_message_with_bogus_address(self):
         example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
         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
         # 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
         # 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
         # data source does its job, it's prudent to confirm the behavior for
         # an unexpected case.
         # 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,
         ret = self._notify._send_notify_message_udp(example_com_info,
                                                     ('invalid', 53))
                                                     ('invalid', 53))
         self.assertFalse(ret)
         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):
     def test_zone_notify_handler(self):
         old_send_msg = self._notify._send_notify_message_udp
         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
     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
-    Then the statistics counter ixfr_running should be 0
 
 
     When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
     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
-    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
     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 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 _SERVER_ should be 1
     Then the statistics counter xfrreqdone for the zone example.org. 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
     # 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 xfrrej for the zone _SERVER_ should be 0
     Then the statistics counter xfrreqdone 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
     # set transfer_acl rejection
     # Local xfr requests from Xfrin module would be rejected here.
     # 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 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 _SERVER_ should be 0
     Then the statistics counter xfrreqdone for the zone example.org. 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