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("-S", "--https", dest="ssl",
173 help="Secure HTTP protocol with SSL",
174 default=False, action="store_true")
175 parser.add_option("-K", "--ssl-key", dest="ssl_key",
177 default=None, type="string")
178 parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
179 help="SSL certificate",
180 default=None, 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()
208 ssconf.CheckMaster(options.debug)
211 utils.Daemonize(logfile=constants.LOG_RAPISERVER)
213 utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
214 stderr_logging=not options.fork)
216 utils.WritePidFile(constants.RAPI_PID)
218 mainloop = daemon.Mainloop()
219 server = RemoteApiHttpServer(mainloop, "", options.port)
226 utils.RemovePidFile(constants.RAPI_PID)
229 if __name__ == '__main__':