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 method = req.request_method.upper()
138 logging.exception("Error while handling the %s request", method)
145 """Parse the command line options.
147 @return: (options, args) as from OptionParser.parse_args()
150 parser = optparse.OptionParser(description="Ganeti Remote API",
151 usage="%prog [-d] [-p port]",
152 version="%%prog (ganeti) %s" %
153 constants.RAPI_VERSION)
154 parser.add_option("-d", "--debug", dest="debug",
155 help="Enable some debug messages",
156 default=False, action="store_true")
157 parser.add_option("-p", "--port", dest="port",
158 help="Port to run API (%s default)." %
160 default=constants.RAPI_PORT, type="int")
161 parser.add_option("-S", "--https", dest="ssl",
162 help="Secure HTTP protocol with SSL",
163 default=False, action="store_true")
164 parser.add_option("-K", "--ssl-key", dest="ssl_key",
166 default=None, type="string")
167 parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
168 help="SSL certificate",
169 default=None, type="string")
170 parser.add_option("-f", "--foreground", dest="fork",
171 help="Don't detach from the current terminal",
172 default=True, action="store_false")
174 options, args = parser.parse_args()
177 print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
180 if options.ssl and not (options.ssl_cert and options.ssl_key):
181 print >> sys.stderr, ("For secure mode please provide "
182 "--ssl-key and --ssl-cert arguments")
192 options, args = ParseOptions()
194 ssconf.CheckMaster(options.debug)
197 utils.Daemonize(logfile=constants.LOG_RAPISERVER)
199 utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
200 stderr_logging=not options.fork)
202 utils.WritePidFile(constants.RAPI_PID)
204 mainloop = daemon.Mainloop()
205 server = RemoteApiHttpServer(mainloop, "", options.port)
212 utils.RemovePidFile(constants.RAPI_PID)
215 if __name__ == '__main__':