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("Method %s is unsupported for path %s" %
110 (method, req.request_path))
112 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
114 # Require permissions definition (usually in the base class)
115 if ctx.handler_access is None:
116 raise AssertionError("Permissions definition missing")
122 def GetAuthRealm(self, req):
123 """Override the auth realm for queries.
126 ctx = self._GetRequestContext(req)
127 if ctx.handler_access:
128 return self.AUTH_REALM
132 def Authenticate(self, req, username, password):
133 """Checks whether a user can access a resource.
136 ctx = self._GetRequestContext(req)
138 # Check username and password
141 user = self._users.get(username, None)
142 if user and self.VerifyBasicAuthPassword(req, username, password,
147 # Unknown user or password wrong
150 if (not ctx.handler_access or
151 set(user.options).intersection(ctx.handler_access)):
156 raise http.HttpForbidden()
158 def HandleRequest(self, req):
159 """Handles a request.
162 ctx = self._GetRequestContext(req)
165 result = ctx.handler_fn()
166 sn = ctx.handler.getSerialNumber()
168 req.response_headers[http.HTTP_ETAG] = str(sn)
169 except luxi.TimeoutError:
170 raise http.HttpGatewayTimeout()
171 except luxi.ProtocolError, err:
172 raise http.HttpBadGateway(str(err))
174 method = req.request_method.upper()
175 logging.exception("Error while handling the %s request", method)
181 def CheckRapi(options, args):
182 """Initial checks whether to run or exit with a failure.
186 print >> sys.stderr, "Usage: %s [-f] [-d] [-p port] [-b ADDRESS]" % \
188 sys.exit(constants.EXIT_FAILURE)
190 ssconf.CheckMaster(options.debug)
193 def ExecRapi(options, args):
194 """Main remote API function, executed with the PID file held.
197 # Read SSL certificate
199 ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
200 ssl_cert_path=options.ssl_cert)
204 mainloop = daemon.Mainloop()
205 server = RemoteApiHttpServer(mainloop, options.bind_address, options.port,
206 ssl_params=ssl_params, ssl_verify_peer=False,
207 request_executor_class=JsonErrorRequestExecutor)
219 parser = optparse.OptionParser(description="Ganeti Remote API",
220 usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
221 version="%%prog (ganeti) %s" % constants.RAPI_VERSION)
223 dirs = [(val, constants.RUN_DIRS_MODE) for val in constants.SUB_RUN_DIRS]
224 dirs.append((constants.LOG_OS_DIR, 0750))
225 daemon.GenericMain(constants.RAPI, parser, dirs, CheckRapi, ExecRapi)
228 if __name__ == "__main__":