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.rapi import connector
40 import ganeti.http.auth
41 import ganeti.http.server
44 class RemoteApiRequestContext(object):
45 """Data structure for Remote API requests.
50 self.handler_fn = None
51 self.handler_access = None
54 class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
55 http.server.HttpServer):
56 """REST Request Handler Class.
59 AUTH_REALM = "Ganeti Remote API"
61 def __init__(self, *args, **kwargs):
62 http.server.HttpServer.__init__(self, *args, **kwargs)
63 http.auth.HttpServerRequestAuthentication.__init__(self)
64 self._resmap = connector.Mapper()
67 if os.path.isfile(constants.RAPI_USERS_FILE):
68 self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
72 def _GetRequestContext(self, req):
73 """Returns the context for a request.
75 The context is cached in the req.private variable.
78 if req.private is None:
79 (HandlerClass, items, args) = \
80 self._resmap.getController(req.request_path)
82 ctx = RemoteApiRequestContext()
83 ctx.handler = HandlerClass(items, args, req)
85 method = req.request_method.upper()
87 ctx.handler_fn = getattr(ctx.handler, method)
88 except AttributeError, err:
89 raise http.HttpBadRequest()
91 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
93 # Require permissions definition (usually in the base class)
94 if ctx.handler_access is None:
95 raise AssertionError("Permissions definition missing")
101 def GetAuthRealm(self, req):
102 """Override the auth realm for queries.
105 ctx = self._GetRequestContext(req)
106 if ctx.handler_access:
107 return self.AUTH_REALM
111 def Authenticate(self, req, username, password):
112 """Checks whether a user can access a resource.
115 ctx = self._GetRequestContext(req)
117 # Check username and password
120 user = self._users.get(username, None)
121 if user and user.password == password:
125 # Unknown user or password wrong
128 if (not ctx.handler_access or
129 set(user.options).intersection(ctx.handler_access)):
134 raise http.HttpForbidden()
136 def HandleRequest(self, req):
137 """Handles a request.
140 ctx = self._GetRequestContext(req)
143 result = ctx.handler_fn()
144 sn = ctx.handler.getSerialNumber()
146 req.response_headers[http.HTTP_ETAG] = str(sn)
148 method = req.request_method.upper()
149 logging.exception("Error while handling the %s request", method)
156 """Parse the command line options.
158 @return: (options, args) as from OptionParser.parse_args()
161 parser = optparse.OptionParser(description="Ganeti Remote API",
162 usage="%prog [-d] [-p port]",
163 version="%%prog (ganeti) %s" %
164 constants.RAPI_VERSION)
165 parser.add_option("-d", "--debug", dest="debug",
166 help="Enable some debug messages",
167 default=False, action="store_true")
168 parser.add_option("-p", "--port", dest="port",
169 help="Port to run API (%s default)." %
171 default=constants.RAPI_PORT, type="int")
172 parser.add_option("--no-ssl", dest="ssl",
173 help="Do not secure HTTP protocol with SSL",
174 default=True, action="store_false")
175 parser.add_option("-K", "--ssl-key", dest="ssl_key",
177 default=constants.RAPI_CERT_FILE, type="string")
178 parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
179 help="SSL certificate",
180 default=constants.RAPI_CERT_FILE, type="string")
181 parser.add_option("-f", "--foreground", dest="fork",
182 help="Don't detach from the current terminal",
183 default=True, action="store_false")
185 options, args = parser.parse_args()
188 print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
191 if options.ssl and not (options.ssl_cert and options.ssl_key):
192 print >> sys.stderr, ("For secure mode please provide "
193 "--ssl-key and --ssl-cert arguments")
203 options, args = ParseOptions()
209 # Read SSL certificate
210 ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
211 ssl_cert_path=options.ssl_cert)
215 ssconf.CheckMaster(options.debug)
218 utils.Daemonize(logfile=constants.LOG_RAPISERVER)
220 utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
221 stderr_logging=not options.fork)
223 utils.WritePidFile(constants.RAPI_PID)
225 mainloop = daemon.Mainloop()
226 server = RemoteApiHttpServer(mainloop, "", options.port,
227 ssl_params=ssl_params, ssl_verify_peer=False)
234 utils.RemovePidFile(constants.RAPI_PID)
237 if __name__ == '__main__':