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.
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.
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
19 """RESTfull HTTPS Server module.
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
35 from ganeti.rapi import connector
36 from ganeti.rapi import httperror
40 """Utility class to write HTTP server log files.
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
47 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
48 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
50 def __init__(self, path):
51 self._fd = open(path, 'a', 1)
61 if self._fd is not None:
65 def LogRequest(self, request, format, *args):
67 raise errors.ProgrammerError("Logfile already closed")
69 request_time = self._FormatCurrentTime()
71 self._fd.write("%s %s %s [%s] %s\n" % (
73 request.address_string(),
75 # RFC1413 identity (identd)
88 def _FormatCurrentTime(self):
89 """Formats current time in Common Log Format.
92 return self._FormatLogTime(time.time())
94 def _FormatLogTime(self, seconds):
95 """Formats time for Common Log Format.
97 All timestamps are logged in the UTC timezone.
100 - seconds: Time in seconds since the epoch
103 (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
104 format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
105 return time.strftime(format, tm)
108 class RESTHTTPServer(BaseHTTPServer.HTTPServer):
109 """Class to provide an HTTP/HTTPS server.
112 allow_reuse_address = True
114 def __init__(self, server_address, HandlerClass, options):
115 """REST Server Constructor.
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
125 logger.SetupLogging(debug=options.debug, program='ganeti-rapi')
127 self.httplog = HttpLogfile(constants.LOG_RAPIACCESS)
129 BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
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,
139 self.socket = socket.socket(self.address_family, self.socket_type)
142 self.server_activate()
146 CONTENT_TYPE = "application/json"
148 def Encode(self, data):
149 return serializer.DumpJson(data)
152 class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
153 """REST Request Handler Class.
157 """Setup secure read and write file objects.
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()
165 def handle_one_request(self):
166 """Handle a single REST request.
169 self.raw_requestline = None
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
177 if not self.parse_request(): # An error code has been sent, just exit
181 (HandlerClass, items, args) = self._resmap.getController(self.path)
182 handler = HandlerClass(self, items, args)
184 command = self.command.upper()
186 fn = getattr(handler, command)
187 except AttributeError, err:
188 raise httperror.HTTPBadRequest()
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))
198 encoder = JsonResponse()
199 encoded_result = encoder.Encode(result)
201 self.send_response(200)
202 self.send_header("Content-Type", encoder.CONTENT_TYPE)
204 self.wfile.write(encoded_result)
206 except httperror.HTTPException, err:
207 self.send_error(err.code, message=err.message)
209 except Exception, err:
210 self.send_error(httperror.HTTPInternalError.code, message=str(err))
212 def log_message(self, format, *args):
213 """Log an arbitrary message.
215 This is used by all other logging functions.
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
224 self.server.httplog.LogRequest(self, format, *args)
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
232 httpd = RESTHTTPServer(("", options.port), RESTRequestHandler, options)
234 httpd.serve_forever()