BIND 10 trac547, updated. ae170ab6674e889369bf4d97c01294222f2a4edd [trac547] add more tests - stats module returns error - "shutdown" command is invoked

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Mar 7 12:45:38 UTC 2011


The branch, trac547 has been updated
       via  ae170ab6674e889369bf4d97c01294222f2a4edd (commit)
       via  4d04514e9ea4b5261c2d8e4f04776d391a00688e (commit)
       via  04a63faa1b803ed862af655e38c9989a48e286b7 (commit)
       via  ce2bc7f565535cfedf1958157d539e0fd02c3354 (commit)
       via  de46ddd9ba5e884e2164c0a6ff66a30d641826b1 (commit)
       via  4e96a5b01f0f11ab92dd2b8d17371da3fb970dff (commit)
       via  c6c2a5e9e9c353f0c7103f269270b0a9a66a8791 (commit)
       via  d050a27bba812802f96524cd8f660dbdcb5c77cc (commit)
       via  c0b77c41f7dc0daa763ee53566fe9ca18d81a7a2 (commit)
      from  bf7bda2a551b71f131bf8d95ca45ef532b1e181e (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ae170ab6674e889369bf4d97c01294222f2a4edd
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 21:39:38 2011 +0900

    [trac547] add more tests
     - stats module returns error
     - "shutdown" command is invoked

commit 4d04514e9ea4b5261c2d8e4f04776d391a00688e
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 19:36:58 2011 +0900

    [trac547] add more test

commit 04a63faa1b803ed862af655e38c9989a48e286b7
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 18:03:56 2011 +0900

    [trac547]
     - add restoring old config data if failed restarting httpd
     - add to a new methods "get_sockets" to return socket list
     - add more verbose messages
     - remove destructor of stats_httpd module and add catching SessionError when stats_httpd object is exiting

commit ce2bc7f565535cfedf1958157d539e0fd02c3354
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 12:27:31 2011 +0900

    [trac547]
     - change URL of XML, XSD and XSL documents
     - add parameter of XSD namespace
     - add default configure
     - add some assertion
     - rename some methods which return XML, XSD and XSL document
     - implement HTTP redirection because of no found document requested
     - remove catching some exceptions because it didn't be needed to be so much strict
     - add error handling when received incorrect configure
     - configurable of multiple IP addresses and ports for HTTP listen (it can hold multiple sockets for HTTP listen)
     - add http closing message for verbose
     - fix some try/except statements
     - add some minor changes and minor refactoring

commit de46ddd9ba5e884e2164c0a6ff66a30d641826b1
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 12:03:12 2011 +0900

    [trac547]
     - add changes according to changes of the target source code
     - change values of dummy data
     - add more tests

commit 4e96a5b01f0f11ab92dd2b8d17371da3fb970dff
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 12:01:24 2011 +0900

    [trac547]
     - change values of constant parameters for testing
     - add more information in error massages

commit c6c2a5e9e9c353f0c7103f269270b0a9a66a8791
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 11:59:35 2011 +0900

    [trac547] add some minor changes for testing
     - add/rename some minor parameter
     - remove some minor getter methods

commit d050a27bba812802f96524cd8f660dbdcb5c77cc
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 11:57:40 2011 +0900

    [trac547] configurable of multiple IP addresses and ports for HTTP listen

commit c0b77c41f7dc0daa763ee53566fe9ca18d81a7a2
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Mon Mar 7 11:53:05 2011 +0900

    [trac547]
     - replaced with substitution strings for namespace and url
     - add text decoration of CSS to XSL document

-----------------------------------------------------------------------

Summary of changes:
 src/bin/stats/stats-httpd-xml.tpl.in        |    6 +-
 src/bin/stats/stats-httpd-xsd.tpl.in        |    4 +-
 src/bin/stats/stats-httpd-xsl.tpl.in        |    5 +-
 src/bin/stats/stats-httpd.spec.in           |   46 +++-
 src/bin/stats/stats_httpd.py.in             |  349 +++++++++++++++-----------
 src/bin/stats/tests/b10-stats-httpd_test.py |  311 +++++++++++++++---------
 src/bin/stats/tests/http/server.py          |   14 +-
 src/bin/stats/tests/socket.py               |    8 +-
 8 files changed, 451 insertions(+), 292 deletions(-)

-----------------------------------------------------------------------
diff --git a/src/bin/stats/stats-httpd-xml.tpl.in b/src/bin/stats/stats-httpd-xml.tpl.in
index 7f2cb68..d5846ad 100644
--- a/src/bin/stats/stats-httpd-xml.tpl.in
+++ b/src/bin/stats/stats-httpd-xml.tpl.in
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet type="text/xsl" href="bind10-statistics.xsl"?>
+<?xml-stylesheet type="text/xsl" href="$xsl_url_path"?>
 <!--
  - Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
  -
@@ -17,8 +17,8 @@
 -->
 
 <stats:stats_data version="1.0"
-  xmlns:stats="http://bind10.isc.org/xsd/bind10-statistics"
+  xmlns:stats="$xsd_namespace"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://bind10.isc.org/xsd/bind10-statistics bind10-statistics.xsd">
+  xsi:schemaLocation="$xsd_namespace $xsd_url_path">
   $xml_string
 </stats:stats_data>
diff --git a/src/bin/stats/stats-httpd-xsd.tpl.in b/src/bin/stats/stats-httpd-xsd.tpl.in
index 469140f..6ad1280 100644
--- a/src/bin/stats/stats-httpd-xsd.tpl.in
+++ b/src/bin/stats/stats-httpd-xsd.tpl.in
@@ -15,9 +15,9 @@
  - PERFORMANCE OF THIS SOFTWARE.
 -->
 
-<schema targetNamespace="http://bind10.isc.org/xsd/bind10-statistics"
+<schema targetNamespace="$xsd_namespace"
   xmlns="http://www.w3.org/2001/XMLSchema"
-  xmlns:stats="http://bind10.isc.org/xsd/bind10-statistics">
+  xmlns:stats="$xsd_namespace">
   <annotation>
     <documentation xml:lang="en">XML schema of the statistics
       data in BIND 10</documentation>
diff --git a/src/bin/stats/stats-httpd-xsl.tpl.in b/src/bin/stats/stats-httpd-xsl.tpl.in
index 9354c78..01ffdc6 100644
--- a/src/bin/stats/stats-httpd-xsl.tpl.in
+++ b/src/bin/stats/stats-httpd-xsl.tpl.in
@@ -17,7 +17,7 @@
 
 <xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"
-  xmlns:stats="http://bind10.isc.org/xsd/bind10-statistics">
+  xmlns:stats="$xsd_namespace">
   <xsl:output method="html" encoding="UTF-8"
     doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
     doctype-system=" http://www.w3.org/TR/html4/loose.dtd " />
@@ -34,6 +34,9 @@ td, th {
   padding: 3px 20px;
   border: 1px #000000 solid;
 }
+td.title {
+  text-decoration:underline;
+}
 ]]>
         </style>
       </head>
diff --git a/src/bin/stats/stats-httpd.spec.in b/src/bin/stats/stats-httpd.spec.in
index d769752..f4d7a52 100644
--- a/src/bin/stats/stats-httpd.spec.in
+++ b/src/bin/stats/stats-httpd.spec.in
@@ -4,18 +4,42 @@
     "module_description": "Stats HTTP daemon",
     "config_data": [
       {
-        "item_name": "listen_on_address",
-        "item_type": "string",
-        "item_optional": true,
-        "item_default": "127.0.0.1",
-        "item_description": "http listen-on address"
-      },
-      {
-        "item_name": "listen_on_port",
-        "item_type": "integer",
+        "item_name": "listen_on",
+        "item_type": "list",
         "item_optional": false,
-        "item_default": 8000,
-        "item_description": "http listen-on port"
+        "item_default": [
+          {
+            "address": "::1",
+            "port": 8000
+          },
+          {
+            "address": "127.0.0.1",
+            "port": 8000
+          }
+        ],
+        "list_item_spec": {
+          "item_name": "address",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+            {
+              "item_name": "address",
+              "item_type": "string",
+              "item_optional": true,
+              "item_default": "127.0.0.1",
+              "item_description": "http listen-on address"
+            },
+            {
+              "item_name": "port",
+              "item_type": "integer",
+              "item_optional": true,
+              "item_default": 8000,
+              "item_description": "http listen-on port"
+            }
+          ]
+        },
+        "item_description": "http listen-on address and port"
       }
     ],
     "commands": [
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index 29a526d..ab0a291 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -45,9 +45,12 @@ STATS_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
 XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
 XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
 XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
-XML_URL_PATH = '/'
-XSD_URL_PATH = '/bind10-statistics.xsd'
-XSL_URL_PATH = '/bind10-statistics.xsl'
+
+XML_URL_PATH = '/bind10/statistics/xml'
+XSD_URL_PATH = '/bind10/statistics/xsd'
+XSL_URL_PATH = '/bind10/statistics/xsl'
+XSD_NAMESPACE = 'http://bind10.isc.org' + XSD_URL_PATH # TODO: should be considered later
+DEFAULT_CONFIG = dict(listen_on=[('127.0.0.1', 8000)])
 
 MAX_SIZE_OF_TEMPLATE = 102400 # under 100k
 
@@ -55,62 +58,78 @@ MAX_SIZE_OF_TEMPLATE = 102400 # under 100k
 isc.util.process.rename()
 
 class HttpHandler(http.server.BaseHTTPRequestHandler):
-    """
-    HTTP handler class
-    """
+    """HTTP handler class"""
     def do_GET(self):
         body = self.send_head()
-        if type(body) is str:
+        if body is not None:
             self.wfile.write(body.encode())
 
     def do_HEAD(self):
         self.send_head()
 
     def send_head(self):
+        assert isinstance(self.server, HttpServer)
         try:
             if self.path == XML_URL_PATH:
-                contents = self.server.stats_httpd.get_stats_xml()
+                body = self.server.xml_handler()
             elif self.path == XSD_URL_PATH:
-                contents = self.server.stats_httpd.get_stats_xsd()
+                body = self.server.xsd_handler()
             elif self.path == XSL_URL_PATH:
-                contents = self.server.stats_httpd.get_stats_xsl()
+                body = self.server.xsl_handler()
             else:
-                self.send_error(404)
-                return None
+                if 'Host' in self.headers.keys():
+                    # redirect to XML URL
+                    self.send_response(302)
+                    self.send_header(
+                        "Location",
+                        "http://" + self.headers.get('Host') + XML_URL_PATH)
+                    self.end_headers()
+                    return None
+                else:
+                    # Couldn't find HOST
+                    self.send_error(404)
+                    return None
         except StatsHttpdError as err:
             self.send_error(500)
-            raise StatsHttpdError(err)
+            if self.server.verbose:
+                self.server.log_writer(
+                    "[b10-stats-httpd] %s\n" % err)
+            return None
         else:
             self.send_response(200)
             self.send_header("Content-type", "text/xml")
-            self.send_header("Content-Length", len(contents))
+            self.send_header("Content-Length", len(body))
             self.end_headers()
-            return contents
+            return body
 
     def log_message(self, format, *args):
-        """
-        Change the default log format
-        """
+        """Change the default log format"""
+        assert isinstance(self.server, HttpServer)
         if self.server.verbose:
-            self.server.stats_httpd.write_log(
+            self.server.log_writer(
                 "[b10-stats-httpd] %s - - [%s] %s\n" %
                 (self.address_string(),
                  self.log_date_time_string(),
                  format%args))
 
+class HttpServerError(Exception):
+    """To be passed from the HttpServer object to the StatsHttpd object"""
+    pass
+
 class HttpServer(http.server.HTTPServer):
-    """
-    HTTP Server class
-    """
-    def __init__(self, handler, stats_httpd, verbose=False):
+    """HTTP Server class"""
+    def __init__(self, server_address, handler,
+                 xml_handler, xsd_handler, xsl_handler, log_writer, verbose=False):
+        self.server_address = server_address
+        self.xml_handler = xml_handler
+        self.xsd_handler = xsd_handler
+        self.xsl_handler = xsl_handler
+        self.log_writer = log_writer
         self.verbose = verbose
-        self.stats_httpd = stats_httpd
-        http.server.HTTPServer.__init__(
-            self,
-            (self.stats_httpd.http_addr, self.stats_httpd.http_port),
-            handler)
+        http.server.HTTPServer.__init__(self, server_address, handler)
 
 class StatsHttpdError(Exception):
+    """To be passed from the StatsHttpd object to the HttpHandler object"""
     pass
 
 class StatsHttpd:
@@ -118,139 +137,176 @@ class StatsHttpd:
     """
     def __init__(self, verbose=False):
         self.verbose = verbose
-        self.running = True
+        self.running = False
         self.poll_intval = 0.5
         self.write_log = sys.stderr.write
         self.mccs = None
-        self.httpd = None
-        try:
-            self.open_mccs()
-            self.load_config()
-            self.load_templates()
-            self.open_httpd()
-        except StatsHttpdError as err:
-            raise StatsHttpdError(
-                "error with initializing StatsHttpd: %s" % err)
+        self.httpd = []
+        self.open_mccs()
+        self.load_config()
+        self.load_templates()
+        self.open_httpd()
 
     def open_mccs(self):
         # create ModuleCCSession
+        if self.verbose:
+            self.write_log("[b10-stats-httpd] Starting CC Session\n")
         self.mccs = isc.config.ModuleCCSession(
             SPECFILE_LOCATION, self.config_handler, self.command_handler)
-        self.mccs_fd = self.mccs.get_socket()
         self.cc_session = self.mccs._session
+        # read spec file of stats module and subscribe 'Stats'
         self.stats_module_spec = isc.config.module_spec_from_file(STATS_SPECFILE_LOCATION)
         self.stats_config_spec = self.stats_module_spec.get_config_spec()
         self.stats_module_name = self.stats_module_spec.get_module_name()
+        if self.verbose:
+            self.write_log("[b10-stats-httpd] Starts to subscribe stats module\n")
+        self.cc_session.group_subscribe(self.stats_module_name, "*")
 
     def close_mccs(self):
         if self.mccs is None:
             return
+        if self.verbose:
+            self.write_log("[b10-stats-httpd] Closing CC Session\n")
         try:
             self.cc_session.group_unsubscribe(self.stats_module_name, "*")
-        except isc.cc.session.SessionError as err:
-            raise StatsHttpdError("error in CCSession: %s\n" % err)
+        except isc.cc.session.SessionError as se:
+            if self.verbose:
+                self.write_log("[b10-stats-httpd] Failed to unsubscribe stats module\n")
         self.mccs.close()
         self.mccs = None
 
-    def load_config(self, new_config=None):
+    def load_config(self, new_config={}):
         # load config
-        if new_config is not None:
+        if len(new_config) > 0:
+            assert type(self.config) is dict
             assert type(new_config) is dict
-            assert self.config is not None
             self.config.update(new_config)
         else:
-            self.config = dict([
-                    (i['item_name'], self.mccs.get_value(i['item_name'])[0])
-                    for i in self.mccs.get_module_spec().get_config_spec()
-                    ])
-        assert self.config is not None and len(self.config) > 0 and type(self.config) is dict
-        assert 'listen_on_address' in self.config
-        assert 'listen_on_port' in self.config
-        self.http_addr = self.config['listen_on_address']
-        self.http_port = self.config['listen_on_port']
-
-    def open_httpd(self, address_family=None):
+            self.config = DEFAULT_CONFIG
+            self.config.update(
+                dict([
+                        (itm['item_name'], self.mccs.get_value(itm['item_name'])[0])
+                        for itm in self.mccs.get_module_spec().get_config_spec()
+                        ])
+                )
+        assert 'listen_on' in self.config
+        assert type(self.config['listen_on']) is list
+        # remove duplicated element
+        self.http_addrs = list(
+            set([ (cf['address'], cf['port']) for cf in self.config['listen_on'] ])
+                )
+
+    def open_httpd(self):
+        assert type(self.http_addrs) is list
+        for addr in self.http_addrs:
+            self.httpd.append(self._open_httpd(addr))
+
+    def _open_httpd(self, server_address, address_family=None):
+        assert type(server_address) is tuple
         try:
-            # try IPv6 as default
-            if address_family is None and socket.has_ipv6:
-                HttpServer.address_family = socket.AF_INET6
-            elif address_family is not None:
+            # try IPv6 at first
+            if address_family is not None:
                 HttpServer.address_family = address_family
-            self.httpd = HttpServer(HttpHandler, self, self.verbose)
+            elif socket.has_ipv6:
+                HttpServer.address_family = socket.AF_INET6
+            httpd = HttpServer(
+                server_address, HttpHandler,
+                self.xml_handler, self.xsd_handler, self.xsl_handler,
+                self.write_log, self.verbose)
         except (socket.gaierror, socket.error,
                 OverflowError, TypeError) as err:
+            # try IPv4 next
             if HttpServer.address_family == socket.AF_INET6:
-                # try IPv4
-                self.open_httpd(address_family=socket.AF_INET)
+                httpd = self._open_httpd(server_address, socket.AF_INET)
             else:
-                raise StatsHttpdError(
-                    "Invalid address %s: %s: %s\n" %
-                    (self.http_addr, self.http_port, err))
+                raise HttpServerError(
+                    "Invalid address %s, port %s: %s: %s" %
+                    (server_address[0], server_address[1],
+                     err.__class__.__name__, err))
         else:
-            self.http_fd = self.httpd.socket
-            if self.verbose and self.running:
+            if self.verbose:
                 self.write_log(
                     "[b10-stats-httpd] Started on address %s, port %s\n" %
-                    (self.http_addr, self.http_port))
+                    server_address)
+        assert isinstance(httpd, HttpServer)
+        return httpd
 
     def close_httpd(self):
-        if self.httpd is None:
+        if len(self.httpd) == 0:
             return
-        self.http_fd.close()
-        self.httpd = None
+        for ht in self.httpd:
+            assert type(ht.server_address) is tuple
+            if self.verbose:
+                self.write_log(
+                    "[b10-stats-httpd] Closing address %s, port %s\n" %
+                    (ht.server_address[0], ht.server_address[1])
+                    )
+            ht.server_close()
+        self.httpd = []
 
     def start(self):
-        try:
-            self.mccs.start()
-            self.cc_session.group_subscribe(self.stats_module_name, "*")
-            while self.running:
-                try:
-                    (rfd, wfd, xfd) = select.select(
-                        [self.mccs_fd, self.http_fd], [], [], self.poll_intval)
-                except select.error as err:
-                    if err.args[0] == errno.EINTR:
-                        (rfd, wfd, xfd) = ([], [], [])
-                    else:
-                        raise StatsHttpdError(err)
-                for fd in rfd + xfd:
-                    if fd == self.mccs_fd:
-                        self.mccs.check_command(nonblock=False)
-                    elif fd == self.http_fd:
-                        self.httpd.handle_request()
-        except (isc.cc.session.SessionError,
-                isc.config.ccsession.ModuleCCSessionError) as err:
-            raise StatsHttpdError("error in CCSession: %s\n" % err)
-        except StatsHttpdError as err:
-            raise StatsHttpdError("error with selecting; %s\n" % err)
-        finally:
-            self.stop()
+        self.mccs.start()
+        self.running = True
+        while self.running:
+            try:
+                (rfd, wfd, xfd) = select.select(
+                    self.get_sockets(), [], [], self.poll_intval)
+            except select.error as err:
+                if err.args[0] == errno.EINTR:
+                    (rfd, wfd, xfd) = ([], [], [])
+                else:
+                    raise select.error(err)
+            for fd in rfd + xfd:
+                if fd == self.mccs.get_socket():
+                    self.mccs.check_command(nonblock=False)
+                    continue
+                for ht in self.httpd:
+                    if fd == ht.socket:
+                        ht.handle_request()
+                        break
+        self.stop()
 
     def stop(self):
         if self.verbose:
             self.write_log("[b10-stats-httpd] Shutting down\n")
-        self.running = False
         self.close_httpd()
         self.close_mccs()
 
-    def __del__(self):
-        self.stop()
+    def get_sockets(self):
+        sockets = []
+        if self.mccs is not None:
+            sockets.append(self.mccs.get_socket())
+        if len(self.httpd) > 0:
+            for ht in self.httpd:
+                sockets.append(ht.socket)
+        return sockets
 
     def config_handler(self, new_config):
-        """
-        config handler
-        """
+        """config handler for ModuleCCSession object"""
+        assert type(new_config) is dict
         if self.verbose:
             self.write_log("[b10-stats-httpd] Loading new config : %s\n" % str(new_config))
+        for key in new_config.keys():
+            if key not in DEFAULT_CONFIG:
+                if self.verbose:
+                    self.write_log(
+                        "[b10-stats-httpd] Unknown known config: %s" % key)
+                return isc.config.ccsession.create_answer(
+                    1, "Unknown known config: %s" % key)
+        # backup old config
+        old_config = self.config.copy()
         self.close_httpd()
+        self.load_config(new_config)
         try:
-            self.load_config(new_config)
             self.open_httpd()
-        except StatsHttpdError as err:
+        except HttpServerError as err:
             if self.verbose:
-                self.write_log(
-                    "[b10-stats-httpd] Unexpected error with reloading new config: %s" % err)
+                self.write_log("[b10-stats-httpd] %s\n" % err)
+                self.write_log("[b10-stats-httpd] Restoring old config\n")
+            # restore old config
+            self.config_handler(old_config)
             return isc.config.ccsession.create_answer(
-                1, "Unexpected error with reloading new config: %s" % err)
+                1, "[b10-stats-httpd] %s" % err)
         else:
             return isc.config.ccsession.create_answer(0)
 
@@ -280,35 +336,27 @@ class StatsHttpd:
             seq = self.cc_session.group_sendmsg(
                 isc.config.ccsession.create_command('show'),
                 self.stats_module_name)
-            answer, env = self.cc_session.group_recvmsg(False, seq)
+            (answer, env) = self.cc_session.group_recvmsg(False, seq)
             if answer:
                 (rcode, value) = isc.config.ccsession.parse_answer(answer)
                 self.cc_session.group_reply(
                     env, isc.config.ccsession.create_answer(0))
-        except (isc.cc.SessionTimeout,
+        except (isc.cc.session.SessionTimeout,
                 isc.cc.session.SessionError) as err:
-            raise StatsHttpdError(err)
+            raise StatsHttpdError("%s: %s" %
+                                  (err.__class__.__name__, err))
         else:
             if rcode == 0:
                 return value
             else:
-                raise StatsHttpdError("can't the stats data")
+                raise StatsHttpdError("Stats module: %s" % str(value))
 
     def get_stats_spec(self):
-        """
-        return spec data
-        """
+        """return spec data"""
         return self.stats_config_spec
 
     def load_templates(self):
-        # open templates
-        self.xml_content = ""
-        self.xsd_content = ""
-        self.xsl_content = ""
-        self.xml_template = self.open_template(XML_TEMPLATE_LOCATION)
-        self.xsd_template = self.open_template(XSD_TEMPLATE_LOCATION)
-        self.xsl_template = self.open_template(XSL_TEMPLATE_LOCATION)
-
+        """open templates of XSD and XSL and create their string"""
         # for XSD
         xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
         for item in self.get_stats_spec():
@@ -329,9 +377,11 @@ class StatsHttpd:
             element.append(annotation)
             xsd_root.append(element)
         xsd_string = xml.etree.ElementTree.tostring(xsd_root)
-        assert self.xsd_template is not None
-        self.xsd_content = self.xsd_template.substitute(
-            xsd_string=xsd_string)
+        self.xsd_body = self.open_template(XSD_TEMPLATE_LOCATION).substitute(
+            xsd_string=xsd_string,
+            xsd_namespace=XSD_NAMESPACE
+            )
+        assert self.xsd_body is not None
 
         # for XSL
         xsd_root = xml.etree.ElementTree.Element(
@@ -340,7 +390,8 @@ class StatsHttpd:
         for item in self.get_stats_spec():
             tr = xml.etree.ElementTree.Element("tr")
             td1 = xml.etree.ElementTree.Element(
-                "td", dict(title=item["item_description"]))
+                "td", { "class" : "title",
+                        "title" : item["item_description"] })
             td1.text = item["item_title"]
             td2 = xml.etree.ElementTree.Element("td")
             xsl_valueof = xml.etree.ElementTree.Element(
@@ -351,40 +402,40 @@ class StatsHttpd:
             tr.append(td2)
             xsd_root.append(tr)
         xsl_string = xml.etree.ElementTree.tostring(xsd_root)
-        assert self.xsl_template is not None
-        self.xsl_content = self.xsl_template.substitute(
-            xsl_string=xsl_string)
+        self.xsl_body = self.open_template(XSL_TEMPLATE_LOCATION).substitute(
+            xsl_string=xsl_string,
+            xsd_namespace=XSD_NAMESPACE)
+        assert self.xsl_body is not None
 
-    def get_stats_xml(self):
+    def xml_handler(self):
         xml_list=[]
         for (k, v) in self.get_stats_data().items():
             (k, v) = (str(k), str(v))
-            assert type(k) is str and type(v) is str
-            x = xml.etree.ElementTree.Element(k)
-            x.text = v
+            elem = xml.etree.ElementTree.Element(k)
+            elem.text = v
             xml_list.append(
-                xml.etree.ElementTree.tostring(x))
+                xml.etree.ElementTree.tostring(elem))
         assert len(xml_list) > 0
         xml_string = "".join(xml_list)
-        assert self.xml_template is not None
-        self.xml_content = self.xml_template.substitute(
-            xml_string=xml_string)
-        return self.xml_content
+        self.xml_body = self.open_template(XML_TEMPLATE_LOCATION).substitute(
+            xml_string=xml_string,
+            xsd_namespace=XSD_NAMESPACE,
+            xsd_url_path=XSD_URL_PATH,
+            xsl_url_path=XSL_URL_PATH)
+        assert self.xml_body is not None
+        return self.xml_body
 
-    def get_stats_xsd(self):
-        return self.xsd_content
+    def xsd_handler(self):
+        return self.xsd_body
 
-    def get_stats_xsl(self):
-        return self.xsl_content
+    def xsl_handler(self):
+        return self.xsl_body
 
     def open_template(self, file_name):
-        try:
-            contents = "".join(
-                open(file_name, 'r').readlines(MAX_SIZE_OF_TEMPLATE))
-        except IOError as err:
-            raise StatsHttpdError("error in opening file; %s" % err)
-        else:
-            return string.Template(contents)
+        lines = "".join(
+            open(file_name, 'r').readlines(MAX_SIZE_OF_TEMPLATE))
+        assert lines is not None
+        return string.Template(lines)
 
 if __name__ == "__main__":
     try:
@@ -400,5 +451,7 @@ if __name__ == "__main__":
     except isc.cc.session.SessionError as se:
         sys.stderr.write("[b10-stats-httpd] Error creating module, "
                          + "is the command channel daemon running?\n")
+    except HttpServerError as hse:
+        sys.stderr.write("[b10-stats-httpd] %s\n" % hse)
     except KeyboardInterrupt as kie:
         sys.stderr.write("[b10-stats-httpd] Interrupted, exiting\n")
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index 082f603..20168b0 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -27,15 +27,15 @@ import isc.cc
 import stats_httpd
 
 DUMMY_DATA = {
-    "auth.queries.tcp": 0,
-    "auth.queries.udp": 0,
-    "bind10.boot_time": "1970-01-01T00:00:00Z",
-    "report_time": "2011-02-24T09:08:32Z",
-    "stats.boot_time": "2011-02-24T09:08:21Z",
-    "stats.last_update_time": "2011-02-24T09:08:21Z",
-    "stats.lname": "4d662005_a at fdvm",
-    "stats.start_time": "2011-02-24T09:08:21Z",
-    "stats.timestamp": 1298538512.87378
+    "auth.queries.tcp": 10000,
+    "auth.queries.udp": 12000,
+    "bind10.boot_time": "2011-03-04T11:59:05Z",
+    "report_time": "2011-03-04T11:59:19Z",
+    "stats.boot_time": "2011-03-04T11:59:06Z",
+    "stats.last_update_time": "2011-03-04T11:59:07Z",
+    "stats.lname": "4d70d40a_c at host",
+    "stats.start_time": "2011-03-04T11:59:06Z",
+    "stats.timestamp": 1299239959.560846
     }
 
 def push_answer(stats_httpd):
@@ -56,16 +56,22 @@ class TestHttpHandler(unittest.TestCase):
     def setUp(self):
         self.verbose = True
         self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
-        self.handler = stats_httpd.HttpHandler(None, None, self.stats_httpd.httpd)
-        self.assertTrue(self.stats_httpd.httpd.verbose)
+        self.assertTrue(type(self.stats_httpd.httpd) is list)
+        self.httpd = self.stats_httpd.httpd
+        for ht in self.httpd:
+            self.assertTrue(ht.verbose)
         self.stats_httpd.cc_session.verbose = False
 
     def test_do_GET(self):
+        for ht in self.httpd:
+            self._test_do_GET(ht._handler)
 
-        # URL is '/'
-        self.handler.path = stats_httpd.XML_URL_PATH
+    def _test_do_GET(self, handler):
+
+        # URL is '/bind10/statistics/xml'
+        handler.path = stats_httpd.XML_URL_PATH
         push_answer(self.stats_httpd)
-        self.handler.do_GET()
+        handler.do_GET()
         (ret, arg, env) = pull_query(self.stats_httpd)
         self.assertEqual(ret, "show")
         self.assertIsNone(arg)
@@ -76,74 +82,117 @@ class TestHttpHandler(unittest.TestCase):
         self.assertIsNone(arg)
         self.assertTrue('group' in env)
         self.assertEqual(env['group'], 'Stats')
-        self.assertEqual(self.handler.response.code, 200)
-        self.assertEqual(self.handler.response.headers["Content-type"], "text/xml")
-        self.assertTrue(self.handler.response.headers["Content-Length"] > 0)
-        self.assertTrue(self.handler.response.wrote_headers)
+        self.assertEqual(handler.response.code, 200)
+        self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+        self.assertTrue(handler.response.headers["Content-Length"] > 0)
+        self.assertTrue(handler.response.wrote_headers)
+        self.assertRegexpMatches(handler.response.body, stats_httpd.XSD_NAMESPACE)
+        self.assertRegexpMatches(handler.response.body, stats_httpd.XSD_URL_PATH)
         for (k, v) in DUMMY_DATA.items():
-            self.assertRegexpMatches(self.handler.response.body, str(k))
-            self.assertRegexpMatches(self.handler.response.body, str(v))
-
-        # URL is '/bind10-statitics.xsd'
-        self.handler.path = stats_httpd.XSD_URL_PATH
-        self.handler.do_GET()
-        self.assertEqual(self.handler.response.code, 200)
-        self.assertEqual(self.handler.response.headers["Content-type"], "text/xml")
-        self.assertTrue(self.handler.response.headers["Content-Length"] > 0)
-        self.assertTrue(self.handler.response.wrote_headers)
+            self.assertRegexpMatches(handler.response.body, str(k))
+            self.assertRegexpMatches(handler.response.body, str(v))
+
+        # URL is '/bind10/statitics/xsd'
+        handler.path = stats_httpd.XSD_URL_PATH
+        handler.do_GET()
+        self.assertEqual(handler.response.code, 200)
+        self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+        self.assertTrue(handler.response.headers["Content-Length"] > 0)
+        self.assertTrue(handler.response.wrote_headers)
+        self.assertRegexpMatches(handler.response.body, stats_httpd.XSD_NAMESPACE)
         for (k, v) in DUMMY_DATA.items():
-            self.assertRegexpMatches(self.handler.response.body, str(k))
-
-        # URL is '/bind10-statitics.xsl'
-        self.handler.path = stats_httpd.XSL_URL_PATH
-        self.handler.do_GET()
-        self.assertEqual(self.handler.response.code, 200)
-        self.assertEqual(self.handler.response.headers["Content-type"], "text/xml")
-        self.assertTrue(self.handler.response.headers["Content-Length"] > 0)
-        self.assertTrue(self.handler.response.wrote_headers)
+            self.assertRegexpMatches(handler.response.body, str(k))
+
+        # URL is '/bind10/statitics/xsl'
+        handler.path = stats_httpd.XSL_URL_PATH
+        handler.do_GET()
+        self.assertEqual(handler.response.code, 200)
+        self.assertEqual(handler.response.headers["Content-type"], "text/xml")
+        self.assertTrue(handler.response.headers["Content-Length"] > 0)
+        self.assertTrue(handler.response.wrote_headers)
+        self.assertRegexpMatches(handler.response.body, stats_httpd.XSD_NAMESPACE)
         for (k, v) in DUMMY_DATA.items():
-            self.assertRegexpMatches(self.handler.response.body, str(k))
-
-        # NotFound URL
-        self.handler.path = '/path/to/foo/bar'
-        self.handler.do_GET()
-        self.assertEqual(self.handler.response.code, 404)
-
-        # failure case(network problem)
-        self.handler.path = stats_httpd.XML_URL_PATH
+            self.assertRegexpMatches(handler.response.body, str(k))
+
+        # 302 redirect
+        handler.path = '/'
+        handler.headers = {'Host': 'my.host.domain'}
+        handler.do_GET()
+        self.assertEqual(handler.response.code, 302)
+        self.assertEqual(handler.response.headers["Location"],
+                         "http://my.host.domain%s" % stats_httpd.XML_URL_PATH)
+
+        # 404 NotFound
+        handler.path = '/path/to/foo/bar'
+        handler.headers = {}
+        handler.do_GET()
+        self.assertEqual(handler.response.code, 404)
+
+        # failure case(connection with Stats is down)
+        handler.path = stats_httpd.XML_URL_PATH
         push_answer(self.stats_httpd)
         self.assertFalse(self.stats_httpd.cc_session._socket._closed)
         self.stats_httpd.cc_session._socket._closed = True
-        self.assertRaises(stats_httpd.StatsHttpdError, self.handler.do_GET)
-        self.assertEqual(self.handler.response.code, 500)
+        handler.do_GET()
+        self.stats_httpd.cc_session._socket._closed = False
+        self.assertEqual(handler.response.code, 500)
+        self.stats_httpd.cc_session._clear_ques()
+
+        # failure case(Stats module returns err)
+        handler.path = stats_httpd.XML_URL_PATH
+        self.stats_httpd.cc_session.group_sendmsg(
+            { 'result': [ 1, "I have an error." ] }, "Stats")
+        self.assertFalse(self.stats_httpd.cc_session._socket._closed)
+        self.stats_httpd.cc_session._socket._closed = False
+        handler.do_GET()
+        self.assertEqual(handler.response.code, 500)
+        self.stats_httpd.cc_session._clear_ques()
 
     def test_do_HEAD(self):
-        # other url
-        self.handler.path = '/path/to/foo/bar'
-        self.handler.do_HEAD()
-        self.assertEqual(self.handler.response.code, 404)
+        for ht in self.httpd:
+            self._test_do_HEAD(ht._handler)
+
+    def _test_do_HEAD(self, handler):
+        handler.path = '/path/to/foo/bar'
+        handler.do_HEAD()
+        self.assertEqual(handler.response.code, 404)
 
     def test_log_message(self):
+        for ht in self.httpd:
+            self._test_log_message(ht._handler)
+
+    def _test_log_message(self, handler):
         # switch write_log function
-        self.stats_httpd.write_log = self.handler.response._write_log
+        handler.server.log_writer = handler.response._write_log
         log_message = 'ABCDEFG'
-        self.handler.log_message("%s", log_message)
-        self.assertEqual(self.handler.response.log, 
+        handler.log_message("%s", log_message)
+        self.assertEqual(handler.response.log, 
                          "[b10-stats-httpd] %s - - [%s] %s\n" %
-                         (self.handler.address_string(),
-                          self.handler.log_date_time_string(),
+                         (handler.address_string(),
+                          handler.log_date_time_string(),
                           log_message))
 
+class TestHttpServerError(unittest.TestCase):
+    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):
     def setUp(self):
         self.verbose = True
         self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
-        self.httpd = stats_httpd.HttpServer(stats_httpd.HttpHandler, self.stats_httpd, self.verbose)
-        self.assertEqual(self.httpd.verbose, self.verbose)
-        self.assertEqual(self.httpd.stats_httpd, self.stats_httpd)
-        self.assertEqual(type(self.httpd._get_handler), stats_httpd.HttpHandler)
-        self.assertEqual(self.httpd._get_server_class,
-                         (self.stats_httpd.http_addr, self.stats_httpd.http_port))
+        self.stats_httpd.cc_session.verbose = False
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(ht.server_address in self.stats_httpd.http_addrs)
+            self.assertEqual(self.ht.verbose, self.verbose)
+            self.assertEqual(self.ht.xml_handler, self.stats_httpd.xml_handler)
+            self.assertEqual(self.ht.xsd_handler, self.stats_httpd.xsd_handler)
+            self.assertEqual(self.ht.xsl_handler, self.stats_httpd.xsl_handler)
+            self.assertEqual(self.ht.log_writer, self.stats_httpd.write_log)
+            self.assertTrue(isinstance(self.ht._handler, stats_httpd.HttpHandler))
+            self.assertTrue(isinstance(self.ht.socket, socket.socket))
 
 class TestStatsHttpdError(unittest.TestCase):
     def test_raises(self):
@@ -157,18 +206,21 @@ class TestStatsHttpd(unittest.TestCase):
     def setUp(self):
         self.verbose = True
         socket._CLOSED = False
+        socket.has_ipv6 = True
         self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+        self.stats_httpd.cc_session.verbose = False
 
     def tearDown(self):
         self.stats_httpd.stop()
 
     def test_init(self):
         self.assertTrue(self.stats_httpd.verbose)
-        self.assertTrue(self.stats_httpd.running)
-        self.assertFalse(self.stats_httpd.mccs_fd._closed)
-        self.assertFalse(self.stats_httpd.http_fd._closed)
-        self.assertEqual(self.stats_httpd.mccs_fd.fileno(), id(self.stats_httpd.mccs_fd))
-        self.assertEqual(self.stats_httpd.http_fd.fileno(), id(self.stats_httpd.http_fd))
+        self.assertFalse(self.stats_httpd.mccs.get_socket()._closed)
+        self.assertEqual(self.stats_httpd.mccs.get_socket().fileno(),
+                         id(self.stats_httpd.mccs.get_socket()))
+        for ht in self.stats_httpd.httpd:
+            self.assertFalse(ht.socket._closed)
+            self.assertEqual(ht.socket.fileno(), id(ht.socket))
         socket._CLOSED = True
         self.assertRaises(isc.cc.session.SessionError,
                           stats_httpd.StatsHttpd)
@@ -176,9 +228,12 @@ class TestStatsHttpd(unittest.TestCase):
 
     def test_mccs(self):
         self.stats_httpd.open_mccs()
-        self.assertTrue(isinstance(self.stats_httpd.mccs_fd, socket.socket))
-        self.assertTrue(isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
-        self.assertTrue(isinstance(self.stats_httpd.stats_module_spec, isc.config.ModuleSpec))
+        self.assertTrue(
+            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
+        self.assertTrue(
+            isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
+        self.assertTrue(
+            isinstance(self.stats_httpd.stats_module_spec, isc.config.ModuleSpec))
         for cfg in self.stats_httpd.stats_config_spec:
             self.assertTrue('item_name' in cfg)
             self.assertTrue(cfg['item_name'] in DUMMY_DATA)
@@ -186,90 +241,108 @@ class TestStatsHttpd(unittest.TestCase):
 
     def test_load_config(self):
         self.stats_httpd.load_config()
-        self.assertEqual(self.stats_httpd.http_addr, '127.0.0.1')
-        self.assertEqual(self.stats_httpd.http_port, 8000)
+        self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
+        self.assertTrue(('::1', 8000) in set(self.stats_httpd.http_addrs))
 
     def test_httpd(self):
-        # dual stack (address is ipv4)
+        # dual stack (addresses is ipv4 and ipv6)
         socket.has_ipv6 = True
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('127.0.0.1', 8000)
-        assert stats_httpd.HttpServer.address_family is socket.AF_INET
+        self.assertTrue(('127.0.0.1', 8000) in set(self.stats_httpd.http_addrs))
+        self.assertTrue(('::1', 8000) in set(self.stats_httpd.http_addrs))
+        self.assertTrue(
+            stats_httpd.HttpServer.address_family in set([socket.AF_INET, socket.AF_INET6]))
         self.stats_httpd.open_httpd()
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
         self.stats_httpd.close_httpd()
+
         # dual stack (address is ipv6)
         socket.has_ipv6 = True
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('::1', 8000)
+        self.stats_httpd.http_addrs = [ ('::1', 8000) ]
         self.stats_httpd.open_httpd()
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
+        self.stats_httpd.close_httpd()
+
+        # dual stack (address is ipv4)
+        socket.has_ipv6 = True
+        self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
+        self.stats_httpd.open_httpd()
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
         self.stats_httpd.close_httpd()
 
         # only-ipv4 single stack
         socket.has_ipv6 = False
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('127.0.0.1', 8000)
+        self.stats_httpd.http_addrs = [ ('127.0.0.1', 8000) ]
         self.stats_httpd.open_httpd()
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
-        self.stats_httpd.close_httpd()
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('::1', 8000)
-        self.assertRaises(stats_httpd.StatsHttpdError, self.stats_httpd.open_httpd)
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
         self.stats_httpd.close_httpd()
 
+        # only-ipv4 single stack (force set ipv6 )
+        socket.has_ipv6 = False
+        self.stats_httpd.http_addrs = [ ('::1', 8000) ]
+        self.assertRaises(stats_httpd.HttpServerError,
+            self.stats_httpd.open_httpd)
+
         # hostname
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('localhost', 8000)
+        self.stats_httpd.http_addrs = [ ('localhost', 8000) ]
         self.stats_httpd.open_httpd()
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
         self.stats_httpd.close_httpd()
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('my.host.domain', 8000)
+
+        self.stats_httpd.http_addrs = [ ('my.host.domain', 8000) ]
         self.stats_httpd.open_httpd()
-        self.assertTrue(isinstance(self.stats_httpd.http_fd, socket.socket))
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht.socket, socket.socket))
         self.stats_httpd.close_httpd()
 
-        # port is over flow
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('', 80000)
-        self.assertRaises(stats_httpd.StatsHttpdError, self.stats_httpd.open_httpd)
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('', -8000)
-        self.assertRaises(stats_httpd.StatsHttpdError, self.stats_httpd.open_httpd)
-        (self.stats_httpd.http_addr, self.stats_httpd.http_port) = ('', 'ABCDE')
-        self.assertRaises(stats_httpd.StatsHttpdError, self.stats_httpd.open_httpd)
+        # over flow of port number
+        self.stats_httpd.http_addrs = [ ('', 80000) ]
+        self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
+        # negative
+        self.stats_httpd.http_addrs = [ ('', -8000) ]
+        self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
+        # alphabet
+        self.stats_httpd.http_addrs = [ ('', 'ABCDE') ]
+        self.assertRaises(stats_httpd.HttpServerError, self.stats_httpd.open_httpd)
 
     def test_start(self):
-        self.assertTrue(self.stats_httpd.running)
+        self.stats_httpd.cc_session.group_sendmsg(
+            { 'command': [ "shutdown" ] }, "StatsHttpd")
+        self.stats_httpd.start()
+        self.stats_httpd = stats_httpd.StatsHttpd(self.verbose)
+        self.stats_httpd.cc_session.verbose = False
         self.assertRaises(
-            stats_httpd.StatsHttpdError,
-            self.stats_httpd.start)
-        self.assertFalse(self.stats_httpd.running)
+            select.error, self.stats_httpd.start)
 
     def test_stop(self):
         # success case
         socket._CLOSED = False
-        self.assertTrue(self.stats_httpd.running)
         self.stats_httpd.stop()
         self.assertFalse(self.stats_httpd.running)
-        self.assertTrue(self.stats_httpd.mccs_fd._closed)
-        self.assertTrue(self.stats_httpd.http_fd._closed)
+        self.assertIsNone(self.stats_httpd.mccs)
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(ht.socket._closed)
         self.assertTrue(self.stats_httpd.cc_session._socket._closed)
         # failure case
         self.stats_httpd.cc_session._socket._closed = False
         self.stats_httpd.open_mccs()
         self.stats_httpd.cc_session._socket._closed = True
-        self.assertRaises(stats_httpd.StatsHttpdError, self.stats_httpd.stop)
+        self.stats_httpd.stop() # No excetion raises
         self.stats_httpd.cc_session._socket._closed = False
 
     def test_open_template(self):
         self.assertRaises(
-            stats_httpd.StatsHttpdError,
+            IOError,
             self.stats_httpd.open_template, '/path/to/foo/bar')
 
     def test_commands(self):
-        self.assertEqual(
-            self.stats_httpd.config_handler(None),
-            isc.config.ccsession.create_answer(0))
-        self.assertTrue(self.stats_httpd.running)
         self.assertEqual(self.stats_httpd.command_handler("status", None),
                          isc.config.ccsession.create_answer(
                 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
-        self.assertTrue(self.stats_httpd.running)
         self.stats_httpd.running = True
         self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
                          isc.config.ccsession.create_answer(
@@ -281,19 +354,29 @@ class TestStatsHttpd(unittest.TestCase):
                 1, "Unknown command: __UNKNOWN_COMMAND__"))
 
     def test_config(self):
+        d = dict(_UNKNOWN_KEY_=None)
+        assert type(d) is dict
+        self.assertEqual(
+            self.stats_httpd.config_handler(d),
+            isc.config.ccsession.create_answer(
+                    1, "Unknown known config: _UNKNOWN_KEY_"))
         self.assertEqual(
             self.stats_httpd.config_handler(
-                { 'listen_on_address' : '::1' }),
+                        dict(listen_on=[dict(address="::2",port=8000)])),
             isc.config.ccsession.create_answer(0))
         self.assertEqual(
             self.stats_httpd.config_handler(
-                { 'listen_on_port' : 80 }),
+                        dict(listen_on=[dict(address="::1",port=80)])),
             isc.config.ccsession.create_answer(0))
         self.assertEqual(
             self.stats_httpd.config_handler(
-                { 'listen_on_address' : "1.2.3.4",
-                  'listen_on_port' : 54321  }),
+                        dict(listen_on=[dict(address="1.2.3.4",port=54321)])),
             isc.config.ccsession.create_answer(0))
+        (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)
 
     def test_no_buildpath(self):
         """
diff --git a/src/bin/stats/tests/http/server.py b/src/bin/stats/tests/http/server.py
index 4d4621e..8bdad98 100644
--- a/src/bin/stats/tests/http/server.py
+++ b/src/bin/stats/tests/http/server.py
@@ -19,10 +19,11 @@ class DummyHttpResponse:
     def __init__(self, path):
         self.path = path
         self.headers={}
+        self.log = ""
 
     def _write_log(self, msg):
         assert type(msg) is str
-        self.log = msg
+        self.log = self.log + msg
 
 class HTTPServer:
     """
@@ -33,20 +34,14 @@ class HTTPServer:
         self.socket = socket.socket(self.address_family)
         self.server_class = server_class
         self.socket.bind(self.server_class)
-        self.handler = handler_class(None, None, self)
+        self._handler = handler_class(None, None, self)
 
     def handle_request(self):
         pass
 
-    def close(self):
+    def server_close(self):
         self.socket.close()
 
-    def _get_handler(self):
-        return self.handler
-
-    def _get_server_class(self):
-        return self.server_class
-
 class BaseHTTPRequestHandler:
     """
     This module is a mock-up class of http.server.BaseHTTPRequestHandler
@@ -54,6 +49,7 @@ class BaseHTTPRequestHandler:
 
     def __init__(self, request, client_address, server):
         self.path = "/path/to"
+        self.headers = {}
         self.server = server
         self.response = DummyHttpResponse(path=self.path)
         self.response.write = self._write
diff --git a/src/bin/stats/tests/socket.py b/src/bin/stats/tests/socket.py
index 8a82955..e6a4dd7 100644
--- a/src/bin/stats/tests/socket.py
+++ b/src/bin/stats/tests/socket.py
@@ -19,8 +19,8 @@ This module is a mock-up classes of socket module
 
 import re
 
-AF_INET = 'IPV4_OK'
-AF_INET6 = 'IPV6_OK'
+AF_INET = 'AF_INET'
+AF_INET6 = 'AF_INET6'
 _ADDRFAMILY = AF_INET
 has_ipv6 = True
 _CLOSED = False
@@ -54,10 +54,10 @@ class socket:
         if self.address_family not in set([AF_INET, AF_INET6]):
             raise error("Address family not supported by protocol: %s" % self.address_family)
         if self.address_family == AF_INET6 and not has_ipv6:
-            raise error("Address family not supported in this machine: %s ipv6: %s"
+            raise error("Address family not supported in this machine: %s has_ipv6: %s"
                         % (self.address_family, str(has_ipv6)))
         if self.address_family == AF_INET and re.search(':', self.server_address) is not None:
-            raise gaierror("Address family for hostname not supported : %s" % str(self.server_address))
+            raise gaierror("Address family for hostname not supported : %s %s" % (self.server_address, self.address_family))
         if self.address_family == AF_INET6 and re.search(':', self.server_address) is None:
             raise error("Cannot assign requested address : %s" % str(self.server_address))
         if type(self.server_port) is not int:




More information about the bind10-changes mailing list