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"
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
                "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")
  - Copyright (C) 2010-2012  Internet Systems Consortium, Inc. ("ISC")
  -
  -
@@ -166,15 +166,15 @@
       <varlistentry>
       <varlistentry>
         <term>notifyoutv4</term>
         <term>notifyoutv4</term>
         <listitem><simpara>
         <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>
 
 
       <varlistentry>
       <varlistentry>
         <term>notifyoutv6</term>
         <term>notifyoutv6</term>
         <listitem><simpara>
         <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>
 
 
       <varlistentry>
       <varlistentry>
@@ -187,7 +187,21 @@
       <varlistentry>
       <varlistentry>
         <term>xfrreqdone</term>
         <term>xfrreqdone</term>
         <listitem><simpara>
         <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>
         </simpara></listitem>
       </varlistentry>
       </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
 # 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
@@ -270,6 +270,7 @@ 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,22 +279,54 @@ class TestXfroutSessionBase(unittest.TestCase):
                                        isc.acl.dns.REQUEST_LOADER.load(
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
                                            [{"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.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)
         # some test replaces a module-wide function.  We should ensure the
         # some test replaces a module-wide function.  We should ensure the
         # 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
-        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):
     def tearDown(self):
         xfrout.get_rrset_len = self.orig_get_rrset_len
         xfrout.get_rrset_len = self.orig_get_rrset_len
@@ -386,6 +419,8 @@ class TestXfroutSession(TestXfroutSessionBase):
                 "action": "DROP"
                 "action": "DROP"
             }
             }
         ]))
         ]))
+        # check the 'xfrrej' counter initially
+        self.assertEqual(self.get_counter('xfrrej'), 0)
         # 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")
@@ -399,6 +434,8 @@ class TestXfroutSession(TestXfroutSessionBase):
                                 ('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
+        self.assertEqual(self.get_counter('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)
@@ -427,6 +464,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
+        self.assertEqual(self.get_counter('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([
@@ -434,6 +473,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
+        self.assertEqual(self.get_counter('xfrrej'), 3)
 
 
         #
         #
         # ACL using IP + TSIG: both should match
         # ACL using IP + TSIG: both should match
@@ -453,34 +494,28 @@ class TestXfroutSession(TestXfroutSessionBase):
                                 ('192.0.2.2', 12345))
                                 ('192.0.2.2', 12345))
         [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
+        self.assertEqual(self.get_counter('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
+        self.assertEqual(self.get_counter('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
+        self.assertEqual(self.get_counter('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
         def acl_setter(acl):
         def acl_setter(acl):
             self.xfrsess._acl = 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.check_transfer_acl(acl_setter)
-        self.assertIsNone(self._zone_name_xfrrej)
 
 
     def test_transfer_acl_with_notcallable_xfrrej(self):
     def test_transfer_acl_with_notcallable_xfrrej(self):
         # ACL checks only with the default ACL and not callable xfrrej
         # 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._zone_config[zone_key]['transfer_acl'] = acl
             self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
             self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
                     {"from": "127.0.0.1", "action": "DROP"}])
-        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
         self.check_transfer_acl(acl_setter)
-        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
 
     def test_transfer_zoneacl_nomatch(self):
     def test_transfer_zoneacl_nomatch(self):
         # similar to the previous one, but the per zone doesn't match the
         # 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([
                 isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
                     {"from": "127.0.0.1", "action": "DROP"}])
             self.xfrsess._acl = acl
             self.xfrsess._acl = acl
-        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
         self.check_transfer_acl(acl_setter)
-        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
 
     def test_get_transfer_acl(self):
     def test_get_transfer_acl(self):
         # set the default ACL.  If there's no specific zone ACL, this one
         # set the default ACL.  If there's no specific zone ACL, this one
@@ -866,25 +897,11 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
         def myreply(msg, sock):
             self.sock.send(b"success")
             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._reply_xfrout_query = myreply
         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.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 test_dns_xfrout_start_with_notcallable_xfrreqdone(self):
         def noerror(msg, name, rrclass):
         def noerror(msg, name, rrclass):
@@ -1154,10 +1171,20 @@ 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)
         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.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):
     def test_ixfr_to_axfr(self):
         self.xfrsess._request_data = \
         self.xfrsess._request_data = \
@@ -1176,6 +1203,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)
         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)
@@ -1191,6 +1222,12 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
         self.assertEqual(len(expected_records), len(actual_records))
         self.assertEqual(len(expected_records), len(actual_records))
         for (expected_rr, actual_rr) in zip(expected_records, actual_records):
         for (expected_rr, actual_rr) in zip(expected_records, actual_records):
             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.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):
     def ixfr_soa_only_common_checks(self, request_serial):
         self.xfrsess._request_data = \
         self.xfrsess._request_data = \
@@ -1578,9 +1615,9 @@ class MyXfroutServer(XfroutServer):
 
 
 class TestXfroutCounter(unittest.TestCase):
 class TestXfroutCounter(unittest.TestCase):
     def setUp(self):
     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.xfrout_counter = XfroutCounter(statistics_spec)
         self._counters = isc.config.spec_name_list(\
         self._counters = isc.config.spec_name_list(\
             isc.config.find_spec_part(\
             isc.config.find_spec_part(\
@@ -1591,22 +1628,23 @@ class TestXfroutCounter(unittest.TestCase):
         self._cycle = 10000 # number of counting per thread
         self._cycle = 10000 # number of counting per thread
 
 
     def test_get_default_statistics_data(self):
     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()
         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 = []
         threads = []
         for i in range(self._number):
         for i in range(self._number):
             threads.append(threading.Thread(\
             threads.append(threading.Thread(\
-                    target=self.setup_incrementer,\
-                        args=(incrementer,)\
+                    target=self.setup_incrementer, \
+                        args=(incrementer,) + args \
                         ))
                         ))
         for th in threads: th.start()
         for th in threads: th.start()
         self._started.set()
         self._started.set()
@@ -1618,24 +1656,61 @@ class TestXfroutCounter(unittest.TestCase):
                 '%s/%s/%s' % (XfroutCounter.perzone_prefix,\
                 '%s/%s/%s' % (XfroutCounter.perzone_prefix,\
                                   zone_name, counter_name))
                                   zone_name, counter_name))
 
 
-    def test_incrementers(self):
+    def test_incdecrementers(self):
+        # for per-zone counters
         result = { XfroutCounter.entire_server: {},
         result = { XfroutCounter.entire_server: {},
                    TEST_ZONE_NAME_STR: {} }
                    TEST_ZONE_NAME_STR: {} }
         for counter_name in self._counters:
         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.assertEqual(
             self.xfrout_counter.get_statistics(),
             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):
     def test_add_perzone_counter(self):
         for counter_name in self._counters:
         for counter_name in self._counters:

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

@@ -1,6 +1,6 @@
 #!@PYTHON@
 #!@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
 # 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
@@ -154,7 +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,
                  default_acl, zone_config, client_class=DataSourceClient,
-                 counter_xfrrej=None, counter_xfrreqdone=None):
+                 **counters):
         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,10 +169,13 @@ 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
-        # 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()
         self._handle()
 
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -275,9 +278,8 @@ class XfroutSession():
                          format_zone_str(zone_name, zone_class))
                          format_zone_str(zone_name, zone_class))
             return None, None
             return None, None
         elif acl_result == REJECT:
         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,
             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))
@@ -527,15 +529,25 @@ class XfroutSession():
             return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
             return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
 
 
         try:
         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,
             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:
             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)
-        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,
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
                     format_addrinfo(self._remote), zone_str)
                     format_addrinfo(self._remote), zone_str)
 
 
@@ -948,6 +960,8 @@ class XfroutCounter:
         zones/example.com./notifyoutv6
         zones/example.com./notifyoutv6
         zones/example.com./xfrrej
         zones/example.com./xfrrej
         zones/example.com./xfrreqdone
         zones/example.com./xfrreqdone
+        ixfr_running
+        axfr_running
     """
     """
     # '_SERVER_' is a special zone name representing an entire
     # '_SERVER_' is a special zone name representing an entire
     # count. It doesn't mean a specific zone, but it means an
     # count. It doesn't mean a specific zone, but it means an
@@ -959,8 +973,15 @@ class XfroutCounter:
         self._statistics_spec = statistics_spec
         self._statistics_spec = statistics_spec
         # holding statistics data for Xfrout module
         # holding statistics data for Xfrout module
         self._statistics_data = {}
         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._lock = threading.RLock()
         self._create_perzone_incrementers()
         self._create_perzone_incrementers()
+        self._create_xfrrunning_incdecrementers()
 
 
     def get_statistics(self):
     def get_statistics(self):
         """Calculates an entire server counts, and returns statistics
         """Calculates an entire server counts, and returns statistics
@@ -971,9 +992,9 @@ class XfroutCounter:
         # If self._statistics_data contains nothing of zone name, it
         # If self._statistics_data contains nothing of zone name, it
         # returns an empty dict.
         # returns an empty dict.
         if len(self._statistics_data) == 0: return {}
         if len(self._statistics_data) == 0: return {}
+        # for per-zone counter
         zones = {}
         zones = {}
-        with self._lock:
-            zones = self._statistics_data[self.perzone_prefix].copy()
+        zones = self._statistics_data[self.perzone_prefix]
         # Start calculation for '_SERVER_' counts
         # Start calculation for '_SERVER_' counts
         attrs = self._get_default_statistics_data()[self.perzone_prefix][self.entire_server]
         attrs = self._get_default_statistics_data()[self.perzone_prefix][self.entire_server]
         statistics_data = {self.perzone_prefix: {}}
         statistics_data = {self.perzone_prefix: {}}
@@ -991,7 +1012,12 @@ class XfroutCounter:
             if  sum_ > 0:
             if  sum_ > 0:
                 if self.entire_server not in statistics_data[self.perzone_prefix]:
                 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] = {}
-                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
         return statistics_data
 
 
     def _get_default_statistics_data(self):
     def _get_default_statistics_data(self):
@@ -1021,11 +1047,51 @@ class XfroutCounter:
                 with self._lock:
                 with self._lock:
                     self._add_perzone_counter(zone_name)
                     self._add_perzone_counter(zone_name)
                     self._statistics_data[self.perzone_prefix][zone_name][counter_name] += step
                     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):
     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:
         try:
             self._statistics_data[self.perzone_prefix][zone]
             self._statistics_data[self.perzone_prefix][zone]
         except KeyError:
         except KeyError:
@@ -1041,6 +1107,17 @@ class XfroutCounter:
                                     (self.perzone_prefix, zone, id_),
                                     (self.perzone_prefix, zone, id_),
                                 spec['item_default'])
                                 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
@@ -1064,8 +1141,7 @@ class XfroutServer:
             self._shutdown_event,
             self._shutdown_event,
             self._config_data,
             self._config_data,
             self._cc,
             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 = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
         listener.start()
@@ -1074,8 +1150,7 @@ class XfroutServer:
         datasrc = self._unix_socket_server.get_db_file()
         datasrc = self._unix_socket_server.get_db_file()
         self._notifier = notify_out.NotifyOut(
         self._notifier = notify_out.NotifyOut(
             datasrc,
             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:
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:
             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
 # 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
@@ -391,19 +391,21 @@ def find_value(dictionary, key):
         for v in dictionary.values():
         for v in dictionary.values():
             return find_value(v, key)
             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
     check the output of bindctl for statistics of specified counter
     and zone.
     and zone.
     Parameters:
     Parameters:
     counter ('counter <counter>'): The counter name of statistics.
     counter ('counter <counter>'): The counter name of statistics.
     zone ('zone <zone>', optional): The zone name.
     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
     number ('<number>): The expect counter number. <number> is assumed
           to be an unsigned integer.
           to be an unsigned integer.
+    upper ('<upper>, optional): The expect upper counter number when
+          using 'between'.
     """
     """
     output = parse_bindctl_output_as_data_structure()
     output = parse_bindctl_output_as_data_structure()
     found = None
     found = None
@@ -416,10 +418,15 @@ def check_statistics(step, counter, zone, gtlt, number):
     assert found is not None, \
     assert found is not None, \
         'Not found statistics counter %s%s' % (counter, zone_str)
         'Not found statistics counter %s%s' % (counter, zone_str)
     msg = "Got %s, expected%s %s as counter %s%s" % \
     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
         assert int(found) > int(number), msg
-    elif gtlt and 'less' in gtlt:
+    elif gtltbt and 'less' in gtltbt:
         assert int(found) < int(number), msg
         assert int(found) < int(number), msg
     else:
     else:
         assert int(found) == int(number), msg
         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
     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 "error"
     last bindctl output should not contain "example.org."
     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
     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
@@ -56,14 +64,14 @@ Feature: Xfrin incoming notify handling
 
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     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 "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
     # 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
     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 "error"
     last bindctl output should not contain "example.org."
     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
     # 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
     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 "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
     # The counts of rejection would be between 1 and 2. They are not
     # fixed. It would depend on timing or the platform.
     # 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