Fix some pylint-detected issues
[ganeti-local] / daemons / ganeti-rapi
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21 """ Ganeti Remote API master script.
22 """
23
24 import glob
25 import logging
26 import optparse
27 import sys
28 import os
29 import os.path
30 import signal
31
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
39
40 import ganeti.http.auth
41 import ganeti.http.server
42
43
44 class RemoteApiRequestContext(object):
45   """Data structure for Remote API requests.
46
47   """
48   def __init__(self):
49     self.handler = None
50     self.handler_fn = None
51     self.handler_access = None
52
53
54 class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
55                           http.server.HttpServer):
56   """REST Request Handler Class.
57
58   """
59   AUTH_REALM = "Ganeti Remote API"
60
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()
65
66     # Load password file
67     if os.path.isfile(constants.RAPI_USERS_FILE):
68       self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
69     else:
70       self._users = None
71
72   def _GetRequestContext(self, req):
73     """Returns the context for a request.
74
75     The context is cached in the req.private variable.
76
77     """
78     if req.private is None:
79       (HandlerClass, items, args) = self._resmap.getController(req.request_path)
80
81       ctx = RemoteApiRequestContext()
82       ctx.handler = HandlerClass(items, args, req)
83
84       method = req.request_method.upper()
85       try:
86         ctx.handler_fn = getattr(ctx.handler, method)
87       except AttributeError, err:
88         raise http.HttpBadRequest()
89
90       ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
91
92       # Require permissions definition (usually in the base class)
93       if ctx.handler_access is None:
94         raise AssertionError("Permissions definition missing")
95
96       req.private = ctx
97
98     return req.private
99
100   def Authenticate(self, req, username, password):
101     """Checks whether a user can access a resource.
102
103     """
104     ctx = self._GetRequestContext(req)
105
106     # Check username and password
107     valid_user = False
108     if self._users:
109       user = self._users.get(username, None)
110       if user and user.password == password:
111         valid_user = True
112
113     if not valid_user:
114       # Unknown user or password wrong
115       return False
116
117     if (not ctx.handler_access or
118         set(user.options).intersection(ctx.handler_access)):
119       # Allow access
120       return True
121
122     # Access forbidden
123     raise http.HttpForbidden()
124
125   def HandleRequest(self, req):
126     """Handles a request.
127
128     """
129     ctx = self._GetRequestContext(req)
130
131     try:
132       result = ctx.handler_fn()
133       sn = ctx.handler.getSerialNumber()
134       if sn:
135         req.response_headers[http.HTTP_ETAG] = str(sn)
136     except:
137       method = req.request_method.upper()
138       logging.exception("Error while handling the %s request", method)
139       raise
140
141     return result
142
143
144 def ParseOptions():
145   """Parse the command line options.
146
147   @return: (options, args) as from OptionParser.parse_args()
148
149   """
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)." %
159                                  constants.RAPI_PORT,
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",
165                     help="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")
173
174   options, args = parser.parse_args()
175
176   if len(args) != 0:
177     print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
178     sys.exit(1)
179
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")
183     sys.exit(1)
184
185   return options, args
186
187
188 def main():
189   """Main function.
190
191   """
192   options, args = ParseOptions()
193
194   ssconf.CheckMaster(options.debug)
195
196   if options.fork:
197     utils.Daemonize(logfile=constants.LOG_RAPISERVER)
198
199   utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
200                      stderr_logging=not options.fork)
201
202   utils.WritePidFile(constants.RAPI_PID)
203   try:
204     mainloop = daemon.Mainloop()
205     server = RemoteApiHttpServer(mainloop, "", options.port)
206     server.Start()
207     try:
208       mainloop.Run()
209     finally:
210       server.Stop()
211   finally:
212     utils.RemovePidFile(constants.RAPI_PID)
213
214
215 if __name__ == '__main__':
216   main()