Skip to content
Snippets Groups Projects
Commit a0638838 authored by Oleksiy Mishchenko's avatar Oleksiy Mishchenko
Browse files

Switch RAPI to ganeti.http module

Reviewed-by: imsnah
parent 0ad64cf8
No related branches found
No related tags found
No related merge requests found
......@@ -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 =
......
......@@ -108,6 +108,7 @@ class ApacheLogfile:
# Message
format % args,
))
self._fd.flush()
def _FormatCurrentTime(self):
"""Formats current time in Common Log Format.
......
......@@ -20,217 +20,56 @@
"""
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()
......@@ -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):
......
#!/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()
......@@ -22,14 +22,13 @@
"""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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment