b10-stats-httpd_test.py 62 KB
Newer Older
Naoki Kambe's avatar
Naoki Kambe committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Copyright (C) 2011  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
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

16
"""
17
18
19
20
21
In each of these tests we start several virtual components. They are
not the real components, no external processes are started. They are
just simple mock objects running each in its own thread and pretending
to be bind10 modules. This helps testing the stats http server in a
close to real environment.
22
23
"""

Naoki Kambe's avatar
Naoki Kambe committed
24
25
26
import unittest
import os
import imp
27
28
29
30
31
32
33
34
import socket
import errno
import select
import string
import time
import threading
import http.client
import xml.etree.ElementTree
35
import random
Naoki Kambe's avatar
Naoki Kambe committed
36

37
import isc
Naoki Kambe's avatar
Naoki Kambe committed
38
import stats_httpd
39
import stats
40
from test_utils import BaseModules, ThreadingServerManager, MyStats, MyStatsHttpd, SignalHandler, send_command, send_shutdown
Naoki Kambe's avatar
Naoki Kambe committed
41
42

DUMMY_DATA = {
43
44
45
46
47
    'Boss' : {
        "boot_time": "2011-03-04T11:59:06Z"
        },
    'Auth' : {
        "queries.tcp": 2,
48
        "queries.udp": 3,
49
        "queries.perzone": [{
50
51
52
53
                "zonename": "test.example",
                "queries.tcp": 2,
                "queries.udp": 3
                }]
54
55
56
57
58
59
60
61
        },
    'Stats' : {
        "report_time": "2011-03-04T11:59:19Z",
        "boot_time": "2011-03-04T11:59:06Z",
        "last_update_time": "2011-03-04T11:59:07Z",
        "lname": "4d70d40a_c@host",
        "timestamp": 1299239959.560846
        }
Naoki Kambe's avatar
Naoki Kambe committed
62
63
    }

64
def get_availaddr(address='127.0.0.1', port=8001):
65
66
67
68
69
70
71
72
    """returns a tuple of address and port which is available to
    listen on the platform. The first argument is a address for
    search. The second argument is a port for search. If a set of
    address and port is failed on the search for the availability, the
    port number is increased and it goes on the next trial until the
    available set of address and port is looked up. If the port number
    reaches over 65535, it may stop the search and raise a
    OverflowError exception."""
73
74
75
76
77
    while True:
        for addr in socket.getaddrinfo(
            address, port, 0,
            socket.SOCK_STREAM, socket.IPPROTO_TCP):
            sock = socket.socket(addr[0], socket.SOCK_STREAM)
78
79
80
81
            try:
                sock.bind((address, port))
                return (address, port)
            except socket.error:
82
83
84
85
86
87
                continue
            finally:
                if sock: sock.close()
        # This address and port number are already in use.
        # next port number is added
        port = port + 1
88

89
90
91
def is_ipv6_enabled(address='::1', port=8001):
    """checks IPv6 enabled on the platform. address for check is '::1'
    and port for check is random number between 8001 and
92
93
94
95
    65535. Retrying is 3 times even if it fails. The built-in socket
    module provides a 'has_ipv6' parameter, but it's not used here
    because there may be a situation where the value is True on an
    environment where the IPv6 config is disabled."""
96
97
98
99
100
101
102
103
104
    for p in random.sample(range(port, 65535), 3):
        try:
            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
            sock.bind((address, p))
            return True
        except socket.error:
            continue
        finally:
            if sock: sock.close()
Naoki Kambe's avatar
Naoki Kambe committed
105
    return False
106

Naoki Kambe's avatar
Naoki Kambe committed
107
108
109
class TestHttpHandler(unittest.TestCase):
    """Tests for HttpHandler class"""
    def setUp(self):
110
111
        # set the signal handler for deadlock
        self.sig_handler = SignalHandler(self.fail)
112
113
114
115
        self.base = BaseModules()
        self.stats_server = ThreadingServerManager(MyStats)
        self.stats = self.stats_server.server
        self.stats_server.run()
116
117
118
119
120
121
122
        (self.address, self.port) = get_availaddr()
        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
        self.stats_httpd = self.stats_httpd_server.server
        self.stats_httpd_server.run()
        self.client = http.client.HTTPConnection(self.address, self.port)
        self.client._http_vsn_str = 'HTTP/1.0\n'
        self.client.connect()
Naoki Kambe's avatar
Naoki Kambe committed
123

124
    def tearDown(self):
125
126
        self.client.close()
        self.stats_httpd_server.shutdown()
127
128
        self.stats_server.shutdown()
        self.base.shutdown()
129
130
        # reset the signal handler
        self.sig_handler.reset()
131

132
133
    def test_do_GET(self):
        self.assertTrue(type(self.stats_httpd.httpd) is list)
134
135
        self.assertEqual(len(self.stats_httpd.httpd), 1)
        self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
Naoki Kambe's avatar
Naoki Kambe committed
136

137
138
139
140
141
142
143
144
145
146
147
148
        def check_XML_URL_PATH(mod=None, item=None):
            url_path = stats_httpd.XML_URL_PATH
            if mod is not None:
                url_path = url_path + '/' + mod
                if item is not None:
                    url_path = url_path + '/' + item
            self.client.putrequest('GET', url_path)
            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)
149
150
151
152
            xml_doctype = response.readline().decode()
            xsl_doctype = response.readline().decode()
            self.assertTrue(len(xml_doctype) > 0)
            self.assertTrue(len(xsl_doctype) > 0)
153
154
            root = xml.etree.ElementTree.parse(response).getroot()
            self.assertTrue(root.tag.find('statistics') > 0)
155
            schema_loc = '{http://www.w3.org/2001/XMLSchema-instance}schemaLocation'
156
            if item is None and mod is None:
157
158
159
160
161
162
163
164
165
                # check the path of XSD
                self.assertEqual(root.attrib[schema_loc],
                                 stats_httpd.XSD_NAMESPACE + ' '
                                 + stats_httpd.XSD_URL_PATH)
                # check the path of XSL
                self.assertTrue(xsl_doctype.startswith(
                        '<?xml-stylesheet type="text/xsl" href="' + 
                        stats_httpd.XSL_URL_PATH
                        + '"?>'))
166
167
168
                for m in DUMMY_DATA:
                    for k in DUMMY_DATA[m].keys():
                        self.assertIsNotNone(root.find(m + '/' + k))
169
170
171
172
173
                        itm = root.find(m + '/' + k)
                        if type(DUMMY_DATA[m][k]) is list:
                            for v in DUMMY_DATA[m][k]:
                                for i in v:
                                    self.assertIsNotNone(itm.find('zones/' + i))
174
            elif item is None:
175
176
177
178
179
180
181
182
183
                # check the path of XSD
                self.assertEqual(root.attrib[schema_loc],
                                 stats_httpd.XSD_NAMESPACE + ' '
                                 + stats_httpd.XSD_URL_PATH + '/' + mod)
                # check the path of XSL
                self.assertTrue(xsl_doctype.startswith( 
                                 '<?xml-stylesheet type="text/xsl" href="'
                                 + stats_httpd.XSL_URL_PATH + '/' + mod
                                 + '"?>'))
184
                for k in DUMMY_DATA[mod].keys():
185
186
187
                    self.assertIsNotNone(root.find(mod + '/' + k))
                    itm = root.find(mod + '/' + k)
                    self.assertIsNotNone(itm)
188
189
190
191
                    if type(DUMMY_DATA[mod][k]) is list:
                        for v in DUMMY_DATA[mod][k]:
                            for i in v:
                                self.assertIsNotNone(itm.find('zones/' + i))
192
            else:
193
194
195
196
197
198
199
200
201
                # check the path of XSD
                self.assertEqual(root.attrib[schema_loc],
                                 stats_httpd.XSD_NAMESPACE + ' '
                                 + stats_httpd.XSD_URL_PATH + '/' + mod + '/' + item)
                # check the path of XSL
                self.assertTrue(xsl_doctype.startswith( 
                                 '<?xml-stylesheet type="text/xsl" href="'
                                 + stats_httpd.XSL_URL_PATH + '/' + mod + '/' + item
                                 + '"?>'))
202
                self.assertIsNotNone(root.find(mod + '/' + item))
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        # URL is '/bind10/statistics/xml'
        check_XML_URL_PATH(mod=None, item=None)
        for m in DUMMY_DATA:
            # URL is '/bind10/statistics/xml/Module'
            check_XML_URL_PATH(mod=m)
            for k in DUMMY_DATA[m].keys():
                # URL is '/bind10/statistics/xml/Module/Item'
                check_XML_URL_PATH(mod=m, item=k)

        def check_XSD_URL_PATH(mod=None, item=None):
            url_path = stats_httpd.XSD_URL_PATH
            if mod is not None:
                url_path = url_path + '/' + mod
                if item is not None:
                    url_path = url_path + '/' + item
            self.client.putrequest('GET', url_path)
            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}'
            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)
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
            if mod is None and item is None:
                for (mod, itm) in DUMMY_DATA.items():
                    xsdpath = '/'.join([ url_xmlschema + t for t in [ 'element', 'complexType', 'all', 'element' ] ])
                    mod_elm = dict([ (elm.attrib['name'], elm) for elm in root.findall(xsdpath) ])
                    self.assertTrue(mod in mod_elm)
                    for (it, val) in itm.items():
                        xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                        itm_elm = dict([ (elm.attrib['name'], elm) for elm in mod_elm[mod].findall(xsdpath) ])
                        self.assertTrue(it in itm_elm)
                        if type(val) is list:
                            xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'sequence', 'element' ] ])
                            itm_elm2 = dict([ (elm.attrib['name'], elm) for elm in itm_elm[it].findall(xsdpath) ])
                            self.assertTrue('zones' in itm_elm2)
                            for i in val:
                                for k in i.keys():
                                    xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                                    self.assertTrue(
                                        k in [ elm.attrib['name'] for elm in itm_elm2['zones'].findall(xsdpath) ])
            elif item is None:
251
252
253
                xsdpath = '/'.join([ url_xmlschema + t for t in [ 'element', 'complexType', 'all', 'element' ] ])
                mod_elm = dict([ (elm.attrib['name'], elm) for elm in root.findall(xsdpath) ])
                self.assertTrue(mod in mod_elm)
254
                for (it, val) in DUMMY_DATA[mod].items():
255
256
                    xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                    itm_elm = dict([ (elm.attrib['name'], elm) for elm in mod_elm[mod].findall(xsdpath) ])
257
258
259
260
261
262
263
264
265
266
267
268
                    self.assertTrue(it in itm_elm)
                    if type(val) is list:
                        xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'sequence', 'element' ] ])
                        itm_elm2 = dict([ (elm.attrib['name'], elm) for elm in itm_elm[it].findall(xsdpath) ])
                        self.assertTrue('zones' in itm_elm2)
                        for i in val:
                            for k in i.keys():
                                xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                                self.assertTrue(
                                    k in [ elm.attrib['name'] for elm in itm_elm2['zones'].findall(xsdpath) ])
            else:
                xsdpath = '/'.join([ url_xmlschema + t for t in [ 'element', 'complexType', 'all', 'element' ] ])
269
270
271
272
                mod_elm = dict([ (elm.attrib['name'], elm) for elm in root.findall(xsdpath) ])
                self.assertTrue(mod in mod_elm)
                xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                itm_elm = dict([ (elm.attrib['name'], elm) for elm in mod_elm[mod].findall(xsdpath) ])
273
274
275
276
277
278
279
280
281
282
                self.assertTrue(item in itm_elm)
                if type(DUMMY_DATA[mod][item]) is list:
                    xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'sequence', 'element' ] ])
                    itm_elm2 = dict([ (elm.attrib['name'], elm) for elm in itm_elm[item].findall(xsdpath) ])
                    self.assertTrue('zones' in itm_elm2)
                    for i in DUMMY_DATA[mod][item]:
                        for k in i.keys():
                            xsdpath = '/'.join([ url_xmlschema + t for t in [ 'complexType', 'all', 'element' ] ])
                            self.assertTrue(
                                k in [ elm.attrib['name'] for elm in itm_elm2['zones'].findall(xsdpath) ])
283
284
285
286
287
288
289
290
291
292
293

        # URL is '/bind10/statistics/xsd'
        check_XSD_URL_PATH(mod=None, item=None)
        for m in DUMMY_DATA:
            # URL is '/bind10/statistics/xsd/Module'
            check_XSD_URL_PATH(mod=m)
            for k in DUMMY_DATA[m].keys():
                # URL is '/bind10/statistics/xsd/Module/Item'
                check_XSD_URL_PATH(mod=m, item=k)

        def check_XSL_URL_PATH(mod=None, item=None):
294
295
296
297
298
299
            url_path = stats_httpd.XSL_URL_PATH
            if mod is not None:
                url_path = url_path + '/' + mod
                if item is not None:
                    url_path = url_path + '/' + item
            self.client.putrequest('GET', url_path)
300
301
302
303
304
305
306
307
308
            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}'
            self.assertEqual(root.tag, url_trans + 'stylesheet')
309
310
            if item is None and mod is None:
                xslpath = url_trans + 'template/' + url_xhtml + 'table/' + url_trans + 'for-each'
311
312
313
314
315
316
                mod_fe = dict([ (x.attrib['select'], x) for x in root.findall(xslpath) ])
                for (mod, itms) in DUMMY_DATA.items():
                    self.assertTrue(mod in mod_fe)
                    for (k, v) in itms.items():
                        if type(v) is list:
                            xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
317
                                + url_xhtml + 'table/' + url_trans + 'for-each'
318
319
320
321
                            itm_fe = dict([ (x.attrib['select'], x) for x in mod_fe[mod].findall(xslpath) ])
                            self.assertTrue(k in itm_fe)
                            for itms in v:
                                xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
322
                                    + url_xhtml + 'table/' + url_trans + 'for-each'
323
324
325
326
327
                                itm_fe = dict([ (x.attrib['select'], x) for x in itm_fe[k].findall(xslpath) ])
                                self.assertTrue('zones' in itm_fe)
                                for (k, v) in itms.items():
                                    xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                                        + url_xhtml + 'table/' + url_xhtml + 'tr/' \
328
                                        + url_xhtml + 'td/' + url_trans + 'value-of'
329
330
331
332
333
                                    itm_vo = [ x.attrib['select'] for x in itm_fe['zones'].findall(xslpath) ]
                                    self.assertTrue(k in itm_vo)
                        else:
                            xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                                + url_xhtml + 'table/' + url_xhtml + 'tr/' \
334
                                + url_xhtml + 'td/' + url_trans + 'value-of'
335
336
                            itm_vo = [ x.attrib['select'] for x in mod_fe[mod].findall(xslpath) ]
                            self.assertTrue(k in itm_vo)
337
            elif item is None:
338
339
340
                xslpath = url_trans + 'template/' + url_xhtml + 'table/' + url_trans + 'for-each'
                mod_fe = dict([ (x.attrib['select'], x) for x in root.findall(xslpath) ])
                self.assertTrue(mod in mod_fe)
341
342
                for (k, v) in DUMMY_DATA[mod].items():
                    if type(v) is list:
343
344
345
                        xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                            + url_xhtml + 'table/' + url_trans + 'for-each'
                        itm_fe = dict([ (x.attrib['select'], x) for x in mod_fe[mod].findall(xslpath) ])
346
347
348
                        self.assertTrue(k in itm_fe)
                        for itms in v:
                            xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
349
                                + url_xhtml + 'table/' + url_trans + 'for-each'
350
351
352
353
354
                            itm_fe = dict([ (x.attrib['select'], x) for x in itm_fe[k].findall(xslpath) ])
                            self.assertTrue('zones' in itm_fe)
                            for (k, v) in itms.items():
                                xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                                    + url_xhtml + 'table/' + url_xhtml + 'tr/' \
355
                                    + url_xhtml + 'td/' + url_trans + 'value-of'
356
357
358
                                itm_vo = [ x.attrib['select'] for x in itm_fe['zones'].findall(xslpath) ]
                                self.assertTrue(k in itm_vo)
                    else:
359
360
361
362
                        xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                            + url_xhtml + 'table/' + url_xhtml + 'tr/' \
                            + url_xhtml + 'td/' + url_trans + 'value-of'
                        itm_vo = [ x.attrib['select'] for x in mod_fe[mod].findall(xslpath) ]
363
                        self.assertTrue(k in itm_vo)
364
            else:
365
366
367
368
369
370
371
372
373
                xslpath = url_trans + 'template/' + url_xhtml + 'table/' + url_trans + 'for-each'
                mod_fe = dict([ (x.attrib['select'], x) for x in root.findall(xslpath) ])
                self.assertTrue(mod in mod_fe)
                if type(DUMMY_DATA[mod][item]) is list:
                    xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                        + url_xhtml + 'table/' + url_trans + 'for-each'
                    itm_fe = dict([ (x.attrib['select'], x) for x in mod_fe[mod].findall(xslpath) ])
                    self.assertTrue(item in itm_fe)
                    for itms in DUMMY_DATA[mod][item]:
374
                        xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
375
                            + url_xhtml + 'table/' + url_trans + 'for-each'
376
                        itm_fe = dict([ (x.attrib['select'], x) for x in itm_fe[item].findall(xslpath) ])
377
378
379
380
                        self.assertTrue('zones' in itm_fe)
                        for (k, v) in itms.items():
                            xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                                + url_xhtml + 'table/' + url_xhtml + 'tr/' \
381
                                + url_xhtml + 'td/' + url_trans + 'value-of'
382
383
384
                            itm_vo = [ x.attrib['select'] for x in itm_fe['zones'].findall(xslpath) ]
                            self.assertTrue(k in itm_vo)
                else:
385
386
387
388
389
                    xslpath = url_xhtml + 'tr/' + url_xhtml + 'td/' \
                        + url_xhtml + 'table/' + url_xhtml + 'tr/' \
                        + url_xhtml + 'td/' + url_trans + 'value-of'
                    itm_vo = [ x.attrib['select'] for x in mod_fe[mod].findall(xslpath) ]
                    self.assertTrue(item in itm_vo)
390
391
392

        # URL is '/bind10/statistics/xsl'
        check_XSL_URL_PATH(mod=None, item=None)
393
394
395
396
397
398
        for m in DUMMY_DATA:
            # URL is '/bind10/statistics/xsl/Module'
            check_XSL_URL_PATH(mod=m)
            for k in DUMMY_DATA[m].keys():
                # URL is '/bind10/statistics/xsl/Module/Item'
                check_XSL_URL_PATH(mod=m, item=k)
399

400
        # 302 redirect
401
402
403
404
405
        self.client._http_vsn_str = 'HTTP/1.1'
        self.client.putrequest('GET', '/')
        self.client.putheader('Host', self.address)
        self.client.endheaders()
        response = self.client.getresponse()
406
407
        self.assertEqual(response.status, 302)
        self.assertEqual(response.getheader('Location'),
408
                         "http://%s:%d%s" % (self.address, self.port, stats_httpd.XML_URL_PATH))
409

410
        # 404 NotFound (random path)
411
412
413
414
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/path/to/foo/bar')
        self.client.endheaders()
        response = self.client.getresponse()
415
        self.assertEqual(response.status, 404)
416
417
418
419
420
421
422
423
424
425
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/foo')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/foo')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
426

427
428
429
430
431
432
433
        # 404 NotFound (too long path)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xml/Boss/boot_time/a')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
        # 404 NotFound (nonexistent module name)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xml/Foo')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsd/Foo')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsl/Foo')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)

        # 404 NotFound (nonexistent item name)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xml/Foo/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsd/Foo/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsl/Foo/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
467

468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
        # 404 NotFound (existent module but nonexistent item name)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xml/Auth/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsd/Auth/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)
        self.client._http_vsn_str = 'HTTP/1.0'
        self.client.putrequest('GET', '/bind10/statistics/xsl/Auth/bar')
        self.client.endheaders()
        response = self.client.getresponse()
        self.assertEqual(response.status, 404)

485
    def test_do_GET_failed1(self):
486
487
488
        # checks status
        self.assertEqual(send_command("status", "Stats"),
                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
489
490
        # failure case(Stats is down)
        self.assertTrue(self.stats.running)
491
        self.assertEqual(send_shutdown("Stats"), (0, None)) # Stats is down
492
493
        self.assertFalse(self.stats.running)
        self.stats_httpd.cc_session.set_timeout(milliseconds=100)
494
495

        # request XML
496
        self.client.putrequest('GET', stats_httpd.XML_URL_PATH)
497
498
        self.client.endheaders()
        response = self.client.getresponse()
499
500
501
        self.assertEqual(response.status, 500)

        # request XSD
502
        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
503
504
        self.client.endheaders()
        response = self.client.getresponse()
505
506
507
        self.assertEqual(response.status, 500)

        # request XSL
508
        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
509
510
        self.client.endheaders()
        response = self.client.getresponse()
511
512
513
        self.assertEqual(response.status, 500)

    def test_do_GET_failed2(self):
514
        # failure case(Stats replies an error)
515
516
        self.stats.mccs.set_command_handler(
            lambda cmd, args: \
517
                isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
518
519
520
            )

        # request XML
521
        self.client.putrequest('GET', stats_httpd.XML_URL_PATH)
522
523
        self.client.endheaders()
        response = self.client.getresponse()
524
        self.assertEqual(response.status, 404)
525
526

        # request XSD
527
        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
528
529
        self.client.endheaders()
        response = self.client.getresponse()
530
        self.assertEqual(response.status, 404)
531
532

        # request XSL
533
        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
534
535
        self.client.endheaders()
        response = self.client.getresponse()
536
        self.assertEqual(response.status, 404)
537

Naoki Kambe's avatar
Naoki Kambe committed
538
    def test_do_HEAD(self):
539
        self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH)
540
541
        self.client.endheaders()
        response = self.client.getresponse()
542
543
        self.assertEqual(response.status, 200)

544
545
546
        self.client.putrequest('HEAD', '/path/to/foo/bar')
        self.client.endheaders()
        response = self.client.getresponse()
547
548
        self.assertEqual(response.status, 404)

Naoki Kambe's avatar
Naoki Kambe committed
549
550
551
552
553
554
555
556
557
558
class TestHttpServerError(unittest.TestCase):
    """Tests for HttpServerError exception"""
    def test_raises(self):
        try:
            raise stats_httpd.HttpServerError('Nothing')
        except stats_httpd.HttpServerError as err:
            self.assertEqual(str(err), 'Nothing')

class TestHttpServer(unittest.TestCase):
    """Tests for HttpServer class"""
559
    def setUp(self):
560
561
        # set the signal handler for deadlock
        self.sig_handler = SignalHandler(self.fail)
562
563
564
        self.base = BaseModules()

    def tearDown(self):
565
566
        if hasattr(self, "stats_httpd"):
            self.stats_httpd.stop()
567
        self.base.shutdown()
568
569
        # reset the signal handler
        self.sig_handler.reset()
570

Naoki Kambe's avatar
Naoki Kambe committed
571
    def test_httpserver(self):
572
573
574
575
576
        self.stats_httpd = MyStatsHttpd(get_availaddr())
        self.assertEqual(type(self.stats_httpd.httpd), list)
        self.assertEqual(len(self.stats_httpd.httpd), 1)
        for httpd in self.stats_httpd.httpd:
            self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
Naoki Kambe's avatar
Naoki Kambe committed
577
578
579
580

class TestStatsHttpdError(unittest.TestCase):
    """Tests for StatsHttpdError exception"""

581
    def test_raises1(self):
Naoki Kambe's avatar
Naoki Kambe committed
582
583
584
585
586
        try:
            raise stats_httpd.StatsHttpdError('Nothing')
        except stats_httpd.StatsHttpdError as err:
            self.assertEqual(str(err), 'Nothing')

587
588
589
590
591
592
    def test_raises2(self):
        try:
            raise stats_httpd.StatsHttpdDataError('Nothing')
        except stats_httpd.StatsHttpdDataError as err:
            self.assertEqual(str(err), 'Nothing')

Naoki Kambe's avatar
Naoki Kambe committed
593
594
595
596
class TestStatsHttpd(unittest.TestCase):
    """Tests for StatsHttpd class"""

    def setUp(self):
597
598
        # set the signal handler for deadlock
        self.sig_handler = SignalHandler(self.fail)
599
600
601
602
        self.base = BaseModules()
        self.stats_server = ThreadingServerManager(MyStats)
        self.stats_server.run()
        # checking IPv6 enabled on this platform
603
        self.ipv6_enabled = is_ipv6_enabled()
604

Naoki Kambe's avatar
Naoki Kambe committed
605
    def tearDown(self):
606
607
        if hasattr(self, "stats_httpd"):
            self.stats_httpd.stop()
608
609
        self.stats_server.shutdown()
        self.base.shutdown()
610
611
        # reset the signal handler
        self.sig_handler.reset()
612

Naoki Kambe's avatar
Naoki Kambe committed
613
    def test_init(self):
614
615
        server_address = get_availaddr()
        self.stats_httpd = MyStatsHttpd(server_address)
616
617
        self.assertEqual(self.stats_httpd.running, False)
        self.assertEqual(self.stats_httpd.poll_intval, 0.5)
618
        self.assertNotEqual(len(self.stats_httpd.httpd), 0)
619
620
621
622
623
624
625
        self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
        self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
        self.assertEqual(len(self.stats_httpd.config), 2)
        self.assertTrue('listen_on' in self.stats_httpd.config)
        self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
        self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
        self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
626
        self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
627
628

    def test_openclose_mccs(self):
629
630
631
632
633
634
635
636
        self.stats_httpd = MyStatsHttpd(get_availaddr())
        self.stats_httpd.close_mccs()
        self.assertEqual(self.stats_httpd.mccs, None)
        self.stats_httpd.open_mccs()
        self.assertIsNotNone(self.stats_httpd.mccs)
        self.stats_httpd.mccs = None
        self.assertEqual(self.stats_httpd.mccs, None)
        self.assertEqual(self.stats_httpd.close_mccs(), None)
Naoki Kambe's avatar
Naoki Kambe committed
637
638

    def test_mccs(self):
639
        self.stats_httpd = MyStatsHttpd(get_availaddr())
640
        self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
Naoki Kambe's avatar
Naoki Kambe committed
641
        self.assertTrue(
642
            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
Naoki Kambe's avatar
Naoki Kambe committed
643
644
        self.assertTrue(
            isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
645
        statistics_spec = self.stats_httpd.get_stats_spec()
646
        for mod in DUMMY_DATA:
647
648
            self.assertTrue(mod in statistics_spec)
            for cfg in statistics_spec[mod]:
649
650
                self.assertTrue('item_name' in cfg)
                self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
651
            self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
652
653
        self.stats_httpd.close_mccs()
        self.assertIsNone(self.stats_httpd.mccs)
Naoki Kambe's avatar
Naoki Kambe committed
654
655
656

    def test_httpd(self):
        # dual stack (addresses is ipv4 and ipv6)
657
        if self.ipv6_enabled:
658
659
            server_addresses = (get_availaddr('::1'), get_availaddr())
            self.stats_httpd = MyStatsHttpd(*server_addresses)
660
            for ht in self.stats_httpd.httpd:
661
662
                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
                self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
663
                self.assertTrue(isinstance(ht.socket, socket.socket))
Naoki Kambe's avatar
Naoki Kambe committed
664
665

        # dual stack (address is ipv6)
666
        if self.ipv6_enabled:
667
668
            server_addresses = get_availaddr('::1')
            self.stats_httpd = MyStatsHttpd(server_addresses)
669
            for ht in self.stats_httpd.httpd:
670
671
                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
                self.assertEqual(ht.address_family, socket.AF_INET6)
672
                self.assertTrue(isinstance(ht.socket, socket.socket))
673

674
675
676
677
678
679
680
        # dual/single stack (address is ipv4)
        server_addresses = get_availaddr()
        self.stats_httpd = MyStatsHttpd(server_addresses)
        for ht in self.stats_httpd.httpd:
            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
            self.assertEqual(ht.address_family, socket.AF_INET)
            self.assertTrue(isinstance(ht.socket, socket.socket))
681

682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
        # any address (IPv4)
        server_addresses = get_availaddr(address='0.0.0.0')
        self.stats_httpd = MyStatsHttpd(server_addresses)
        for ht in self.stats_httpd.httpd:
            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
            self.assertEqual(ht.address_family,socket.AF_INET)
            self.assertTrue(isinstance(ht.socket, socket.socket))

        # any address (IPv6)
        if self.ipv6_enabled:
            server_addresses = get_availaddr(address='::')
            self.stats_httpd = MyStatsHttpd(server_addresses)
            for ht in self.stats_httpd.httpd:
                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
                self.assertEqual(ht.address_family,socket.AF_INET6)
                self.assertTrue(isinstance(ht.socket, socket.socket))
698

699
700
701
        # existent hostname
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
                          get_availaddr(address='localhost'))
702

703
704
        # nonexistent hostname
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
Naoki Kambe's avatar
Naoki Kambe committed
705
706

        # over flow of port number
707
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
708

Naoki Kambe's avatar
Naoki Kambe committed
709
        # negative
710
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
711

Naoki Kambe's avatar
Naoki Kambe committed
712
        # alphabet
713
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
Naoki Kambe's avatar
Naoki Kambe committed
714

715
        # Address already in use
716
717
718
719
        server_addresses = get_availaddr()
        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
        self.stats_httpd_server.run()
        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
720
        send_shutdown("StatsHttpd")
Naoki Kambe's avatar
Naoki Kambe committed
721

722
    def test_running(self):
723
724
        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
        self.stats_httpd = self.stats_httpd_server.server
Naoki Kambe's avatar
Naoki Kambe committed
725
        self.assertFalse(self.stats_httpd.running)
726
        self.stats_httpd_server.run()
727
728
        self.assertEqual(send_command("status", "StatsHttpd"),
                         (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
729
        self.assertTrue(self.stats_httpd.running)
730
        self.assertEqual(send_shutdown("StatsHttpd"), (0, None))
731
        self.assertFalse(self.stats_httpd.running)
732
        self.stats_httpd_server.shutdown()
733

Naoki Kambe's avatar
Naoki Kambe committed
734
        # failure case
735
        self.stats_httpd = MyStatsHttpd(get_availaddr())
736
        self.stats_httpd.cc_session.close()
737
        self.assertRaises(ValueError, self.stats_httpd.start)
738

739
740
741
    def test_failure_with_a_select_error (self):
        """checks select.error is raised if the exception except
        errno.EINTR is raised while it's selecting"""
742
743
        def raise_select_except(*args):
            raise select.error('dummy error')
744
745
746
747
748
749
        orig_select = stats_httpd.select.select
        stats_httpd.select.select = raise_select_except
        self.stats_httpd = MyStatsHttpd(get_availaddr())
        self.assertRaises(select.error, self.stats_httpd.start)
        stats_httpd.select.select = orig_select

750
751
752
    def test_nofailure_with_errno_EINTR(self):
        """checks no exception is raised if errno.EINTR is raised
        while it's selecting"""
753
        def raise_select_except(*args):
754
            raise select.error(errno.EINTR)
755
        orig_select = stats_httpd.select.select
756
        stats_httpd.select.select = raise_select_except
757
758
759
760
        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
        self.stats_httpd_server.run()
        self.stats_httpd_server.shutdown()
        stats_httpd.select.select = orig_select
Naoki Kambe's avatar
Naoki Kambe committed
761
762

    def test_open_template(self):
763
        self.stats_httpd = MyStatsHttpd(get_availaddr())
764
        # successful conditions
765
766
767
768
769
770
771
772
773
774
        tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
        self.assertTrue(isinstance(tmpl, string.Template))
        opts = dict(
            xml_string="<dummy></dummy>",
            xsl_url_path="/path/to/")
        lines = tmpl.substitute(opts)
        for n in opts:
            self.assertTrue(lines.find(opts[n])>0)
        tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
        self.assertTrue(isinstance(tmpl, string.Template))
775
        opts = dict(xsd_string="<dummy></dummy>")
776
777
778
779
780
781
782
783
784
785
786
        lines = tmpl.substitute(opts)
        for n in opts:
            self.assertTrue(lines.find(opts[n])>0)
        tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
        self.assertTrue(isinstance(tmpl, string.Template))
        opts = dict(
            xsl_string="<dummy></dummy>",
            xsd_namespace="http://host/path/to/")
        lines = tmpl.substitute(opts)
        for n in opts:
            self.assertTrue(lines.find(opts[n])>0)
787
        # unsuccessful condition
Naoki Kambe's avatar
Naoki Kambe committed
788
789
790
791
792
        self.assertRaises(
            IOError,
            self.stats_httpd.open_template, '/path/to/foo/bar')

    def test_commands(self):
793
        self.stats_httpd = MyStatsHttpd(get_availaddr())
Naoki Kambe's avatar
Naoki Kambe committed
794
795
796
797
798
        self.assertEqual(self.stats_httpd.command_handler("status", None),
                         isc.config.ccsession.create_answer(
                0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
        self.stats_httpd.running = True
        self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
799
                         isc.config.ccsession.create_answer(0))
Naoki Kambe's avatar
Naoki Kambe committed
800
801
802
803
804
805
806
        self.assertFalse(self.stats_httpd.running)
        self.assertEqual(
            self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
            isc.config.ccsession.create_answer(
                1, "Unknown command: __UNKNOWN_COMMAND__"))

    def test_config(self):
807
        self.stats_httpd = MyStatsHttpd(get_availaddr())
Naoki Kambe's avatar
Naoki Kambe committed
808
        self.assertEqual(
809
            self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
Naoki Kambe's avatar
Naoki Kambe committed
810
            isc.config.ccsession.create_answer(
811
                1, "unknown item _UNKNOWN_KEY_"))
812

813
        addresses = get_availaddr()
Naoki Kambe's avatar
Naoki Kambe committed
814
815
        self.assertEqual(
            self.stats_httpd.config_handler(
816
                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
Naoki Kambe's avatar
Naoki Kambe committed
817
            isc.config.ccsession.create_answer(0))
818
819
820
821
        self.assertTrue("listen_on" in self.stats_httpd.config)
        for addr in self.stats_httpd.config["listen_on"]:
            self.assertTrue("address" in addr)
            self.assertTrue("port" in addr)
822
823
            self.assertTrue(addr["address"] == addresses[0])
            self.assertTrue(addr["port"] == addresses[1])
824

825
        if self.ipv6_enabled:
826
            addresses = get_availaddr("::1")
827
828
            self.assertEqual(
                self.stats_httpd.config_handler(
829
                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
830
831
832
833
834
                isc.config.ccsession.create_answer(0))
            self.assertTrue("listen_on" in self.stats_httpd.config)
            for addr in self.stats_httpd.config["listen_on"]:
                self.assertTrue("address" in addr)
                self.assertTrue("port" in addr)
835
836
                self.assertTrue(addr["address"] == addresses[0])
                self.assertTrue(addr["port"] == addresses[1])
837

838
        addresses = get_availaddr()
Naoki Kambe's avatar
Naoki Kambe committed
839
840
        self.assertEqual(
            self.stats_httpd.config_handler(
841
                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
Naoki Kambe's avatar
Naoki Kambe committed
842
            isc.config.ccsession.create_answer(0))
843
844
845
846
        self.assertTrue("listen_on" in self.stats_httpd.config)
        for addr in self.stats_httpd.config["listen_on"]:
            self.assertTrue("address" in addr)
            self.assertTrue("port" in addr)
847
848
            self.assertTrue(addr["address"] == addresses[0])
            self.assertTrue(addr["port"] == addresses[1])
Naoki Kambe's avatar
Naoki Kambe committed
849
850
851
852
853
854
        (ret, arg) = isc.config.ccsession.parse_answer(
            self.stats_httpd.config_handler(
                dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
            )
        self.assertEqual(ret, 1)

855
    def test_xml_handler(self):
856
        self.stats_httpd = MyStatsHttpd(get_availaddr())
857
        self.stats_httpd.get_stats_spec = lambda x,y: \
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
            { "Dummy" :
                  [{
                        "item_name": "foo",
                        "item_type": "string",
                        "item_optional": False,
                        "item_default": "bar",
                        "item_description": "foo is bar",
                        "item_title": "Foo"
                        },
                   {
                        "item_name": "foo2",
                        "item_type": "list",
                        "item_optional": False,
                        "item_default": [
                            {
                                "zonename" : "test1",
                                "queries.udp" : 1,
                                "queries.tcp" : 2
                                },
                            {
                                "zonename" : "test2",
                                "queries.udp" : 3,
                                "queries.tcp" : 4
                                }
                        ],
                        "item_title": "Foo bar",
                        "item_description": "Foo bar",
                        "list_item_spec": {
                            "item_name": "foo2-1",
                            "item_type": "map",
                            "item_optional": False,
                            "item_default": {},
                            "map_item_spec": [
                                {
                                    "item_name": "foo2-1-1",
                                    "item_type": "string",
                                    "item_optional": False,
                                    "item_default": "",
                                    "item_title": "Foo2 1 1",
                                    "item_description": "Foo bar"
                                    },
                                {
                                    "item_name": "foo2-1-2",
                                    "item_type": "integer",
                                    "item_optional": False,
                                    "item_default": 0,
                                    "item_title": "Foo2 1 2",
                                    "item_description": "Foo bar"
                                    },
                                {
                                    "item_name": "foo2-1-3",
                                    "item_type": "integer",
                                    "item_optional": False,
                                    "item_default": 0,
                                    "item_title": "Foo2 1 3",
                                    "item_description": "Foo bar"
                                    }
                                ]
                            }
                        }]
              }
919
        self.stats_httpd.get_stats_data = lambda x,y: \
920
921
922
923
924
925
926
927
928
929
930
931
932
            { 'Dummy' : { 'foo':'bar',
                          'foo2': [
                            {
                                "foo2-1-1" : "bar1",
                                "foo2-1-2" : 10,
                                "foo2-1-3" : 9
                                },
                            {
                                "foo2-1-1" : "bar2",
                                "foo2-1-2" : 8,
                                "foo2-1-3" : 7
                                }
                            ] } }
933
        xml_body1 = self.stats_httpd.open_template(
934
            stats_httpd.XML_TEMPLATE_LOCATION).substitute(
935
            xml_string='<bind10:statistics xmlns:bind10="http://bind10.isc.org/bind10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://bind10.isc.org/bind10 /bind10/statistics/xsd"><Dummy><foo>bar</foo><foo2><foo2-1><foo2-1-1>bar1</foo2-1-1><foo2-1-2>10</foo2-1-2><foo2-1-3>9</foo2-1-3></foo2-1><foo2-1><foo2-1-1>bar2</foo2-1-1><foo2-1-2>8</foo2-1-2><foo2-1-3>7</foo2-1-3></foo2-1></foo2></Dummy></bind10:statistics>',
936
            xsl_url_path=stats_httpd.XSL_URL_PATH)
937
        xml_body2 = self.stats_httpd.xml_handler()
938
939
940
        self.assertEqual(type(xml_body1), str)
        self.assertEqual(type(xml_body2), str)
        self.assertEqual(xml_body1, xml_body2)
941
        self.stats_httpd.get_stats_spec = lambda x,y: \
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
            { "Dummy" :
                  [{
                        "item_name": "bar",
                        "item_type": "string",
                        "item_optional": False,
                        "item_default": "foo",
                        "item_description": "bar foo",
                        "item_title": "Bar"
                        },
                   {
                        "item_name": "bar2",
                        "item_type": "list",
                        "item_optional": False,
                        "item_default": [
                            {
                                "zonename" : "test1",
                                "queries.udp" : 1,
                                "queries.tcp" : 2
                                },
                            {
                                "zonename" : "test2",
                                "queries.udp" : 3,
                                "queries.tcp" : 4
                                }
                        ],
                        "item_title": "Bar foo",
                        "item_description": "Bar foo",
                        "list_item_spec": {
                            "item_name": "bar2-1",
                            "item_type": "map",
                            "item_optional": False,
                            "item_default": {},
                            "map_item_spec": [
                                {
                                    "item_name": "bar2-1-1",
                                    "item_type": "string",
                                    "item_optional": False,
                                    "item_default": "",
                                    "item_title": "Bar2 1 1",
                                    "item_description": "Bar foo"
                                    },
                                {
                                    "item_name": "bar2-1-2",
                                    "item_type": "integer",
                                    "item_optional": False,
                                    "item_default": 0,
                                    "item_title": "Bar2 1 2",
                                    "item_description": "Bar foo"
                                    },
                                {
                                    "item_name": "bar2-1-3",
                                    "item_type": "integer",
                                    "item_optional": False,
                                    "item_default": 0,
                                    "item_title": "Bar2 1 3",
                                    "item_description": "Bar foo"
                                    }
                                ]
                            }
For faster browsing, not all history is shown. View entire blame