Switch RAPI to ganeti.http module
authorOleksiy Mishchenko <oleksiy@google.com>
Thu, 24 Jul 2008 16:34:49 +0000 (16:34 +0000)
committerOleksiy Mishchenko <oleksiy@google.com>
Thu, 24 Jul 2008 16:34:49 +0000 (16:34 +0000)
Reviewed-by: imsnah

Makefile.am
lib/http.py
lib/rapi/RESTHTTPServer.py
lib/rapi/connector.py
test/ganeti.http_unittest.py [new file with mode: 0755]
test/ganeti.rapi.resources_unittest.py

index 07332a8..7507f85 100644 (file)
@@ -195,6 +195,7 @@ dist_TESTS = \
        test/ganeti.serializer_unittest.py \
        test/ganeti.workerpool_unittest.py \
        test/ganeti.rapi.resources_unittest.py \
+       test/ganeti.http_unittest.py \
        test/ganeti.constants_unittest.py
 
 nodist_TESTS =
index a9febda..8f78d57 100644 (file)
@@ -108,6 +108,7 @@ class ApacheLogfile:
       # Message
       format % args,
       ))
+    self._fd.flush()
 
   def _FormatCurrentTime(self):
     """Formats current time in Common Log Format.
index bbea1a4..11c83b7 100644 (file)
 
 """
 
-import BaseHTTPServer
-import OpenSSL
-import re
-import socket
-import time
-
 from ganeti import constants
+from ganeti import http
 from ganeti import errors
-from ganeti import logger
 from ganeti import rpc
-from ganeti import serializer
-
 from ganeti.rapi import connector
 from ganeti.rapi import httperror
 
 
-class HttpLogfile:
-  """Utility class to write HTTP server log files.
-
-  The written format is the "Common Log Format" as defined by Apache:
-  http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
-
-  """
-  MONTHNAME = [None,
-               'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
-               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
-  def __init__(self, path):
-    self._fd = open(path, 'a', 1)
-
-  def __del__(self):
-    try:
-      self.Close()
-    except:
-      # Swallow exceptions
-      pass
-
-  def Close(self):
-    if self._fd is not None:
-      self._fd.close()
-      self._fd = None
-
-  def LogRequest(self, request, format, *args):
-    if self._fd is None:
-      raise errors.ProgrammerError("Logfile already closed")
-
-    request_time = self._FormatCurrentTime()
-
-    self._fd.write("%s %s %s [%s] %s\n" % (
-      # Remote host address
-      request.address_string(),
-
-      # RFC1413 identity (identd)
-      "-",
-
-      # Remote user
-      "-",
-
-      # Request time
-      request_time,
-
-      # Message
-      format % args,
-      ))
-
-  def _FormatCurrentTime(self):
-    """Formats current time in Common Log Format.
-
-    """
-    return self._FormatLogTime(time.time())
-
-  def _FormatLogTime(self, seconds):
-    """Formats time for Common Log Format.
-
-    All timestamps are logged in the UTC timezone.
-
-    Args:
-    - seconds: Time in seconds since the epoch
-
-    """
-    (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
-    format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
-    return time.strftime(format, tm)
-
-
-class RESTHTTPServer(BaseHTTPServer.HTTPServer):
-  """Class to provide an HTTP/HTTPS server.
-
-  """
-  allow_reuse_address = True
-
-  def __init__(self, server_address, HandlerClass, options):
-    """REST Server Constructor.
-
-    Args:
-      server_address: a touple containing:
-        ip: a string with IP address, localhost if empty string
-        port: port number, integer
-      HandlerClass: HTTPRequestHandler object
-      options: Command-line options
-
-    """
-    logger.SetupLogging(debug=options.debug, program='ganeti-rapi')
-
-    self.httplog = HttpLogfile(constants.LOG_RAPIACCESS)
-
-    BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
-    if options.ssl:
-      # Set up SSL
-      context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
-      context.use_privatekey_file(options.ssl_key)
-      context.use_certificate_file(options.ssl_cert)
-      self.socket = OpenSSL.SSL.Connection(context,
-                                           socket.socket(self.address_family,
-                                           self.socket_type))
-    else:
-      self.socket = socket.socket(self.address_family, self.socket_type)
-
-    self.server_bind()
-    self.server_activate()
-
-
-class JsonResponse:
-  CONTENT_TYPE = "application/json"
-
-  def Encode(self, data):
-    return serializer.DumpJson(data)
-
-
-class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class RESTRequestHandler(http.HTTPRequestHandler):
   """REST Request Handler Class.
 
   """
   def setup(self):
-    """Setup secure read and write file objects.
-
-    """
-    self.connection = self.request
-    self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
-    self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+    super(RESTRequestHandler, self).setup()
     self._resmap = connector.Mapper()
-
-  def handle_one_request(self):
-    """Handle a single REST request.
+  
+  def HandleRequest(self):
+    """ Handels a request.
 
     """
-    self.raw_requestline = None
-    try:
-      self.raw_requestline = self.rfile.readline()
-    except OpenSSL.SSL.Error, ex:
-      logger.Error("Error in SSL: %s" % str(ex))
-    if not self.raw_requestline:
-      self.close_connection = 1
-      return
-    if not self.parse_request(): # An error code has been sent, just exit
-      return
+    (HandlerClass, items, args) = self._resmap.getController(self.path)
+    handler = HandlerClass(self, items, args)
 
+    command = self.command.upper()
     try:
-      (HandlerClass, items, args) = self._resmap.getController(self.path)
-      handler = HandlerClass(self, items, args)
+      fn = getattr(handler, command)
+    except AttributeError, err:
+      raise httperror.HTTPBadRequest()
 
-      command = self.command.upper()
-      try:
-        fn = getattr(handler, command)
-      except AttributeError, err:
-        raise httperror.HTTPBadRequest()
-
-      try:
-        result = fn()
-
-      except errors.OpPrereqError, err:
-        # TODO: "Not found" is not always the correct error. Ganeti's core must
-        # differentiate between different error types.
-        raise httperror.HTTPNotFound(message=str(err))
-
-      encoder = JsonResponse()
-      encoded_result = encoder.Encode(result)
-
-      self.send_response(200)
-      self.send_header("Content-Type", encoder.CONTENT_TYPE)
-      self.end_headers()
-      self.wfile.write(encoded_result)
-
-    except httperror.HTTPException, err:
-      self.send_error(err.code, message=err.message)
-
-    except Exception, err:
-      self.send_error(httperror.HTTPInternalError.code, message=str(err))
-
-  def log_message(self, format, *args):
-    """Log an arbitrary message.
-
-    This is used by all other logging functions.
-
-    The first argument, FORMAT, is a format string for the
-    message to be logged.  If the format string contains
-    any % escapes requiring parameters, they should be
-    specified as subsequent arguments (it's just like
-    printf!).
+    try:
+      result = fn()
 
-    """
-    self.server.httplog.LogRequest(self, format, *args)
+    except errors.OpPrereqError, err:
+      # TODO: "Not found" is not always the correct error. Ganeti's core must
+      # differentiate between different error types.
+      raise httperror.HTTPNotFound(message=str(err))
+    
+    return result
 
 
 def start(options):
-  # Disable signal handlers, otherwise we can't exit the daemon in a clean way
-  # by sending a signal.
-  rpc.install_twisted_signal_handlers = False
-
-  httpd = RESTHTTPServer(("", options.port), RESTRequestHandler, options)
+  log_fd = open(constants.LOG_RAPIACCESS, 'a')
   try:
-    httpd.serve_forever()
+    apache_log = http.ApacheLogfile(log_fd)
+    httpd = http.HTTPServer(("", options.port), RESTRequestHandler,
+                            httplog=apache_log)
+    try:
+      httpd.serve_forever()
+    finally:
+      httpd.server_close()
+
   finally:
-    httpd.server_close()
+    log_fd.close()
index 7820654..a80250c 100644 (file)
@@ -26,9 +26,9 @@ import cgi
 import re
 
 from ganeti import constants 
+from ganeti import http 
 
 from ganeti.rapi import baserlib 
-from ganeti.rapi import httperror 
 from ganeti.rapi import rlib1
 from ganeti.rapi import rlib2
 
@@ -85,10 +85,10 @@ class Mapper:
         result = (handler, [], args)
         break
 
-    if result is not None:
+    if result:
       return result
     else:
-      raise httperror.HTTPNotFound()
+      raise http.HTTPNotFound()
 
 
 class R_root(baserlib.R_Generic):
diff --git a/test/ganeti.http_unittest.py b/test/ganeti.http_unittest.py
new file mode 100755 (executable)
index 0000000..448fca2
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2007, 2008 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the http module"""
+
+
+import os
+import unittest
+import tempfile
+import time
+
+from ganeti import http
+
+
+class HttpLogfileTests(unittest.TestCase):
+  """Rests for ApacheLogfile class."""
+
+  class FakeRequest:
+    FAKE_ADDRESS = "1.2.3.4"
+
+    def address_string(self):
+      return self.FAKE_ADDRESS
+
+  def setUp(self):
+    self.tmpfile = tempfile.NamedTemporaryFile()
+    self.logfile = http.ApacheLogfile(self.tmpfile)
+
+  def tearDown(self):
+    self.tmpfile.close()
+
+  def testFormatLogTime(self):
+    self._TestInTimezone(1208646123.0, "Europe/London",
+                         "19/Apr/2008:23:02:03 +0000")
+    self._TestInTimezone(1208646123, "Europe/Zurich",
+                         "19/Apr/2008:23:02:03 +0000")
+    self._TestInTimezone(1208646123, "Australia/Sydney",
+                         "19/Apr/2008:23:02:03 +0000")
+
+  def _TestInTimezone(self, seconds, timezone, expected):
+    """Tests HttpLogfile._FormatLogTime with a specific timezone
+
+    """
+    # Preserve environment
+    old_TZ = os.environ.get("TZ", None)
+    try:
+      os.environ["TZ"] = timezone
+      time.tzset()
+      result = self.logfile._FormatLogTime(seconds)
+    finally:
+      # Restore environment
+      if old_TZ is not None:
+        os.environ["TZ"] = old_TZ
+      elif "TZ" in os.environ:
+        del os.environ["TZ"]
+      time.tzset()
+
+    self.assertEqual(result, expected)
+
+
+  def testLogRequest(self):
+    request = self.FakeRequest()
+    self.logfile.LogRequest(request, "This is only a %s", "test")
+
+
+if __name__ == '__main__':
+  unittest.main()
index 461cfe9..1385eab 100755 (executable)
 """Script for unittesting the RAPI resources module"""
 
 
-import os
 import unittest
 import tempfile
-import time
 
 from ganeti import errors
+from ganeti import http
+
 from ganeti.rapi import connector 
-from ganeti.rapi import httperror
 from ganeti.rapi import RESTHTTPServer
 from ganeti.rapi import rlib1 
 
@@ -44,7 +43,7 @@ class MapperTests(unittest.TestCase):
     self.assertEquals(self.map.getController(uri), result)
 
   def _TestFailingUri(self, uri):
-    self.failUnlessRaises(httperror.HTTPNotFound, self.map.getController, uri)
+    self.failUnlessRaises(http.HTTPNotFound, self.map.getController, uri)
 
   def testMapper(self):
     """Testing Mapper"""
@@ -86,61 +85,5 @@ class R_RootTests(unittest.TestCase):
     self.assertEquals(self.root.GET(), expected)
 
 
-class HttpLogfileTests(unittest.TestCase):
-  """Rests for HttpLogfile class."""
-
-  class FakeRequest:
-    FAKE_ADDRESS = "1.2.3.4"
-
-    def address_string(self):
-      return self.FAKE_ADDRESS
-
-  def setUp(self):
-    self.tmpfile = tempfile.NamedTemporaryFile()
-    self.logfile = RESTHTTPServer.HttpLogfile(self.tmpfile.name)
-
-  def testFormatLogTime(self):
-    self._TestInTimezone(1208646123.0, "Europe/London",
-                         "19/Apr/2008:23:02:03 +0000")
-    self._TestInTimezone(1208646123, "Europe/Zurich",
-                         "19/Apr/2008:23:02:03 +0000")
-    self._TestInTimezone(1208646123, "Australia/Sydney",
-                         "19/Apr/2008:23:02:03 +0000")
-
-  def _TestInTimezone(self, seconds, timezone, expected):
-    """Tests HttpLogfile._FormatLogTime with a specific timezone
-
-    """
-    # Preserve environment
-    old_TZ = os.environ.get("TZ", None)
-    try:
-      os.environ["TZ"] = timezone
-      time.tzset()
-      result = self.logfile._FormatLogTime(seconds)
-    finally:
-      # Restore environment
-      if old_TZ is not None:
-        os.environ["TZ"] = old_TZ
-      elif "TZ" in os.environ:
-        del os.environ["TZ"]
-      time.tzset()
-
-    self.assertEqual(result, expected)
-
-  def testClose(self):
-    self.logfile.Close()
-
-  def testCloseAndWrite(self):
-    request = self.FakeRequest()
-    self.logfile.Close()
-    self.assertRaises(errors.ProgrammerError, self.logfile.LogRequest,
-                      request, "Message")
-
-  def testLogRequest(self):
-    request = self.FakeRequest()
-    self.logfile.LogRequest(request, "This is only a %s", "test")
-    self.logfile.Close()
-
-
 if __name__ == '__main__':
   unittest.main()