Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / RESTHTTPServer.py @ 10b207d4

History | View | Annotate | Download (6.3 kB)

1
#
2
#
3

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

    
19
"""RESTfull HTTPS Server module.
20

21
"""
22

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

    
29
from ganeti import constants
30
from ganeti import errors
31
from ganeti import logger
32
from ganeti import rpc
33
from ganeti import serializer
34

    
35
from ganeti.rapi import connector
36
from ganeti.rapi import httperror
37

    
38

    
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):
153
  """REST Request Handler Class.
154

155
  """
156
  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)
163
    self._resmap = connector.Mapper()
164

    
165
  def handle_one_request(self):
166
    """Handle a single REST request.
167

168
    """
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
179

    
180
    try:
181
      (HandlerClass, items, args) = self._resmap.getController(self.path)
182
      handler = HandlerClass(self, items, args)
183

    
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!).
222

223
    """
224
    self.server.httplog.LogRequest(self, format, *args)
225

    
226

    
227
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)
233
  try:
234
    httpd.serve_forever()
235
  finally:
236
    httpd.server_close()