Browse Source

[master] Merge branch 'trac2222'

Naoki Kambe 12 years ago
parent
commit
91311bdbfe

+ 20 - 6
src/bin/xfrout/b10-xfrout.xml

@@ -1,6 +1,6 @@
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
-	       [<!ENTITY mdash "&#8212;">]>
+               [<!ENTITY mdash "&#8212;">]>
 <!--
  - Copyright (C) 2010-2012  Internet Systems Consortium, Inc. ("ISC")
  -
@@ -166,15 +166,15 @@
       <varlistentry>
         <term>notifyoutv4</term>
         <listitem><simpara>
-	 Number of IPv4 notifies per zone name sent out from Xfrout
-	</simpara></listitem>
+         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>
+         Number of IPv6 notifies per zone name sent out from Xfrout
+        </simpara></listitem>
       </varlistentry>
 
       <varlistentry>
@@ -187,7 +187,21 @@
       <varlistentry>
         <term>xfrreqdone</term>
         <listitem><simpara>
-	 Number of requested zone transfers per zone name completed
+         Number of requested zone transfers per zone name completed
+        </simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>ixfr_running</term>
+        <listitem><simpara>
+         Number of IXFRs in progress
+        </simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>axfr_running</term>
+        <listitem><simpara>
+         Number of AXFRs in progress
         </simpara></listitem>
       </varlistentry>
 

+ 143 - 68
src/bin/xfrout/tests/xfrout_test.py.in

@@ -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
@@ -270,6 +270,7 @@ 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,22 +279,54 @@ class TestXfroutSessionBase(unittest.TestCase):
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
                                        {},
-                                       counter_xfrrej=self._counter_xfrrej,
-                                       counter_xfrreqdone=self._counter_xfrreqdone)
+                                       **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)
         # some test replaces a module-wide function.  We should ensure the
         # original is used elsewhere.
         self.orig_get_rrset_len = xfrout.get_rrset_len
-        self._zone_name_xfrrej = None
-        self._zone_name_xfrreqdone = None
 
-    def _counter_xfrrej(self, zone_name):
-        self._zone_name_xfrrej = zone_name
+    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 _counter_xfrreqdone(self, zone_name):
-        self._zone_name_xfrreqdone = zone_name
+    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
@@ -386,6 +419,8 @@ class TestXfroutSession(TestXfroutSessionBase):
                 "action": "DROP"
             }
         ]))
+        # check the 'xfrrej' counter initially
+        self.assertEqual(self.get_counter('xfrrej'), 0)
         # Localhost (the default in this test) is accepted
         rcode, msg = self.xfrsess._parse_query_message(self.mdata)
         self.assertEqual(rcode.to_text(), "NOERROR")
@@ -399,6 +434,8 @@ class TestXfroutSession(TestXfroutSessionBase):
                                 ('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'), 1)
 
         # TSIG signed request
         request_data = self.create_request_data(with_tsig=True)
@@ -427,6 +464,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)
 
         # ACL using TSIG: no TSIG; should be rejected
         acl_setter(isc.acl.dns.REQUEST_LOADER.load([
@@ -434,6 +473,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)
 
         #
         # ACL using IP + TSIG: both should match
@@ -453,34 +494,28 @@ class TestXfroutSession(TestXfroutSessionBase):
                                 ('192.0.2.2', 12345))
         [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)
         # 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)
         # 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)
 
     def test_transfer_acl(self):
         # ACL checks only with the default ACL
         def acl_setter(acl):
             self.xfrsess._acl = acl
-        self.assertIsNone(self._zone_name_xfrrej)
-        self.check_transfer_acl(acl_setter)
-        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
-
-    def test_transfer_acl_with_nonetype_xfrrej(self):
-        # ACL checks only with the default ACL and NoneType xfrrej
-        # counter
-        def acl_setter(acl):
-            self.xfrsess._acl = acl
-        self.xfrsess._counter_xfrrej = None
-        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
-        self.assertIsNone(self._zone_name_xfrrej)
 
     def test_transfer_acl_with_notcallable_xfrrej(self):
         # ACL checks only with the default ACL and not callable xfrrej
@@ -500,9 +535,7 @@ class TestXfroutSession(TestXfroutSessionBase):
             self.xfrsess._zone_config[zone_key]['transfer_acl'] = acl
             self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
-        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
-        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
     def test_transfer_zoneacl_nomatch(self):
         # similar to the previous one, but the per zone doesn't match the
@@ -514,9 +547,7 @@ class TestXfroutSession(TestXfroutSessionBase):
                 isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
             self.xfrsess._acl = acl
-        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
-        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
     def test_get_transfer_acl(self):
         # set the default ACL.  If there's no specific zone ACL, this one
@@ -866,25 +897,11 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
             self.sock.send(b"success")
 
-        self.assertIsNone(self._zone_name_xfrreqdone)
+        self.assertEqual(self.get_counter('xfrreqdone'), 0)
         self.xfrsess._reply_xfrout_query = myreply
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.assertEqual(self.sock.readsent(), b"success")
-        self.assertEqual(self._zone_name_xfrreqdone, TEST_ZONE_NAME_STR)
-
-    def test_dns_xfrout_start_with_nonetype_xfrreqdone(self):
-        def noerror(msg, name, rrclass):
-            return Rcode.NOERROR()
-        self.xfrsess._xfrout_setup = noerror
-
-        def myreply(msg, sock):
-            self.sock.send(b"success")
-
-        self.assertIsNone(self._zone_name_xfrreqdone)
-        self.xfrsess._reply_xfrout_query = myreply
-        self.xfrsess._counter_xfrreqdone = None
-        self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
-        self.assertIsNone(self._zone_name_xfrreqdone)
+        self.assertGreater(self.get_counter('xfrreqdone'), 0)
 
     def test_dns_xfrout_start_with_notcallable_xfrreqdone(self):
         def noerror(msg, name, rrclass):
@@ -1154,10 +1171,20 @@ 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)
         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)
 
     def test_ixfr_to_axfr(self):
         self.xfrsess._request_data = \
@@ -1176,6 +1203,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)
         XfroutSession._handle(self.xfrsess)
         response = self.sock.read_msg(Message.PRESERVE_ORDER)
         actual_records = response.get_section(Message.SECTION_ANSWER)
@@ -1191,6 +1222,12 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
         self.assertEqual(len(expected_records), len(actual_records))
         for (expected_rr, actual_rr) in zip(expected_records, actual_records):
             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)
 
     def ixfr_soa_only_common_checks(self, request_serial):
         self.xfrsess._request_data = \
@@ -1578,9 +1615,9 @@ class MyXfroutServer(XfroutServer):
 
 class TestXfroutCounter(unittest.TestCase):
     def setUp(self):
-        statistics_spec = \
-            isc.config.module_spec_from_file(\
-            xfrout.SPECFILE_LOCATION).get_statistics_spec()
+        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(\
@@ -1591,22 +1628,23 @@ class TestXfroutCounter(unittest.TestCase):
         self._cycle = 10000 # number of counting per thread
 
     def test_get_default_statistics_data(self):
-        self.assertEqual(self.xfrout_counter._get_default_statistics_data(),
-                         {XfroutCounter.perzone_prefix: {
-                            XfroutCounter.entire_server: \
-                              dict([(cnt, 0) for cnt in self._counters])
-                         }})
-
-    def setup_incrementer(self, incrementer):
+        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(TEST_ZONE_NAME_STR)
+        for i in range(self._cycle): incrementer(*args)
 
-    def start_incrementer(self, incrementer):
+    def start_incrementer(self, incrementer, *args):
         threads = []
         for i in range(self._number):
             threads.append(threading.Thread(\
-                    target=self.setup_incrementer,\
-                        args=(incrementer,)\
+                    target=self.setup_incrementer, \
+                        args=(incrementer,) + args \
                         ))
         for th in threads: th.start()
         self._started.set()
@@ -1618,24 +1656,61 @@ class TestXfroutCounter(unittest.TestCase):
                 '%s/%s/%s' % (XfroutCounter.perzone_prefix,\
                                   zone_name, counter_name))
 
-    def test_incrementers(self):
+    def test_incdecrementers(self):
+        # for per-zone counters
         result = { XfroutCounter.entire_server: {},
                    TEST_ZONE_NAME_STR: {} }
         for counter_name in self._counters:
-                incrementer = getattr(self.xfrout_counter, 'inc_%s' % counter_name)
-                self.start_incrementer(incrementer)
-                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
+            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(),
-            {XfroutCounter.perzone_prefix: result})
+            statistics_data)
+        self.assertTrue(\
+            self._module_spec.validate_statistics(\
+                True, statistics_data
+                )
+            )
 
     def test_add_perzone_counter(self):
         for counter_name in self._counters:

+ 97 - 22
src/bin/xfrout/xfrout.py.in

@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# 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
@@ -154,7 +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,
-                 counter_xfrrej=None, counter_xfrreqdone=None):
+                 **counters):
         self._sock_fd = sock_fd
         self._request_data = request_data
         self._server = server
@@ -169,10 +169,13 @@ 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
-        # Set counter handlers for counting Xfr requests. An argument
-        # is required for zone name.
-        self._counter_xfrrej = counter_xfrrej
-        self._counter_xfrreqdone = counter_xfrreqdone
+        # 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)
         self._handle()
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -275,9 +278,8 @@ class XfroutSession():
                          format_zone_str(zone_name, zone_class))
             return None, None
         elif acl_result == REJECT:
-            if self._counter_xfrrej is not None:
-                # count rejected Xfr request by each zone name
-                self._counter_xfrrej(zone_name.to_text())
+            # count rejected Xfr request by each zone name
+            self._counter_xfrrej(zone_name.to_text())
             logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
                          self._request_type, format_addrinfo(self._remote),
                          format_zone_str(zone_name, zone_class))
@@ -527,15 +529,25 @@ class XfroutSession():
             return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
 
         try:
+            # increment Xfr starts by RRType
+            if self._request_type == RRType.AXFR():
+                self._inc_axfr_running()
+            else:
+                self._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:
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
                     format_addrinfo(self._remote), zone_str, err)
-        if self._counter_xfrreqdone is not None:
-            # count done Xfr requests by each zone name
-            self._counter_xfrreqdone(zone_name.to_text())
+        finally:
+            # decrement Xfr starts by RRType
+            if self._request_type == RRType.AXFR():
+                self._dec_axfr_running()
+            else:
+                self._dec_ixfr_running()
+        # count done Xfr requests by each zone name
+        self._counter_xfrreqdone(zone_name.to_text())
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
                     format_addrinfo(self._remote), zone_str)
 
@@ -948,6 +960,8 @@ class XfroutCounter:
         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
@@ -959,8 +973,15 @@ class XfroutCounter:
         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
@@ -971,9 +992,9 @@ class XfroutCounter:
         # 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 = {}
-        with self._lock:
-            zones = self._statistics_data[self.perzone_prefix].copy()
+        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: {}}
@@ -991,7 +1012,12 @@ class XfroutCounter:
             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_})
+                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):
@@ -1021,11 +1047,51 @@ class XfroutCounter:
                 with self._lock:
                     self._add_perzone_counter(zone_name)
                     self._statistics_data[self.perzone_prefix][zone_name][counter_name] += step
-            setattr(self, 'inc_%s' % item, __perzone_incrementer)
-
+            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 named_set-type counter for each zone name"""
+        """Adds a named_set-type counter for each zone name."""
         try:
             self._statistics_data[self.perzone_prefix][zone]
         except KeyError:
@@ -1041,6 +1107,17 @@ class XfroutCounter:
                                     (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
@@ -1064,8 +1141,7 @@ class XfroutServer:
             self._shutdown_event,
             self._config_data,
             self._cc,
-            counter_xfrrej=self._counter.inc_xfrrej,
-            counter_xfrreqdone=self._counter.inc_xfrreqdone
+            **self._counter.get_counters_for_xfroutsession()
             )
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
@@ -1074,8 +1150,7 @@ class XfroutServer:
         datasrc = self._unix_socket_server.get_db_file()
         self._notifier = notify_out.NotifyOut(
             datasrc,
-            counter_notifyoutv4=self._counter.inc_notifyoutv4,
-            counter_notifyoutv6=self._counter.inc_notifyoutv6
+            **self._counter.get_counters_for_notifyout()
             )
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:

+ 16 - 0
src/bin/xfrout/xfrout.spec.pre.in

@@ -172,6 +172,22 @@
               }
             ]
           }
+        },
+        {
+          "item_name": "ixfr_running",
+          "item_type": "integer",
+          "item_optional": false,
+          "item_default": 0,
+          "item_title": "IXFR running",
+          "item_description": "Number of IXFRs in progress"
+        },
+        {
+          "item_name": "axfr_running",
+          "item_type": "integer",
+          "item_optional": false,
+          "item_default": 0,
+          "item_title": "AXFR running",
+          "item_description": "Number of AXFRs in progress"
         }
       ]
   }

+ 16 - 9
tests/lettuce/features/terrain/bind10_control.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2011  Internet Systems Consortium.
+# Copyright (C) 2011-2012  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -391,19 +391,21 @@ def find_value(dictionary, key):
         for v in dictionary.values():
             return find_value(v, key)
 
-@step('The counter (\S+)(?: for the zone (\S+))? should be' + \
-          '(?:( greater than| less than))? (\d+)')
-def check_statistics(step, counter, zone, gtlt, number):
+@step('the statistics counter (\S+)(?: for the zone (\S+))? should be' + \
+          '(?:( greater than| less than| between))? (\-?\d+)(?: and (\-?\d+))?')
+def check_statistics(step, counter, zone, gtltbt, number, upper):
     """
     check the output of bindctl for statistics of specified counter
     and zone.
     Parameters:
     counter ('counter <counter>'): The counter name of statistics.
     zone ('zone <zone>', optional): The zone name.
-    gtlt (' greater than'|' less than', optional): greater than
-          <number> or less than <number>.
+    gtltbt (' greater than'|' less than'|' between', optional): greater than
+          <number> or less than <number> or between <number> and <upper>.
     number ('<number>): The expect counter number. <number> is assumed
           to be an unsigned integer.
+    upper ('<upper>, optional): The expect upper counter number when
+          using 'between'.
     """
     output = parse_bindctl_output_as_data_structure()
     found = None
@@ -416,10 +418,15 @@ def check_statistics(step, counter, zone, gtlt, number):
     assert found is not None, \
         'Not found statistics counter %s%s' % (counter, zone_str)
     msg = "Got %s, expected%s %s as counter %s%s" % \
-        (found, gtlt, number, counter, zone_str)
-    if gtlt and 'greater' in gtlt:
+        (found, gtltbt, number, counter, zone_str)
+    if gtltbt and 'between' in gtltbt and upper:
+        msg = "Got %s, expected%s %s and %s as counter %s%s" % \
+            (found, gtltbt, number, upper, counter, zone_str)
+        assert int(number) <= int(found) \
+            and int(found) <= int(upper), msg
+    elif gtltbt and 'greater' in gtltbt:
         assert int(found) > int(number), msg
-    elif gtlt and 'less' in gtlt:
+    elif gtltbt and 'less' in gtltbt:
         assert int(found) < int(number), msg
     else:
         assert int(found) == int(number), msg

+ 32 - 24
tests/lettuce/features/xfrin_notify_handling.feature

@@ -27,10 +27,18 @@ Feature: Xfrin incoming notify handling
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     last bindctl output should not contain "error"
     last bindctl output should not contain "example.org."
-    The counter notifyoutv4 for the zone _SERVER_ should be 0
-    The counter notifyoutv6 for the zone _SERVER_ should be 0
-    The counter xfrrej for the zone _SERVER_ should be 0
-    The counter xfrreqdone for the zone _SERVER_ should be 0
+    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
+    Then the statistics counter notifyoutv6 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
+
+    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 send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
@@ -56,14 +64,14 @@ Feature: Xfrin incoming notify handling
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     last bindctl output should not contain "error"
-    The counter notifyoutv4 for the zone _SERVER_ should be 0
-    The counter notifyoutv4 for the zone example.org. should be 0
-    The counter notifyoutv6 for the zone _SERVER_ should be 5
-    The counter notifyoutv6 for the zone example.org. should be 5
-    The counter xfrrej for the zone _SERVER_ should be 0
-    The counter xfrrej for the zone example.org. should be 0
-    The counter xfrreqdone for the zone _SERVER_ should be 1
-    The counter xfrreqdone for the zone example.org. should be 1
+    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 5
+    Then the statistics counter notifyoutv6 for the zone example.org. should be 5
+    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 1
+    Then the statistics counter xfrreqdone for the zone example.org. should be 1
 
     #
     # Test for Xfr request rejected
@@ -94,10 +102,10 @@ Feature: Xfrin incoming notify handling
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     last bindctl output should not contain "error"
     last bindctl output should not contain "example.org."
-    The counter notifyoutv4 for the zone _SERVER_ should be 0
-    The counter notifyoutv6 for the zone _SERVER_ should be 0
-    The counter xfrrej for the zone _SERVER_ should be 0
-    The counter xfrreqdone for the zone _SERVER_ should be 0
+    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
+    Then the statistics counter notifyoutv6 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
 
     #
     # set transfer_acl rejection
@@ -134,13 +142,13 @@ Feature: Xfrin incoming notify handling
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     last bindctl output should not contain "error"
-    The counter notifyoutv4 for the zone _SERVER_ should be 0
-    The counter notifyoutv4 for the zone example.org. should be 0
-    The counter notifyoutv6 for the zone _SERVER_ should be 5
-    The counter notifyoutv6 for the zone example.org. should be 5
+    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 5
+    Then the statistics counter notifyoutv6 for the zone example.org. should be 5
     # The counts of rejection would be between 1 and 2. They are not
     # fixed. It would depend on timing or the platform.
-    The counter xfrrej for the zone _SERVER_ should be greater than 0
-    The counter xfrrej for the zone example.org. should be greater than 0
-    The counter xfrreqdone for the zone _SERVER_ should be 0
-    The counter xfrreqdone for the zone example.org. should be 0
+    Then the statistics counter xfrrej for the zone _SERVER_ 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 example.org. should be 0