Revision a0638838 lib/rapi/RESTHTTPServer.py

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()

Also available in: Unified diff