Browse Source

[trac930]
- remove "stats-schema.spec" setting and getting statistics data schema via
this spec file

- add "version" item in DEFAULT_CONFIG

- get the address family by socket.getaddrinfo function with specified
server_address in advance, and create HttpServer object once, in stead of
creating double HttpServer objects for IPv6 and IPv4 in the prior code
(It is aimed for avoiding to fail to close the once opened sockets.)

- open HTTP port in start method

- avoid calling config_handler recursively in the except statement

- create XML, XSD, XSL documents after getting statistics data and schema from
remote stats module via CC session

- definitely close once opened template file object

Naoki Kambe 14 years ago
parent
commit
daa1d6dd07
1 changed files with 121 additions and 108 deletions
  1. 121 108
      src/bin/stats/stats_httpd.py.in

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

@@ -57,7 +57,6 @@ else:
     BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
     BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
     BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
     BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
 SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd.spec"
 SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd.spec"
-SCHEMA_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-schema.spec"
 XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
 XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
 XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
 XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
 XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
 XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
@@ -69,7 +68,7 @@ XSD_URL_PATH = '/bind10/statistics/xsd'
 XSL_URL_PATH = '/bind10/statistics/xsl'
 XSL_URL_PATH = '/bind10/statistics/xsl'
 # TODO: This should be considered later.
 # TODO: This should be considered later.
 XSD_NAMESPACE = 'http://bind10.isc.org' + XSD_URL_PATH
 XSD_NAMESPACE = 'http://bind10.isc.org' + XSD_URL_PATH
-DEFAULT_CONFIG = dict(listen_on=[('127.0.0.1', 8000)])
+DEFAULT_CONFIG = dict(version=0, listen_on=[('127.0.0.1', 8000)])
 
 
 # Assign this process name
 # Assign this process name
 isc.util.process.rename()
 isc.util.process.rename()
@@ -161,8 +160,6 @@ class StatsHttpd:
         self.httpd = []
         self.httpd = []
         self.open_mccs()
         self.open_mccs()
         self.load_config()
         self.load_config()
-        self.load_templates()
-        self.open_httpd()
 
 
     def open_mccs(self):
     def open_mccs(self):
         """Opens a ModuleCCSession object"""
         """Opens a ModuleCCSession object"""
@@ -171,10 +168,6 @@ class StatsHttpd:
         self.mccs = isc.config.ModuleCCSession(
         self.mccs = isc.config.ModuleCCSession(
             SPECFILE_LOCATION, self.config_handler, self.command_handler)
             SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self.cc_session = self.mccs._session
         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(SCHEMA_SPECFILE_LOCATION)
-        self.stats_config_spec = self.stats_module_spec.get_config_spec()
-        self.stats_module_name = self.stats_module_spec.get_module_name()
 
 
     def close_mccs(self):
     def close_mccs(self):
         """Closes a ModuleCCSession object"""
         """Closes a ModuleCCSession object"""
@@ -208,45 +201,41 @@ class StatsHttpd:
         for addr in self.http_addrs:
         for addr in self.http_addrs:
             self.httpd.append(self._open_httpd(addr))
             self.httpd.append(self._open_httpd(addr))
 
 
-    def _open_httpd(self, server_address, address_family=None):
+    def _open_httpd(self, server_address):
+        httpd = None
         try:
         try:
-            # try IPv6 at first
-            if address_family is not None:
-                HttpServer.address_family = address_family
-            elif socket.has_ipv6:
-                HttpServer.address_family = socket.AF_INET6
+            # get address family for the server_address before
+            # creating HttpServer object
+            address_family = socket.getaddrinfo(*server_address)[0][0]
+            HttpServer.address_family = address_family
             httpd = HttpServer(
             httpd = HttpServer(
                 server_address, HttpHandler,
                 server_address, HttpHandler,
                 self.xml_handler, self.xsd_handler, self.xsl_handler,
                 self.xml_handler, self.xsd_handler, self.xsl_handler,
                 self.write_log)
                 self.write_log)
-        except (socket.gaierror, socket.error,
-                OverflowError, TypeError) as err:
-            # try IPv4 next
-            if HttpServer.address_family == socket.AF_INET6:
-                httpd = self._open_httpd(server_address, socket.AF_INET)
-            else:
-                raise HttpServerError(
-                    "Invalid address %s, port %s: %s: %s" %
-                    (server_address[0], server_address[1],
-                     err.__class__.__name__, err))
-        else:
             logger.info(STATHTTPD_STARTED, server_address[0],
             logger.info(STATHTTPD_STARTED, server_address[0],
                         server_address[1])
                         server_address[1])
-        return httpd
+            return httpd
+        except (socket.gaierror, socket.error,
+                OverflowError, TypeError) as err:
+           if httpd:
+                httpd.server_close()
+           raise HttpServerError(
+               "Invalid address %s, port %s: %s: %s" %
+               (server_address[0], server_address[1],
+                err.__class__.__name__, err))
 
 
     def close_httpd(self):
     def close_httpd(self):
         """Closes sockets for HTTP"""
         """Closes sockets for HTTP"""
-        if len(self.httpd) == 0:
-            return
-        for ht in self.httpd:
+        while len(self.httpd)>0:
+            ht = self.httpd.pop()
             logger.info(STATHTTPD_CLOSING, ht.server_address[0],
             logger.info(STATHTTPD_CLOSING, ht.server_address[0],
                         ht.server_address[1])
                         ht.server_address[1])
             ht.server_close()
             ht.server_close()
-        self.httpd = []
 
 
     def start(self):
     def start(self):
         """Starts StatsHttpd objects to run. Waiting for client
         """Starts StatsHttpd objects to run. Waiting for client
         requests by using select.select functions"""
         requests by using select.select functions"""
+        self.open_httpd()
         self.mccs.start()
         self.mccs.start()
         self.running = True
         self.running = True
         while self.running:
         while self.running:
@@ -310,7 +299,8 @@ class StatsHttpd:
         except HttpServerError as err:
         except HttpServerError as err:
             logger.error(STATHTTPD_SERVER_ERROR, err)
             logger.error(STATHTTPD_SERVER_ERROR, err)
             # restore old config
             # restore old config
-            self.config_handler(old_config)
+            self.load_config(old_config)
+            self.open_httpd()
             return isc.config.ccsession.create_answer(
             return isc.config.ccsession.create_answer(
                 1, "[b10-stats-httpd] %s" % err)
                 1, "[b10-stats-httpd] %s" % err)
         else:
         else:
@@ -341,8 +331,7 @@ class StatsHttpd:
         the data which obtains from it"""
         the data which obtains from it"""
         try:
         try:
             seq = self.cc_session.group_sendmsg(
             seq = self.cc_session.group_sendmsg(
-                isc.config.ccsession.create_command('show'),
-                self.stats_module_name)
+                isc.config.ccsession.create_command('show'), 'Stats')
             (answer, env) = self.cc_session.group_recvmsg(False, seq)
             (answer, env) = self.cc_session.group_recvmsg(False, seq)
             if answer:
             if answer:
                 (rcode, value) = isc.config.ccsession.parse_answer(answer)
                 (rcode, value) = isc.config.ccsession.parse_answer(answer)
@@ -357,34 +346,82 @@ class StatsHttpd:
                 raise StatsHttpdError("Stats module: %s" % str(value))
                 raise StatsHttpdError("Stats module: %s" % str(value))
 
 
     def get_stats_spec(self):
     def get_stats_spec(self):
-        """Just returns spec data"""
-        return self.stats_config_spec
-
-    def load_templates(self):
-        """Setup the bodies of XSD and XSL documents to be responds to
-        HTTP clients. Before that it also creates XML tag structures by
-        using xml.etree.ElementTree.Element class and substitutes
-        concrete strings with parameters embed in the string.Template
-        object."""
+        """Requests statistics data to the Stats daemon and returns
+        the data which obtains from it"""
+        try:
+            seq = self.cc_session.group_sendmsg(
+                isc.config.ccsession.create_command('showschema'), 'Stats')
+            (answer, env) = self.cc_session.group_recvmsg(False, seq)
+            if answer:
+                (rcode, value) = isc.config.ccsession.parse_answer(answer)
+                if rcode == 0:
+                    return value
+                else:
+                    raise StatsHttpdError("Stats module: %s" % str(value))
+        except (isc.cc.session.SessionTimeout,
+                isc.cc.session.SessionError) as err:
+            raise StatsHttpdError("%s: %s" %
+                                  (err.__class__.__name__, err))
+
+    def xml_handler(self):
+        """Handler which requests to Stats daemon to obtain statistics
+        data and returns the body of XML document"""
+        xml_list=[]
+        for (mod, spec) in self.get_stats_data().items():
+            if not spec: continue
+            elem1 = xml.etree.ElementTree.Element(str(mod))
+            for (k, v) in spec.items():
+                elem2 = xml.etree.ElementTree.Element(str(k))
+                elem2.text = str(v)
+                elem1.append(elem2)
+            # The coding conversion is tricky. xml..tostring() of Python 3.2
+            # returns bytes (not string) regardless of the coding, while
+            # tostring() of Python 3.1 returns a string.  To support both
+            # cases transparently, we first make sure tostring() returns
+            # bytes by specifying utf-8 and then convert the result to a
+            # plain string (code below assume it).
+            xml_list.append(
+                str(xml.etree.ElementTree.tostring(elem1, encoding='utf-8'),
+                    encoding='us-ascii'))
+        xml_string = "".join(xml_list)
+        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 xsd_handler(self):
+        """Handler which just returns the body of XSD document"""
         # for XSD
         # for XSD
         xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
         xsd_root = xml.etree.ElementTree.Element("all") # started with "all" tag
-        for item in self.get_stats_spec():
-            element = xml.etree.ElementTree.Element(
-                "element",
-                dict( name=item["item_name"],
-                      type=item["item_type"] if item["item_type"].lower() != 'real' else 'float',
-                      minOccurs="1",
-                      maxOccurs="1" ),
-                )
-            annotation = xml.etree.ElementTree.Element("annotation")
-            appinfo = xml.etree.ElementTree.Element("appinfo")
-            documentation = xml.etree.ElementTree.Element("documentation")
-            appinfo.text = item["item_title"]
-            documentation.text = item["item_description"]
-            annotation.append(appinfo)
-            annotation.append(documentation)
-            element.append(annotation)
-            xsd_root.append(element)
+        for (mod, spec) in self.get_stats_spec().items():
+            if not spec: continue
+            alltag = xml.etree.ElementTree.Element("all")
+            for item in spec:
+                element = xml.etree.ElementTree.Element(
+                    "element",
+                    dict( name=item["item_name"],
+                          type=item["item_type"] if item["item_type"].lower() != 'real' else 'float',
+                          minOccurs="1",
+                          maxOccurs="1" ),
+                    )
+                annotation = xml.etree.ElementTree.Element("annotation")
+                appinfo = xml.etree.ElementTree.Element("appinfo")
+                documentation = xml.etree.ElementTree.Element("documentation")
+                appinfo.text = item["item_title"]
+                documentation.text = item["item_description"]
+                annotation.append(appinfo)
+                annotation.append(documentation)
+                element.append(annotation)
+                alltag.append(element)
+        
+            complextype = xml.etree.ElementTree.Element("complexType")
+            complextype.append(alltag)
+            mod_element = xml.etree.ElementTree.Element("element", { "name" : mod })
+            mod_element.append(complextype)
+            xsd_root.append(mod_element)
         # The coding conversion is tricky. xml..tostring() of Python 3.2
         # The coding conversion is tricky. xml..tostring() of Python 3.2
         # returns bytes (not string) regardless of the coding, while
         # returns bytes (not string) regardless of the coding, while
         # tostring() of Python 3.1 returns a string.  To support both
         # tostring() of Python 3.1 returns a string.  To support both
@@ -398,25 +435,33 @@ class StatsHttpd:
             xsd_namespace=XSD_NAMESPACE
             xsd_namespace=XSD_NAMESPACE
             )
             )
         assert self.xsd_body is not None
         assert self.xsd_body is not None
+        return self.xsd_body
 
 
+    def xsl_handler(self):
+        """Handler which just returns the body of XSL document"""
         # for XSL
         # for XSL
         xsd_root = xml.etree.ElementTree.Element(
         xsd_root = xml.etree.ElementTree.Element(
             "xsl:template",
             "xsl:template",
             dict(match="*")) # started with xml:template tag
             dict(match="*")) # started with xml:template tag
-        for item in self.get_stats_spec():
-            tr = xml.etree.ElementTree.Element("tr")
-            td1 = xml.etree.ElementTree.Element(
-                "td", { "class" : "title",
-                        "title" : item["item_description"] })
-            td1.text = item["item_title"]
-            td2 = xml.etree.ElementTree.Element("td")
-            xsl_valueof = xml.etree.ElementTree.Element(
-                "xsl:value-of",
-                dict(select=item["item_name"]))
-            td2.append(xsl_valueof)
-            tr.append(td1)
-            tr.append(td2)
-            xsd_root.append(tr)
+        for (mod, spec) in self.get_stats_spec().items():
+            if not spec: continue
+            for item in spec:
+                tr = xml.etree.ElementTree.Element("tr")
+                td0 = xml.etree.ElementTree.Element("td")
+                td0.text = str(mod)
+                td1 = xml.etree.ElementTree.Element(
+                    "td", { "class" : "title",
+                            "title" : item["item_description"] })
+                td1.text = item["item_title"]
+                td2 = xml.etree.ElementTree.Element("td")
+                xsl_valueof = xml.etree.ElementTree.Element(
+                    "xsl:value-of",
+                    dict(select=mod+'/'+item["item_name"]))
+                td2.append(xsl_valueof)
+                tr.append(td0)
+                tr.append(td1)
+                tr.append(td2)
+                xsd_root.append(tr)
         # The coding conversion is tricky. xml..tostring() of Python 3.2
         # The coding conversion is tricky. xml..tostring() of Python 3.2
         # returns bytes (not string) regardless of the coding, while
         # returns bytes (not string) regardless of the coding, while
         # tostring() of Python 3.1 returns a string.  To support both
         # tostring() of Python 3.1 returns a string.  To support both
@@ -429,47 +474,15 @@ class StatsHttpd:
             xsl_string=xsl_string,
             xsl_string=xsl_string,
             xsd_namespace=XSD_NAMESPACE)
             xsd_namespace=XSD_NAMESPACE)
         assert self.xsl_body is not None
         assert self.xsl_body is not None
-
-    def xml_handler(self):
-        """Handler which requests to Stats daemon to obtain statistics
-        data and returns the body of XML document"""
-        xml_list=[]
-        for (k, v) in self.get_stats_data().items():
-            (k, v) = (str(k), str(v))
-            elem = xml.etree.ElementTree.Element(k)
-            elem.text = v
-            # The coding conversion is tricky. xml..tostring() of Python 3.2
-            # returns bytes (not string) regardless of the coding, while
-            # tostring() of Python 3.1 returns a string.  To support both
-            # cases transparently, we first make sure tostring() returns
-            # bytes by specifying utf-8 and then convert the result to a
-            # plain string (code below assume it).
-            xml_list.append(
-                str(xml.etree.ElementTree.tostring(elem, encoding='utf-8'),
-                    encoding='us-ascii'))
-        xml_string = "".join(xml_list)
-        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 xsd_handler(self):
-        """Handler which just returns the body of XSD document"""
-        return self.xsd_body
-
-    def xsl_handler(self):
-        """Handler which just returns the body of XSL document"""
         return self.xsl_body
         return self.xsl_body
 
 
     def open_template(self, file_name):
     def open_template(self, file_name):
         """It opens a template file, and it loads all lines to a
         """It opens a template file, and it loads all lines to a
         string variable and returns string. Template object includes
         string variable and returns string. Template object includes
         the variable. Limitation of a file size isn't needed there."""
         the variable. Limitation of a file size isn't needed there."""
-        lines = "".join(
-            open(file_name, 'r').readlines())
+        f = open(file_name, 'r')
+        lines = "".join(f.readlines())
+        f.close()
         assert lines is not None
         assert lines is not None
         return string.Template(lines)
         return string.Template(lines)