Browse Source

[917] implement stats_data2xml and modify definition of per-zone count

Naoki Kambe 13 years ago
parent
commit
1294219279

+ 1 - 7
src/bin/stats/stats-httpd-xml.tpl

@@ -15,10 +15,4 @@
  - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  - PERFORMANCE OF THIS SOFTWARE.
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 -->
-
-<stats:stats_data version="1.0"
-  xmlns:stats="$xsd_namespace"
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="$xsd_namespace $xsd_url_path">
-  $xml_string
-</stats:stats_data>
+$xml_string

+ 108 - 37
src/bin/stats/stats_httpd.py.in

@@ -29,6 +29,7 @@ import http.server
 import socket
 import socket
 import string
 import string
 import xml.etree.ElementTree
 import xml.etree.ElementTree
+import urllib.parse
 
 
 import isc.cc
 import isc.cc
 import isc.config
 import isc.config
@@ -85,14 +86,28 @@ class HttpHandler(http.server.BaseHTTPRequestHandler):
 
 
     def send_head(self):
     def send_head(self):
         try:
         try:
-            if self.path == XML_URL_PATH:
-                body = self.server.xml_handler()
-            elif self.path == XSD_URL_PATH:
-                body = self.server.xsd_handler()
-            elif self.path == XSL_URL_PATH:
-                body = self.server.xsl_handler()
+            req_path = self.path
+            req_path = urllib.parse.urlsplit(req_path).path
+            req_path = urllib.parse.unquote(req_path)
+            req_path = os.path.normpath(req_path)
+            path_dirs = req_path.split('/')
+            path_dirs = [ d for d in filter(None, path_dirs) ]
+            module_name = None
+            item_name = None
+            # in case of /bind10/statistics/xxx/YYY/zzz/
+            if len(path_dirs) >= 5:
+                item_name = path_dirs[4]
+            # in case of /bind10/statistics/xxx/YYY/
+            if len(path_dirs) >= 4:
+                module_name = path_dirs[3]
+            if req_path.startswith(XML_URL_PATH):
+                body = self.server.xml_handler(module_name, item_name)
+            elif req_path.startswith(XSD_URL_PATH):
+                body = self.server.xsd_handler(module_name, item_name)
+            elif req_path.startswith(XSL_URL_PATH):
+                body = self.server.xsl_handler(module_name, item_name)
             else:
             else:
-                if self.path == '/' and 'Host' in self.headers.keys():
+                if req_path == '/' and 'Host' in self.headers.keys():
                     # redirect to XML URL only when requested with '/'
                     # redirect to XML URL only when requested with '/'
                     self.send_response(302)
                     self.send_response(302)
                     self.send_header(
                     self.send_header(
@@ -334,12 +349,19 @@ class StatsHttpd:
             return isc.config.ccsession.create_answer(
             return isc.config.ccsession.create_answer(
                 1, "Unknown command: " + str(command))
                 1, "Unknown command: " + str(command))
 
 
-    def get_stats_data(self):
+    def get_stats_data(self, owner=None, name=None):
         """Requests statistics data to the Stats daemon and returns
         """Requests statistics data to the Stats daemon and returns
-        the data which obtains from it"""
+        the data which obtains from it. args are owner and name."""
+        param = {}
+        if owner is None and name is None:
+            param = None
+        if owner is not None:
+            param['owner'] = owner
+        if name is not None:
+            param['name'] = name
         try:
         try:
             seq = self.cc_session.group_sendmsg(
             seq = self.cc_session.group_sendmsg(
-                isc.config.ccsession.create_command('show'), 'Stats')
+                isc.config.ccsession.create_command('show', param), 'Stats')
             (answer, env) = self.cc_session.group_recvmsg(False, seq)
             (answer, env) = self.cc_session.group_recvmsg(False, seq)
             if answer:
             if answer:
                 (rcode, value) = isc.config.ccsession.parse_answer(answer)
                 (rcode, value) = isc.config.ccsession.parse_answer(answer)
@@ -353,9 +375,16 @@ class StatsHttpd:
             else:
             else:
                 raise StatsHttpdError("Stats module: %s" % str(value))
                 raise StatsHttpdError("Stats module: %s" % str(value))
 
 
-    def get_stats_spec(self):
+    def get_stats_spec(self, owner=None, name=None):
         """Requests statistics data to the Stats daemon and returns
         """Requests statistics data to the Stats daemon and returns
-        the data which obtains from it"""
+        the data which obtains from it. args are owner and name."""
+        param = {}
+        if owner is None and name is None:
+            param = None
+        if owner is not None:
+            param['owner'] = owner
+        if name is not None:
+            param['name'] = name
         try:
         try:
             seq = self.cc_session.group_sendmsg(
             seq = self.cc_session.group_sendmsg(
                 isc.config.ccsession.create_command('showschema'), 'Stats')
                 isc.config.ccsession.create_command('showschema'), 'Stats')
@@ -371,40 +400,82 @@ class StatsHttpd:
             raise StatsHttpdError("%s: %s" %
             raise StatsHttpdError("%s: %s" %
                                   (err.__class__.__name__, err))
                                   (err.__class__.__name__, err))
 
 
-    def xml_handler(self):
+    def stats_data2xml(self, stats_spec, stats_data, xml_elem):
+        """Reads stats_data and stats_spec specified as first and
+        second arguments, and modify the xml object specified as
+        fourth argument. xml_elem must be modified and always returns
+        None."""
+        # assumed started with module_spec or started with item_spec in statistics
+        if type(stats_spec) is dict:
+            # assumed started with module_spec
+            if 'item_name' not in stats_spec \
+                    and 'item_type' not in stats_spec:
+                for module_name in stats_spec.keys():
+                    elem = xml.etree.ElementTree.Element(module_name)
+                    self.stats_data2xml(stats_spec[module_name],
+                                        stats_data[module_name], elem)
+                    xml_elem.append(elem)
+            # started with item_spec in statistics
+            else:
+                if stats_spec['item_type'] == 'map':
+                    elem = xml.etree.ElementTree.Element(stats_spec['item_name'])
+                    self.stats_data2xml(stats_spec['map_item_spec'],
+                                        stats_data,
+                                        elem)
+                    xml_elem.append(elem)
+                elif stats_spec['item_type'] == 'list':
+                    elem = xml.etree.ElementTree.Element(stats_spec['item_name'])
+                    for item in stats_data:
+                        self.stats_data2xml(stats_spec['list_item_spec'],
+                                            item,
+                                            elem)
+                    xml_elem.append(elem)
+                else:
+                    elem = xml.etree.ElementTree.Element(stats_spec['item_name'])
+                    elem.text = str(stats_data)
+                    xml_elem.append(elem)
+        # assumed started with stats_spec
+        elif type(stats_spec) is list:
+            for item_spec in stats_spec:
+                #elem = xml.etree.ElementTree.Element(item_spec['item_name'])
+                self.stats_data2xml(item_spec,
+                                    stats_data[item_spec['item_name']],
+                                    xml_elem)
+                #xml_elem.append(elem)
+        else:
+            xml_elem.text = str(stats_data)
+        return None
+
+    def xml_handler(self, module_name=None, item_name=None):
         """Handler which requests to Stats daemon to obtain statistics
         """Handler which requests to Stats daemon to obtain statistics
         data and returns the body of XML document"""
         data and returns the body of XML document"""
-        xml_list=[]
-        for (mod, spec) in self.get_stats_data().items():
-            if not spec: continue
-            elem1 = xml.etree.ElementTree.Element(str(mod))
-            for (k, v) in spec.items():
-                elem2 = xml.etree.ElementTree.Element(str(k))
-                elem2.text = str(v)
-                elem1.append(elem2)
-            # The coding conversion is tricky. xml..tostring() of Python 3.2
-            # returns bytes (not string) regardless of the coding, while
-            # tostring() of Python 3.1 returns a string.  To support both
-            # cases transparently, we first make sure tostring() returns
-            # bytes by specifying utf-8 and then convert the result to a
-            # plain string (code below assume it).
-            xml_list.append(
-                str(xml.etree.ElementTree.tostring(elem1, encoding='utf-8'),
-                    encoding='us-ascii'))
-        xml_string = "".join(xml_list)
+        stats_spec = self.get_stats_spec(module_name, item_name)
+        stats_data = self.get_stats_data(module_name, item_name)
+        xml_elem = xml.etree.ElementTree.Element(
+            'bind10:statistics',
+            attrib={ 'xsi:schemaLocation' : XSD_NAMESPACE + ' ' + XSD_URL_PATH,
+                     'xmlns:bind10' : XSD_NAMESPACE,
+                     'xmlns:xsi' : "http://www.w3.org/2001/XMLSchema-instance" })
+        self.stats_data2xml(stats_spec, stats_data, xml_elem)
+        # The coding conversion is tricky. xml..tostring() of Python 3.2
+        # returns bytes (not string) regardless of the coding, while
+        # tostring() of Python 3.1 returns a string.  To support both
+        # cases transparently, we first make sure tostring() returns
+        # bytes by specifying utf-8 and then convert the result to a
+        # plain string (code below assume it).
+        xml_string = str(xml.etree.ElementTree.tostring(xml_elem, encoding='utf-8'),
+                         encoding='us-ascii')
         self.xml_body = self.open_template(XML_TEMPLATE_LOCATION).substitute(
         self.xml_body = self.open_template(XML_TEMPLATE_LOCATION).substitute(
             xml_string=xml_string,
             xml_string=xml_string,
-            xsd_namespace=XSD_NAMESPACE,
-            xsd_url_path=XSD_URL_PATH,
             xsl_url_path=XSL_URL_PATH)
             xsl_url_path=XSL_URL_PATH)
         assert self.xml_body is not None
         assert self.xml_body is not None
         return self.xml_body
         return self.xml_body
 
 
-    def xsd_handler(self):
+    def xsd_handler(self, module_name=None, item_name=None):
         """Handler which just returns the body of XSD document"""
         """Handler which just returns the body of XSD document"""
         # for XSD
         # for XSD
         xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
         xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
-        for (mod, spec) in self.get_stats_spec().items():
+        for (mod, spec) in self.get_stats_spec(module_name, item_name).items():
             if not spec: continue
             if not spec: continue
             alltag = xml.etree.ElementTree.Element("all")
             alltag = xml.etree.ElementTree.Element("all")
             for item in spec:
             for item in spec:
@@ -445,13 +516,13 @@ class StatsHttpd:
         assert self.xsd_body is not None
         assert self.xsd_body is not None
         return self.xsd_body
         return self.xsd_body
 
 
-    def xsl_handler(self):
+    def xsl_handler(self, module_name=None, item_name=None):
         """Handler which just returns the body of XSL document"""
         """Handler which just returns the body of XSL document"""
         # for XSL
         # for XSL
         xsd_root = xml.etree.ElementTree.Element(
         xsd_root = xml.etree.ElementTree.Element(
             "xsl:template",
             "xsl:template",
             dict(match="*")) # started with xml:template tag
             dict(match="*")) # started with xml:template tag
-        for (mod, spec) in self.get_stats_spec().items():
+        for (mod, spec) in self.get_stats_spec(module_name, item_name).items():
             if not spec: continue
             if not spec: continue
             for item in spec:
             for item in spec:
                 tr = xml.etree.ElementTree.Element("tr")
                 tr = xml.etree.ElementTree.Element("tr")

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

@@ -1,5 +1,6 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
 PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+PYTESTS = b10-stats-httpd_test.py
 EXTRA_DIST = $(PYTESTS) test_utils.py
 EXTRA_DIST = $(PYTESTS) test_utils.py
 CLEANFILES = test_utils.pyc msgq_socket_test
 CLEANFILES = test_utils.pyc msgq_socket_test
 
 

+ 84 - 12
src/bin/stats/tests/b10-stats-httpd_test.py

@@ -46,7 +46,7 @@ DUMMY_DATA = {
     'Auth' : {
     'Auth' : {
         "queries.tcp": 2,
         "queries.tcp": 2,
         "queries.udp": 3,
         "queries.udp": 3,
-        "queries.per-zone": [{
+        "queries.perzone": [{
                 "zonename": "test.example",
                 "zonename": "test.example",
                 "queries.tcp": 2,
                 "queries.tcp": 2,
                 "queries.udp": 3
                 "queries.udp": 3
@@ -142,6 +142,23 @@ class TestHttpHandler(unittest.TestCase):
         self.assertTrue(int(response.getheader("Content-Length")) > 0)
         self.assertTrue(int(response.getheader("Content-Length")) > 0)
         self.assertEqual(response.status, 200)
         self.assertEqual(response.status, 200)
         root = xml.etree.ElementTree.parse(response).getroot()
         root = xml.etree.ElementTree.parse(response).getroot()
+        self.assertTrue(root.tag.find('statistics') > 0)
+        for (k,v) in root.attrib.items():
+            if k.find('schemaLocation') > 0:
+                self.assertEqual(v, stats_httpd.XSD_NAMESPACE + ' ' + stats_httpd.XSD_URL_PATH)
+        for mod in DUMMY_DATA:
+            for (item, value) in DUMMY_DATA[mod].items():
+                self.assertIsNotNone(root.find(mod + '/' + item))
+
+        """
+        # URL is '/bind10/statistics/xml/Auth/queries.tcp/'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/queries.tcp/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.getheader("Content-type"), "text/xml")
+        self.assertTrue(int(response.getheader("Content-Length")) > 0)
+        self.assertEqual(response.status, 200)
+        root = xml.etree.ElementTree.parse(response).getroot()
         self.assertTrue(root.tag.find('stats_data') > 0)
         self.assertTrue(root.tag.find('stats_data') > 0)
         for (k,v) in root.attrib.items():
         for (k,v) in root.attrib.items():
             if k.find('schemaLocation') > 0:
             if k.find('schemaLocation') > 0:
@@ -149,6 +166,7 @@ class TestHttpHandler(unittest.TestCase):
         for mod in DUMMY_DATA:
         for mod in DUMMY_DATA:
             for (item, value) in DUMMY_DATA[mod].items():
             for (item, value) in DUMMY_DATA[mod].items():
                 self.assertIsNotNone(root.find(mod + '/' + item))
                 self.assertIsNotNone(root.find(mod + '/' + item))
+        """
 
 
         # URL is '/bind10/statitics/xsd'
         # URL is '/bind10/statitics/xsd'
         self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
         self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
@@ -169,6 +187,27 @@ class TestHttpHandler(unittest.TestCase):
         for elm in root.findall(xsdpath):
         for elm in root.findall(xsdpath):
             self.assertIsNotNone(elm.attrib['name'])
             self.assertIsNotNone(elm.attrib['name'])
             self.assertTrue(elm.attrib['name'] in DUMMY_DATA)
             self.assertTrue(elm.attrib['name'] in DUMMY_DATA)
+        """
+        # URL is '/bind10/statitics/xsd/Auth/queries.tcp/'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/queries.tcp/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.getheader("Content-type"), "text/xml")
+        self.assertTrue(int(response.getheader("Content-Length")) > 0)
+        self.assertEqual(response.status, 200)
+        root = xml.etree.ElementTree.parse(response).getroot()
+        url_xmlschema = '{http://www.w3.org/2001/XMLSchema}'
+        tags = [ url_xmlschema + t for t in [ 'element', 'complexType', 'all', 'element' ] ]
+        xsdpath = '/'.join(tags)
+        self.assertTrue(root.tag.find('schema') > 0)
+        self.assertTrue(hasattr(root, 'attrib'))
+        self.assertTrue('targetNamespace' in root.attrib)
+        self.assertEqual(root.attrib['targetNamespace'],
+                         stats_httpd.XSD_NAMESPACE)
+        for elm in root.findall(xsdpath):
+            self.assertIsNotNone(elm.attrib['name'])
+            self.assertTrue(elm.attrib['name'] in DUMMY_DATA)
+        """
 
 
         # URL is '/bind10/statitics/xsl'
         # URL is '/bind10/statitics/xsl'
         self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
         self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
@@ -197,6 +236,35 @@ class TestHttpHandler(unittest.TestCase):
             self.assertTrue(valueof.attrib['select'] in \
             self.assertTrue(valueof.attrib['select'] in \
                                 [ tds[0].text+'/'+item for item in DUMMY_DATA[tds[0].text].keys() ])
                                 [ tds[0].text+'/'+item for item in DUMMY_DATA[tds[0].text].keys() ])
 
 
+        """
+        # URL is '/bind10/statitics/xsl/Auth/queries.tcp/'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/queries.tcp/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.getheader("Content-type"), "text/xml")
+        self.assertTrue(int(response.getheader("Content-Length")) > 0)
+        self.assertEqual(response.status, 200)
+        root = xml.etree.ElementTree.parse(response).getroot()
+        url_trans = '{http://www.w3.org/1999/XSL/Transform}'
+        url_xhtml = '{http://www.w3.org/1999/xhtml}'
+        xslpath = url_trans + 'template/' + url_xhtml + 'tr'
+        self.assertEqual(root.tag, url_trans + 'stylesheet')
+        for tr in root.findall(xslpath):
+            tds = tr.findall(url_xhtml + 'td')
+            self.assertIsNotNone(tds)
+            self.assertEqual(type(tds), list)
+            self.assertTrue(len(tds) > 2)
+            self.assertTrue(hasattr(tds[0], 'text'))
+            self.assertTrue(tds[0].text in DUMMY_DATA)
+            valueof = tds[2].find(url_trans + 'value-of')
+            self.assertIsNotNone(valueof)
+            self.assertTrue(hasattr(valueof, 'attrib'))
+            self.assertIsNotNone(valueof.attrib)
+            self.assertTrue('select' in valueof.attrib)
+            self.assertTrue(valueof.attrib['select'] in \
+                                [ tds[0].text+'/'+item for item in DUMMY_DATA[tds[0].text].keys() ])
+        """
+
         # 302 redirect
         # 302 redirect
         self.client._http_vsn_str = 'HTTP/1.1'
         self.client._http_vsn_str = 'HTTP/1.1'
         self.client.putrequest('GET', '/')
         self.client.putrequest('GET', '/')
@@ -493,8 +561,6 @@ class TestStatsHttpd(unittest.TestCase):
         self.assertTrue(isinstance(tmpl, string.Template))
         self.assertTrue(isinstance(tmpl, string.Template))
         opts = dict(
         opts = dict(
             xml_string="<dummy></dummy>",
             xml_string="<dummy></dummy>",
-            xsd_namespace="http://host/path/to/",
-            xsd_url_path="/path/to/",
             xsl_url_path="/path/to/")
             xsl_url_path="/path/to/")
         lines = tmpl.substitute(opts)
         lines = tmpl.substitute(opts)
         for n in opts:
         for n in opts:
@@ -585,26 +651,32 @@ class TestStatsHttpd(unittest.TestCase):
 
 
     def test_xml_handler(self):
     def test_xml_handler(self):
         self.stats_httpd = MyStatsHttpd(get_availaddr())
         self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.stats_httpd.get_stats_data = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
+            { 'Dummy' : [ {
+                'item_name' : 'foo',
+                'item_type' : 'string' } ] }
+        self.stats_httpd.get_stats_data = lambda x,y: \
             { 'Dummy' : { 'foo':'bar' } }
             { 'Dummy' : { 'foo':'bar' } }
         xml_body1 = self.stats_httpd.open_template(
         xml_body1 = self.stats_httpd.open_template(
             stats_httpd.XML_TEMPLATE_LOCATION).substitute(
             stats_httpd.XML_TEMPLATE_LOCATION).substitute(
-            xml_string='<Dummy><foo>bar</foo></Dummy>',
-            xsd_namespace=stats_httpd.XSD_NAMESPACE,
-            xsd_url_path=stats_httpd.XSD_URL_PATH,
+            xml_string='<bind10:statistics xmlns:bind10="http://bind10.isc.org/bind10/statistics/xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://bind10.isc.org/bind10/statistics/xsd /bind10/statistics/xsd"><Dummy><foo>bar</foo></Dummy></bind10:statistics>',
             xsl_url_path=stats_httpd.XSL_URL_PATH)
             xsl_url_path=stats_httpd.XSL_URL_PATH)
         xml_body2 = self.stats_httpd.xml_handler()
         xml_body2 = self.stats_httpd.xml_handler()
         self.assertEqual(type(xml_body1), str)
         self.assertEqual(type(xml_body1), str)
         self.assertEqual(type(xml_body2), str)
         self.assertEqual(type(xml_body2), str)
         self.assertEqual(xml_body1, xml_body2)
         self.assertEqual(xml_body1, xml_body2)
-        self.stats_httpd.get_stats_data = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
+            { 'Dummy' : [ {
+                'item_name' : 'bar',
+                'item_type' : 'string' } ] }
+        self.stats_httpd.get_stats_data = lambda x,y: \
             { 'Dummy' : {'bar':'foo'} }
             { 'Dummy' : {'bar':'foo'} }
         xml_body2 = self.stats_httpd.xml_handler()
         xml_body2 = self.stats_httpd.xml_handler()
         self.assertNotEqual(xml_body1, xml_body2)
         self.assertNotEqual(xml_body1, xml_body2)
 
 
     def test_xsd_handler(self):
     def test_xsd_handler(self):
         self.stats_httpd = MyStatsHttpd(get_availaddr())
         self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.stats_httpd.get_stats_spec = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
             { "Dummy" :
             { "Dummy" :
                   [{
                   [{
                         "item_name": "foo",
                         "item_name": "foo",
@@ -629,7 +701,7 @@ class TestStatsHttpd(unittest.TestCase):
         self.assertEqual(type(xsd_body1), str)
         self.assertEqual(type(xsd_body1), str)
         self.assertEqual(type(xsd_body2), str)
         self.assertEqual(type(xsd_body2), str)
         self.assertEqual(xsd_body1, xsd_body2)
         self.assertEqual(xsd_body1, xsd_body2)
-        self.stats_httpd.get_stats_spec = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
             { "Dummy" :
             { "Dummy" :
                   [{
                   [{
                         "item_name": "bar",
                         "item_name": "bar",
@@ -645,7 +717,7 @@ class TestStatsHttpd(unittest.TestCase):
 
 
     def test_xsl_handler(self):
     def test_xsl_handler(self):
         self.stats_httpd = MyStatsHttpd(get_availaddr())
         self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.stats_httpd.get_stats_spec = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
             { "Dummy" :
             { "Dummy" :
                   [{
                   [{
                         "item_name": "foo",
                         "item_name": "foo",
@@ -668,7 +740,7 @@ class TestStatsHttpd(unittest.TestCase):
         self.assertEqual(type(xsl_body1), str)
         self.assertEqual(type(xsl_body1), str)
         self.assertEqual(type(xsl_body2), str)
         self.assertEqual(type(xsl_body2), str)
         self.assertEqual(xsl_body1, xsl_body2)
         self.assertEqual(xsl_body1, xsl_body2)
-        self.stats_httpd.get_stats_spec = lambda: \
+        self.stats_httpd.get_stats_spec = lambda x,y: \
             { "Dummy" :
             { "Dummy" :
                   [{
                   [{
                         "item_name": "bar",
                         "item_name": "bar",

+ 15 - 4
src/bin/stats/tests/test_utils.py

@@ -234,14 +234,25 @@ class MockAuth:
         "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
         "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
       },
       },
       {
       {
-        "item_name": "queries.per-zone",
+        "item_name": "queries.perzone",
         "item_type": "list",
         "item_type": "list",
         "item_optional": false,
         "item_optional": false,
-        "item_default": [],
+        "item_default": [
+          {
+            "zonename" : "test1.example",
+            "queries.udp" : 1,
+            "queries.tcp" : 2
+          },
+          {
+            "zonename" : "test2.example",
+            "queries.udp" : 3,
+            "queries.tcp" : 4
+          }
+        ],
         "item_title": "Queries per zone",
         "item_title": "Queries per zone",
         "item_description": "Queries per zone",
         "item_description": "Queries per zone",
         "list_item_spec": {
         "list_item_spec": {
-          "item_name": "item",
+          "item_name": "zones",
           "item_type": "map",
           "item_type": "map",
           "item_optional": false,
           "item_optional": false,
           "item_default": {},
           "item_default": {},
@@ -290,7 +301,7 @@ class MockAuth:
         self.queries_tcp = 3
         self.queries_tcp = 3
         self.queries_udp = 2
         self.queries_udp = 2
         self.queries_per_zone = [{
         self.queries_per_zone = [{
-                'zonename': 'test.example',
+                'zonename': 'test1.example',
                 'queries.tcp': 5,
                 'queries.tcp': 5,
                 'queries.udp': 4
                 'queries.udp': 4
                 }]
                 }]