Revision a0638838

b/Makefile.am
195 195
	test/ganeti.serializer_unittest.py \
196 196
	test/ganeti.workerpool_unittest.py \
197 197
	test/ganeti.rapi.resources_unittest.py \
198
	test/ganeti.http_unittest.py \
198 199
	test/ganeti.constants_unittest.py
199 200

  
200 201
nodist_TESTS =
b/lib/http.py
108 108
      # Message
109 109
      format % args,
110 110
      ))
111
    self._fd.flush()
111 112

  
112 113
  def _FormatCurrentTime(self):
113 114
    """Formats current time in Common Log Format.
b/lib/rapi/RESTHTTPServer.py
20 20

  
21 21
"""
22 22

  
23
import BaseHTTPServer
24
import OpenSSL
25
import re
26
import socket
27
import time
28

  
29 23
from ganeti import constants
24
from ganeti import http
30 25
from ganeti import errors
31
from ganeti import logger
32 26
from ganeti import rpc
33
from ganeti import serializer
34

  
35 27
from ganeti.rapi import connector
36 28
from ganeti.rapi import httperror
37 29

  
38 30

  
39
class HttpLogfile:
40
  """Utility class to write HTTP server log files.
41

  
42
  The written format is the "Common Log Format" as defined by Apache:
43
  http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
44

  
45
  """
46
  MONTHNAME = [None,
47
               'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
48
               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
49

  
50
  def __init__(self, path):
51
    self._fd = open(path, 'a', 1)
52

  
53
  def __del__(self):
54
    try:
55
      self.Close()
56
    except:
57
      # Swallow exceptions
58
      pass
59

  
60
  def Close(self):
61
    if self._fd is not None:
62
      self._fd.close()
63
      self._fd = None
64

  
65
  def LogRequest(self, request, format, *args):
66
    if self._fd is None:
67
      raise errors.ProgrammerError("Logfile already closed")
68

  
69
    request_time = self._FormatCurrentTime()
70

  
71
    self._fd.write("%s %s %s [%s] %s\n" % (
72
      # Remote host address
73
      request.address_string(),
74

  
75
      # RFC1413 identity (identd)
76
      "-",
77

  
78
      # Remote user
79
      "-",
80

  
81
      # Request time
82
      request_time,
83

  
84
      # Message
85
      format % args,
86
      ))
87

  
88
  def _FormatCurrentTime(self):
89
    """Formats current time in Common Log Format.
90

  
91
    """
92
    return self._FormatLogTime(time.time())
93

  
94
  def _FormatLogTime(self, seconds):
95
    """Formats time for Common Log Format.
96

  
97
    All timestamps are logged in the UTC timezone.
98

  
99
    Args:
100
    - seconds: Time in seconds since the epoch
101

  
102
    """
103
    (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
104
    format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
105
    return time.strftime(format, tm)
106

  
107

  
108
class RESTHTTPServer(BaseHTTPServer.HTTPServer):
109
  """Class to provide an HTTP/HTTPS server.
110

  
111
  """
112
  allow_reuse_address = True
113

  
114
  def __init__(self, server_address, HandlerClass, options):
115
    """REST Server Constructor.
116

  
117
    Args:
118
      server_address: a touple containing:
119
        ip: a string with IP address, localhost if empty string
120
        port: port number, integer
121
      HandlerClass: HTTPRequestHandler object
122
      options: Command-line options
123

  
124
    """
125
    logger.SetupLogging(debug=options.debug, program='ganeti-rapi')
126

  
127
    self.httplog = HttpLogfile(constants.LOG_RAPIACCESS)
128

  
129
    BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
130
    if options.ssl:
131
      # Set up SSL
132
      context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
133
      context.use_privatekey_file(options.ssl_key)
134
      context.use_certificate_file(options.ssl_cert)
135
      self.socket = OpenSSL.SSL.Connection(context,
136
                                           socket.socket(self.address_family,
137
                                           self.socket_type))
138
    else:
139
      self.socket = socket.socket(self.address_family, self.socket_type)
140

  
141
    self.server_bind()
142
    self.server_activate()
143

  
144

  
145
class JsonResponse:
146
  CONTENT_TYPE = "application/json"
147

  
148
  def Encode(self, data):
149
    return serializer.DumpJson(data)
150

  
151

  
152
class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
31
class RESTRequestHandler(http.HTTPRequestHandler):
153 32
  """REST Request Handler Class.
154 33

  
155 34
  """
156 35
  def setup(self):
157
    """Setup secure read and write file objects.
158

  
159
    """
160
    self.connection = self.request
161
    self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
162
    self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
36
    super(RESTRequestHandler, self).setup()
163 37
    self._resmap = connector.Mapper()
164

  
165
  def handle_one_request(self):
166
    """Handle a single REST request.
38
  
39
  def HandleRequest(self):
40
    """ Handels a request.
167 41

  
168 42
    """
169
    self.raw_requestline = None
170
    try:
171
      self.raw_requestline = self.rfile.readline()
172
    except OpenSSL.SSL.Error, ex:
173
      logger.Error("Error in SSL: %s" % str(ex))
174
    if not self.raw_requestline:
175
      self.close_connection = 1
176
      return
177
    if not self.parse_request(): # An error code has been sent, just exit
178
      return
43
    (HandlerClass, items, args) = self._resmap.getController(self.path)
44
    handler = HandlerClass(self, items, args)
179 45

  
46
    command = self.command.upper()
180 47
    try:
181
      (HandlerClass, items, args) = self._resmap.getController(self.path)
182
      handler = HandlerClass(self, items, args)
48
      fn = getattr(handler, command)
49
    except AttributeError, err:
50
      raise httperror.HTTPBadRequest()
183 51

  
184
      command = self.command.upper()
185
      try:
186
        fn = getattr(handler, command)
187
      except AttributeError, err:
188
        raise httperror.HTTPBadRequest()
189

  
190
      try:
191
        result = fn()
192

  
193
      except errors.OpPrereqError, err:
194
        # TODO: "Not found" is not always the correct error. Ganeti's core must
195
        # differentiate between different error types.
196
        raise httperror.HTTPNotFound(message=str(err))
197

  
198
      encoder = JsonResponse()
199
      encoded_result = encoder.Encode(result)
200

  
201
      self.send_response(200)
202
      self.send_header("Content-Type", encoder.CONTENT_TYPE)
203
      self.end_headers()
204
      self.wfile.write(encoded_result)
205

  
206
    except httperror.HTTPException, err:
207
      self.send_error(err.code, message=err.message)
208

  
209
    except Exception, err:
210
      self.send_error(httperror.HTTPInternalError.code, message=str(err))
211

  
212
  def log_message(self, format, *args):
213
    """Log an arbitrary message.
214

  
215
    This is used by all other logging functions.
216

  
217
    The first argument, FORMAT, is a format string for the
218
    message to be logged.  If the format string contains
219
    any % escapes requiring parameters, they should be
220
    specified as subsequent arguments (it's just like
221
    printf!).
52
    try:
53
      result = fn()
222 54

  
223
    """
224
    self.server.httplog.LogRequest(self, format, *args)
55
    except errors.OpPrereqError, err:
56
      # TODO: "Not found" is not always the correct error. Ganeti's core must
57
      # differentiate between different error types.
58
      raise httperror.HTTPNotFound(message=str(err))
59
    
60
    return result
225 61

  
226 62

  
227 63
def start(options):
228
  # Disable signal handlers, otherwise we can't exit the daemon in a clean way
229
  # by sending a signal.
230
  rpc.install_twisted_signal_handlers = False
231

  
232
  httpd = RESTHTTPServer(("", options.port), RESTRequestHandler, options)
64
  log_fd = open(constants.LOG_RAPIACCESS, 'a')
233 65
  try:
234
    httpd.serve_forever()
66
    apache_log = http.ApacheLogfile(log_fd)
67
    httpd = http.HTTPServer(("", options.port), RESTRequestHandler,
68
                            httplog=apache_log)
69
    try:
70
      httpd.serve_forever()
71
    finally:
72
      httpd.server_close()
73

  
235 74
  finally:
236
    httpd.server_close()
75
    log_fd.close()
b/lib/rapi/connector.py
26 26
import re
27 27

  
28 28
from ganeti import constants 
29
from ganeti import http 
29 30

  
30 31
from ganeti.rapi import baserlib 
31
from ganeti.rapi import httperror 
32 32
from ganeti.rapi import rlib1
33 33
from ganeti.rapi import rlib2
34 34

  
......
85 85
        result = (handler, [], args)
86 86
        break
87 87

  
88
    if result is not None:
88
    if result:
89 89
      return result
90 90
    else:
91
      raise httperror.HTTPNotFound()
91
      raise http.HTTPNotFound()
92 92

  
93 93

  
94 94
class R_root(baserlib.R_Generic):
b/test/ganeti.http_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2007, 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
"""Script for unittesting the http module"""
23

  
24

  
25
import os
26
import unittest
27
import tempfile
28
import time
29

  
30
from ganeti import http
31

  
32

  
33
class HttpLogfileTests(unittest.TestCase):
34
  """Rests for ApacheLogfile class."""
35

  
36
  class FakeRequest:
37
    FAKE_ADDRESS = "1.2.3.4"
38

  
39
    def address_string(self):
40
      return self.FAKE_ADDRESS
41

  
42
  def setUp(self):
43
    self.tmpfile = tempfile.NamedTemporaryFile()
44
    self.logfile = http.ApacheLogfile(self.tmpfile)
45

  
46
  def tearDown(self):
47
    self.tmpfile.close()
48

  
49
  def testFormatLogTime(self):
50
    self._TestInTimezone(1208646123.0, "Europe/London",
51
                         "19/Apr/2008:23:02:03 +0000")
52
    self._TestInTimezone(1208646123, "Europe/Zurich",
53
                         "19/Apr/2008:23:02:03 +0000")
54
    self._TestInTimezone(1208646123, "Australia/Sydney",
55
                         "19/Apr/2008:23:02:03 +0000")
56

  
57
  def _TestInTimezone(self, seconds, timezone, expected):
58
    """Tests HttpLogfile._FormatLogTime with a specific timezone
59

  
60
    """
61
    # Preserve environment
62
    old_TZ = os.environ.get("TZ", None)
63
    try:
64
      os.environ["TZ"] = timezone
65
      time.tzset()
66
      result = self.logfile._FormatLogTime(seconds)
67
    finally:
68
      # Restore environment
69
      if old_TZ is not None:
70
        os.environ["TZ"] = old_TZ
71
      elif "TZ" in os.environ:
72
        del os.environ["TZ"]
73
      time.tzset()
74

  
75
    self.assertEqual(result, expected)
76

  
77

  
78
  def testLogRequest(self):
79
    request = self.FakeRequest()
80
    self.logfile.LogRequest(request, "This is only a %s", "test")
81

  
82

  
83
if __name__ == '__main__':
84
  unittest.main()
b/test/ganeti.rapi.resources_unittest.py
22 22
"""Script for unittesting the RAPI resources module"""
23 23

  
24 24

  
25
import os
26 25
import unittest
27 26
import tempfile
28
import time
29 27

  
30 28
from ganeti import errors
29
from ganeti import http
30

  
31 31
from ganeti.rapi import connector 
32
from ganeti.rapi import httperror
33 32
from ganeti.rapi import RESTHTTPServer
34 33
from ganeti.rapi import rlib1 
35 34

  
......
44 43
    self.assertEquals(self.map.getController(uri), result)
45 44

  
46 45
  def _TestFailingUri(self, uri):
47
    self.failUnlessRaises(httperror.HTTPNotFound, self.map.getController, uri)
46
    self.failUnlessRaises(http.HTTPNotFound, self.map.getController, uri)
48 47

  
49 48
  def testMapper(self):
50 49
    """Testing Mapper"""
......
86 85
    self.assertEquals(self.root.GET(), expected)
87 86

  
88 87

  
89
class HttpLogfileTests(unittest.TestCase):
90
  """Rests for HttpLogfile class."""
91

  
92
  class FakeRequest:
93
    FAKE_ADDRESS = "1.2.3.4"
94

  
95
    def address_string(self):
96
      return self.FAKE_ADDRESS
97

  
98
  def setUp(self):
99
    self.tmpfile = tempfile.NamedTemporaryFile()
100
    self.logfile = RESTHTTPServer.HttpLogfile(self.tmpfile.name)
101

  
102
  def testFormatLogTime(self):
103
    self._TestInTimezone(1208646123.0, "Europe/London",
104
                         "19/Apr/2008:23:02:03 +0000")
105
    self._TestInTimezone(1208646123, "Europe/Zurich",
106
                         "19/Apr/2008:23:02:03 +0000")
107
    self._TestInTimezone(1208646123, "Australia/Sydney",
108
                         "19/Apr/2008:23:02:03 +0000")
109

  
110
  def _TestInTimezone(self, seconds, timezone, expected):
111
    """Tests HttpLogfile._FormatLogTime with a specific timezone
112

  
113
    """
114
    # Preserve environment
115
    old_TZ = os.environ.get("TZ", None)
116
    try:
117
      os.environ["TZ"] = timezone
118
      time.tzset()
119
      result = self.logfile._FormatLogTime(seconds)
120
    finally:
121
      # Restore environment
122
      if old_TZ is not None:
123
        os.environ["TZ"] = old_TZ
124
      elif "TZ" in os.environ:
125
        del os.environ["TZ"]
126
      time.tzset()
127

  
128
    self.assertEqual(result, expected)
129

  
130
  def testClose(self):
131
    self.logfile.Close()
132

  
133
  def testCloseAndWrite(self):
134
    request = self.FakeRequest()
135
    self.logfile.Close()
136
    self.assertRaises(errors.ProgrammerError, self.logfile.LogRequest,
137
                      request, "Message")
138

  
139
  def testLogRequest(self):
140
    request = self.FakeRequest()
141
    self.logfile.LogRequest(request, "This is only a %s", "test")
142
    self.logfile.Close()
143

  
144

  
145 88
if __name__ == '__main__':
146 89
  unittest.main()

Also available in: Unified diff