4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
21 """ Ganeti Remote API master script.
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import http
35 from ganeti import daemon
36 from ganeti import ssconf
37 from ganeti import utils
38 from ganeti import luxi
39 from ganeti import serializer
40 from ganeti.rapi import connector
42 import ganeti.http.auth
43 import ganeti.http.server
46 class RemoteApiRequestContext(object):
47 """Data structure for Remote API requests.
52 self.handler_fn = None
53 self.handler_access = None
56 class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor):
57 """Custom Request Executor class that formats HTTP errors in JSON.
60 error_content_type = "application/json"
62 def _FormatErrorMessage(self, values):
63 """Formats the body of an error message.
66 @param values: dictionary with keys code, message and explain.
68 @return: the body of the message
71 return serializer.DumpJson(values, indent=True)
74 class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
75 http.server.HttpServer):
76 """REST Request Handler Class.
79 AUTH_REALM = "Ganeti Remote API"
81 def __init__(self, *args, **kwargs):
82 http.server.HttpServer.__init__(self, *args, **kwargs)
83 http.auth.HttpServerRequestAuthentication.__init__(self)
84 self._resmap = connector.Mapper()
87 if os.path.isfile(constants.RAPI_USERS_FILE):
88 self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
92 def _GetRequestContext(self, req):
93 """Returns the context for a request.
95 The context is cached in the req.private variable.
98 if req.private is None:
99 (HandlerClass, items, args) = \
100 self._resmap.getController(req.request_path)
102 ctx = RemoteApiRequestContext()
103 ctx.handler = HandlerClass(items, args, req)
105 method = req.request_method.upper()
107 ctx.handler_fn = getattr(ctx.handler, method)
108 except AttributeError, err:
109 raise http.HttpBadRequest()
111 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
113 # Require permissions definition (usually in the base class)
114 if ctx.handler_access is None:
115 raise AssertionError("Permissions definition missing")
121 def GetAuthRealm(self, req):
122 """Override the auth realm for queries.
125 ctx = self._GetRequestContext(req)
126 if ctx.handler_access:
127 return self.AUTH_REALM
131 def Authenticate(self, req, username, password):
132 """Checks whether a user can access a resource.
135 ctx = self._GetRequestContext(req)
137 # Check username and password
140 user = self._users.get(username, None)
141 if user and user.password == password:
145 # Unknown user or password wrong
148 if (not ctx.handler_access or
149 set(user.options).intersection(ctx.handler_access)):
154 raise http.HttpForbidden()
156 def HandleRequest(self, req):
157 """Handles a request.
160 ctx = self._GetRequestContext(req)
163 result = ctx.handler_fn()
164 sn = ctx.handler.getSerialNumber()
166 req.response_headers[http.HTTP_ETAG] = str(sn)
167 except luxi.TimeoutError:
168 raise http.HttpGatewayTimeout()
169 except luxi.ProtocolError, err:
170 raise http.HttpBadGateway(str(err))
172 method = req.request_method.upper()
173 logging.exception("Error while handling the %s request", method)
180 """Parse the command line options.
182 @return: (options, args) as from OptionParser.parse_args()
185 parser = optparse.OptionParser(description="Ganeti Remote API",
186 usage="%prog [-d] [-p port]",
187 version="%%prog (ganeti) %s" %
188 constants.RAPI_VERSION)
189 parser.add_option("-d", "--debug", dest="debug",
190 help="Enable some debug messages",
191 default=False, action="store_true")
192 parser.add_option("-p", "--port", dest="port",
193 help="Port to run API (%s default)." %
195 default=constants.RAPI_PORT, type="int")
196 parser.add_option("--no-ssl", dest="ssl",
197 help="Do not secure HTTP protocol with SSL",
198 default=True, action="store_false")
199 parser.add_option("-K", "--ssl-key", dest="ssl_key",
201 default=constants.RAPI_CERT_FILE, type="string")
202 parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
203 help="SSL certificate",
204 default=constants.RAPI_CERT_FILE, type="string")
205 parser.add_option("-f", "--foreground", dest="fork",
206 help="Don't detach from the current terminal",
207 default=True, action="store_false")
209 options, args = parser.parse_args()
212 print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
215 if options.ssl and not (options.ssl_cert and options.ssl_key):
216 print >> sys.stderr, ("For secure mode please provide "
217 "--ssl-key and --ssl-cert arguments")
227 options, args = ParseOptions()
233 # Read SSL certificate
235 ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
236 ssl_cert_path=options.ssl_cert)
237 except Exception, err:
238 sys.stderr.write("Can't load the SSL certificate/key: %s\n" % (err,))
243 ssconf.CheckMaster(options.debug)
246 utils.Daemonize(logfile=constants.LOG_RAPISERVER)
248 utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
249 stderr_logging=not options.fork)
251 utils.WritePidFile(constants.RAPI_PID)
253 mainloop = daemon.Mainloop()
254 server = RemoteApiHttpServer(mainloop, "", options.port,
255 ssl_params=ssl_params, ssl_verify_peer=False,
256 request_executor_class=
257 JsonErrorRequestExecutor)
264 utils.RemovePidFile(constants.RAPI_PID)
267 if __name__ == '__main__':