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) = self._resmap.getController(req.request_path)
81 ctx = RemoteApiRequestContext()
82 ctx.handler = HandlerClass(items, args, req)
84 method = req.request_method.upper()
86 ctx.handler_fn = getattr(ctx.handler, method)
87 except AttributeError, err:
88 raise http.HttpBadRequest()
90 ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
92 # Require permissions definition (usually in the base class)
93 if ctx.handler_access is None:
94 raise AssertionError("Permissions definition missing")
100 def Authenticate(self, req, username, password):
101 """Checks whether a user can access a resource.
104 ctx = self._GetRequestContext(req)
106 # Check username and password
109 user = self._users.get(username, None)
110 if user and user.password == password:
114 # Unknown user or password wrong
117 if (not ctx.handler_access or
118 set(user.options).intersection(ctx.handler_access)):
123 raise http.HttpForbidden()
125 def HandleRequest(self, req):
126 """Handles a request.
129 ctx = self._GetRequestContext(req)
132 result = ctx.handler_fn()
133 sn = ctx.handler.getSerialNumber()
135 req.response_headers[http.HTTP_ETAG] = str(sn)
137 logging.exception("Error while handling the %s request", method)
144 """Parse the command line options.
146 @return: (options, args) as from OptionParser.parse_args()
149 parser = optparse.OptionParser(description="Ganeti Remote API",
150 usage="%prog [-d] [-p port]",
151 version="%%prog (ganeti) %s" %
152 constants.RAPI_VERSION)
153 parser.add_option("-d", "--debug", dest="debug",
154 help="Enable some debug messages",
155 default=False, action="store_true")
156 parser.add_option("-p", "--port", dest="port",
157 help="Port to run API (%s default)." %
159 default=constants.RAPI_PORT, type="int")
160 parser.add_option("-S", "--https", dest="ssl",
161 help="Secure HTTP protocol with SSL",
162 default=False, action="store_true")
163 parser.add_option("-K", "--ssl-key", dest="ssl_key",
165 default=None, type="string")
166 parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
167 help="SSL certificate",
168 default=None, type="string")
169 parser.add_option("-f", "--foreground", dest="fork",
170 help="Don't detach from the current terminal",
171 default=True, action="store_false")
173 options, args = parser.parse_args()
176 print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
179 if options.ssl and not (options.ssl_cert and options.ssl_key):
180 print >> sys.stderr, ("For secure mode please provide "
181 "--ssl-key and --ssl-cert arguments")
191 options, args = ParseOptions()
193 ssconf.CheckMaster(options.debug)
196 utils.Daemonize(logfile=constants.LOG_RAPISERVER)
198 utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
199 stderr_logging=not options.fork)
201 utils.WritePidFile(constants.RAPI_PID)
203 mainloop = daemon.Mainloop()
204 server = RemoteApiHttpServer(mainloop, "", options.port)
211 utils.RemovePidFile(constants.RAPI_PID)
214 if __name__ == '__main__':