Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 90b54c26

History | View | Annotate | Download (7.6 kB)

1 8c229cc7 Oleksiy Mishchenko
#!/usr/bin/python
2 8c229cc7 Oleksiy Mishchenko
#
3 8c229cc7 Oleksiy Mishchenko
4 8c229cc7 Oleksiy Mishchenko
# Copyright (C) 2006, 2007 Google Inc.
5 8c229cc7 Oleksiy Mishchenko
#
6 8c229cc7 Oleksiy Mishchenko
# This program is free software; you can redistribute it and/or modify
7 8c229cc7 Oleksiy Mishchenko
# it under the terms of the GNU General Public License as published by
8 8c229cc7 Oleksiy Mishchenko
# the Free Software Foundation; either version 2 of the License, or
9 8c229cc7 Oleksiy Mishchenko
# (at your option) any later version.
10 8c229cc7 Oleksiy Mishchenko
#
11 8c229cc7 Oleksiy Mishchenko
# This program is distributed in the hope that it will be useful, but
12 8c229cc7 Oleksiy Mishchenko
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 8c229cc7 Oleksiy Mishchenko
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 8c229cc7 Oleksiy Mishchenko
# General Public License for more details.
15 8c229cc7 Oleksiy Mishchenko
#
16 8c229cc7 Oleksiy Mishchenko
# You should have received a copy of the GNU General Public License
17 8c229cc7 Oleksiy Mishchenko
# along with this program; if not, write to the Free Software
18 8c229cc7 Oleksiy Mishchenko
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 8c229cc7 Oleksiy Mishchenko
# 02110-1301, USA.
20 8c229cc7 Oleksiy Mishchenko
21 8c229cc7 Oleksiy Mishchenko
""" Ganeti Remote API master script.
22 8c229cc7 Oleksiy Mishchenko
"""
23 8c229cc7 Oleksiy Mishchenko
24 8c229cc7 Oleksiy Mishchenko
import glob
25 441e7cfd Oleksiy Mishchenko
import logging
26 8c229cc7 Oleksiy Mishchenko
import optparse
27 8c229cc7 Oleksiy Mishchenko
import sys
28 8c229cc7 Oleksiy Mishchenko
import os
29 b5b67ef9 Michael Hanselmann
import os.path
30 cfe3c70f Michael Hanselmann
import signal
31 8c229cc7 Oleksiy Mishchenko
32 8c229cc7 Oleksiy Mishchenko
from ganeti import constants
33 3cd62121 Michael Hanselmann
from ganeti import errors
34 3cd62121 Michael Hanselmann
from ganeti import http
35 16a8967d Michael Hanselmann
from ganeti import daemon
36 5675cd1f Iustin Pop
from ganeti import ssconf
37 8c229cc7 Oleksiy Mishchenko
from ganeti import utils
38 77e1d753 Iustin Pop
from ganeti import luxi
39 1f8588f6 Iustin Pop
from ganeti import serializer
40 3cd62121 Michael Hanselmann
from ganeti.rapi import connector
41 3cd62121 Michael Hanselmann
42 b5b67ef9 Michael Hanselmann
import ganeti.http.auth
43 bc2929fc Michael Hanselmann
import ganeti.http.server
44 3cd62121 Michael Hanselmann
45 bc2929fc Michael Hanselmann
46 7e9760c3 Michael Hanselmann
class RemoteApiRequestContext(object):
47 7e9760c3 Michael Hanselmann
  """Data structure for Remote API requests.
48 7e9760c3 Michael Hanselmann
49 7e9760c3 Michael Hanselmann
  """
50 7e9760c3 Michael Hanselmann
  def __init__(self):
51 7e9760c3 Michael Hanselmann
    self.handler = None
52 7e9760c3 Michael Hanselmann
    self.handler_fn = None
53 b5b67ef9 Michael Hanselmann
    self.handler_access = None
54 7e9760c3 Michael Hanselmann
55 7e9760c3 Michael Hanselmann
56 1f8588f6 Iustin Pop
class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor):
57 1f8588f6 Iustin Pop
  """Custom Request Executor class that formats HTTP errors in JSON.
58 1f8588f6 Iustin Pop
59 1f8588f6 Iustin Pop
  """
60 1f8588f6 Iustin Pop
  error_content_type = "application/json"
61 1f8588f6 Iustin Pop
62 1f8588f6 Iustin Pop
  def _FormatErrorMessage(self, values):
63 1f8588f6 Iustin Pop
    """Formats the body of an error message.
64 1f8588f6 Iustin Pop
65 1f8588f6 Iustin Pop
    @type values: dict
66 1f8588f6 Iustin Pop
    @param values: dictionary with keys code, message and explain.
67 1f8588f6 Iustin Pop
    @rtype: string
68 1f8588f6 Iustin Pop
    @return: the body of the message
69 1f8588f6 Iustin Pop
70 1f8588f6 Iustin Pop
    """
71 1f8588f6 Iustin Pop
    return serializer.DumpJson(values, indent=True)
72 1f8588f6 Iustin Pop
73 1f8588f6 Iustin Pop
74 b5b67ef9 Michael Hanselmann
class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
75 b5b67ef9 Michael Hanselmann
                          http.server.HttpServer):
76 3cd62121 Michael Hanselmann
  """REST Request Handler Class.
77 3cd62121 Michael Hanselmann
78 3cd62121 Michael Hanselmann
  """
79 b5b67ef9 Michael Hanselmann
  AUTH_REALM = "Ganeti Remote API"
80 b5b67ef9 Michael Hanselmann
81 16a8967d Michael Hanselmann
  def __init__(self, *args, **kwargs):
82 bc2929fc Michael Hanselmann
    http.server.HttpServer.__init__(self, *args, **kwargs)
83 b5b67ef9 Michael Hanselmann
    http.auth.HttpServerRequestAuthentication.__init__(self)
84 3cd62121 Michael Hanselmann
    self._resmap = connector.Mapper()
85 3cd62121 Michael Hanselmann
86 b5b67ef9 Michael Hanselmann
    # Load password file
87 b5b67ef9 Michael Hanselmann
    if os.path.isfile(constants.RAPI_USERS_FILE):
88 b5b67ef9 Michael Hanselmann
      self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
89 b5b67ef9 Michael Hanselmann
    else:
90 b5b67ef9 Michael Hanselmann
      self._users = None
91 b5b67ef9 Michael Hanselmann
92 7e9760c3 Michael Hanselmann
  def _GetRequestContext(self, req):
93 7e9760c3 Michael Hanselmann
    """Returns the context for a request.
94 7e9760c3 Michael Hanselmann
95 7e9760c3 Michael Hanselmann
    The context is cached in the req.private variable.
96 7e9760c3 Michael Hanselmann
97 7e9760c3 Michael Hanselmann
    """
98 7e9760c3 Michael Hanselmann
    if req.private is None:
99 85414b69 Iustin Pop
      (HandlerClass, items, args) = \
100 85414b69 Iustin Pop
                     self._resmap.getController(req.request_path)
101 7e9760c3 Michael Hanselmann
102 7e9760c3 Michael Hanselmann
      ctx = RemoteApiRequestContext()
103 7e9760c3 Michael Hanselmann
      ctx.handler = HandlerClass(items, args, req)
104 7e9760c3 Michael Hanselmann
105 7e9760c3 Michael Hanselmann
      method = req.request_method.upper()
106 7e9760c3 Michael Hanselmann
      try:
107 7e9760c3 Michael Hanselmann
        ctx.handler_fn = getattr(ctx.handler, method)
108 7e9760c3 Michael Hanselmann
      except AttributeError, err:
109 6e99c5a0 Iustin Pop
        raise http.HttpBadRequest("Method %s is unsupported for path %s" %
110 6e99c5a0 Iustin Pop
                                  (method, req.request_path))
111 7e9760c3 Michael Hanselmann
112 b5b67ef9 Michael Hanselmann
      ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
113 b5b67ef9 Michael Hanselmann
114 b5b67ef9 Michael Hanselmann
      # Require permissions definition (usually in the base class)
115 b5b67ef9 Michael Hanselmann
      if ctx.handler_access is None:
116 b5b67ef9 Michael Hanselmann
        raise AssertionError("Permissions definition missing")
117 b5b67ef9 Michael Hanselmann
118 7e9760c3 Michael Hanselmann
      req.private = ctx
119 7e9760c3 Michael Hanselmann
120 7e9760c3 Michael Hanselmann
    return req.private
121 7e9760c3 Michael Hanselmann
122 85414b69 Iustin Pop
  def GetAuthRealm(self, req):
123 85414b69 Iustin Pop
    """Override the auth realm for queries.
124 85414b69 Iustin Pop
125 85414b69 Iustin Pop
    """
126 85414b69 Iustin Pop
    ctx = self._GetRequestContext(req)
127 85414b69 Iustin Pop
    if ctx.handler_access:
128 85414b69 Iustin Pop
      return self.AUTH_REALM
129 85414b69 Iustin Pop
    else:
130 85414b69 Iustin Pop
      return None
131 85414b69 Iustin Pop
132 b5b67ef9 Michael Hanselmann
  def Authenticate(self, req, username, password):
133 b5b67ef9 Michael Hanselmann
    """Checks whether a user can access a resource.
134 b5b67ef9 Michael Hanselmann
135 b5b67ef9 Michael Hanselmann
    """
136 b5b67ef9 Michael Hanselmann
    ctx = self._GetRequestContext(req)
137 b5b67ef9 Michael Hanselmann
138 b5b67ef9 Michael Hanselmann
    # Check username and password
139 b5b67ef9 Michael Hanselmann
    valid_user = False
140 b5b67ef9 Michael Hanselmann
    if self._users:
141 b5b67ef9 Michael Hanselmann
      user = self._users.get(username, None)
142 b5b67ef9 Michael Hanselmann
      if user and user.password == password:
143 b5b67ef9 Michael Hanselmann
        valid_user = True
144 b5b67ef9 Michael Hanselmann
145 b5b67ef9 Michael Hanselmann
    if not valid_user:
146 b5b67ef9 Michael Hanselmann
      # Unknown user or password wrong
147 b5b67ef9 Michael Hanselmann
      return False
148 b5b67ef9 Michael Hanselmann
149 b5b67ef9 Michael Hanselmann
    if (not ctx.handler_access or
150 b5b67ef9 Michael Hanselmann
        set(user.options).intersection(ctx.handler_access)):
151 b5b67ef9 Michael Hanselmann
      # Allow access
152 b5b67ef9 Michael Hanselmann
      return True
153 b5b67ef9 Michael Hanselmann
154 b5b67ef9 Michael Hanselmann
    # Access forbidden
155 b5b67ef9 Michael Hanselmann
    raise http.HttpForbidden()
156 b5b67ef9 Michael Hanselmann
157 16a8967d Michael Hanselmann
  def HandleRequest(self, req):
158 16a8967d Michael Hanselmann
    """Handles a request.
159 3cd62121 Michael Hanselmann
160 3cd62121 Michael Hanselmann
    """
161 7e9760c3 Michael Hanselmann
    ctx = self._GetRequestContext(req)
162 3cd62121 Michael Hanselmann
163 3cd62121 Michael Hanselmann
    try:
164 7e9760c3 Michael Hanselmann
      result = ctx.handler_fn()
165 7e9760c3 Michael Hanselmann
      sn = ctx.handler.getSerialNumber()
166 713faea6 Oleksiy Mishchenko
      if sn:
167 713faea6 Oleksiy Mishchenko
        req.response_headers[http.HTTP_ETAG] = str(sn)
168 77e1d753 Iustin Pop
    except luxi.TimeoutError:
169 77e1d753 Iustin Pop
      raise http.HttpGatewayTimeout()
170 77e1d753 Iustin Pop
    except luxi.ProtocolError, err:
171 77e1d753 Iustin Pop
      raise http.HttpBadGateway(str(err))
172 16a8967d Michael Hanselmann
    except:
173 e09fdcfa Iustin Pop
      method = req.request_method.upper()
174 16a8967d Michael Hanselmann
      logging.exception("Error while handling the %s request", method)
175 16a8967d Michael Hanselmann
      raise
176 3cd62121 Michael Hanselmann
177 3cd62121 Michael Hanselmann
    return result
178 8c229cc7 Oleksiy Mishchenko
179 8c229cc7 Oleksiy Mishchenko
180 8c229cc7 Oleksiy Mishchenko
def ParseOptions():
181 8c229cc7 Oleksiy Mishchenko
  """Parse the command line options.
182 8c229cc7 Oleksiy Mishchenko
183 c41eea6e Iustin Pop
  @return: (options, args) as from OptionParser.parse_args()
184 8c229cc7 Oleksiy Mishchenko
185 8c229cc7 Oleksiy Mishchenko
  """
186 8c229cc7 Oleksiy Mishchenko
  parser = optparse.OptionParser(description="Ganeti Remote API",
187 8c229cc7 Oleksiy Mishchenko
                    usage="%prog [-d] [-p port]",
188 8c229cc7 Oleksiy Mishchenko
                    version="%%prog (ganeti) %s" %
189 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_VERSION)
190 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-d", "--debug", dest="debug",
191 8c229cc7 Oleksiy Mishchenko
                    help="Enable some debug messages",
192 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
193 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-p", "--port", dest="port",
194 8c229cc7 Oleksiy Mishchenko
                    help="Port to run API (%s default)." %
195 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_PORT,
196 8c229cc7 Oleksiy Mishchenko
                    default=constants.RAPI_PORT, type="int")
197 2ed6a7d6 Iustin Pop
  parser.add_option("--no-ssl", dest="ssl",
198 2ed6a7d6 Iustin Pop
                    help="Do not secure HTTP protocol with SSL",
199 2ed6a7d6 Iustin Pop
                    default=True, action="store_false")
200 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-K", "--ssl-key", dest="ssl_key",
201 8c229cc7 Oleksiy Mishchenko
                    help="SSL key",
202 2ed6a7d6 Iustin Pop
                    default=constants.RAPI_CERT_FILE, type="string")
203 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
204 8c229cc7 Oleksiy Mishchenko
                    help="SSL certificate",
205 2ed6a7d6 Iustin Pop
                    default=constants.RAPI_CERT_FILE, type="string")
206 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-f", "--foreground", dest="fork",
207 8c229cc7 Oleksiy Mishchenko
                    help="Don't detach from the current terminal",
208 8c229cc7 Oleksiy Mishchenko
                    default=True, action="store_false")
209 8c229cc7 Oleksiy Mishchenko
210 8c229cc7 Oleksiy Mishchenko
  options, args = parser.parse_args()
211 8c229cc7 Oleksiy Mishchenko
212 8c229cc7 Oleksiy Mishchenko
  if len(args) != 0:
213 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
214 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
215 8c229cc7 Oleksiy Mishchenko
216 8c229cc7 Oleksiy Mishchenko
  if options.ssl and not (options.ssl_cert and options.ssl_key):
217 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, ("For secure mode please provide "
218 8c229cc7 Oleksiy Mishchenko
                         "--ssl-key and --ssl-cert arguments")
219 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
220 8c229cc7 Oleksiy Mishchenko
221 8c229cc7 Oleksiy Mishchenko
  return options, args
222 8c229cc7 Oleksiy Mishchenko
223 8c229cc7 Oleksiy Mishchenko
224 8c229cc7 Oleksiy Mishchenko
def main():
225 8c229cc7 Oleksiy Mishchenko
  """Main function.
226 8c229cc7 Oleksiy Mishchenko
227 8c229cc7 Oleksiy Mishchenko
  """
228 8c229cc7 Oleksiy Mishchenko
  options, args = ParseOptions()
229 3cd62121 Michael Hanselmann
230 7d88772a Iustin Pop
  if options.fork:
231 7d88772a Iustin Pop
    utils.CloseFDs()
232 7d88772a Iustin Pop
233 2ed6a7d6 Iustin Pop
  if options.ssl:
234 2ed6a7d6 Iustin Pop
    # Read SSL certificate
235 6bb258a7 Iustin Pop
    try:
236 6bb258a7 Iustin Pop
      ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
237 6bb258a7 Iustin Pop
                                      ssl_cert_path=options.ssl_cert)
238 6bb258a7 Iustin Pop
    except Exception, err:
239 6bb258a7 Iustin Pop
      sys.stderr.write("Can't load the SSL certificate/key: %s\n" % (err,))
240 6bb258a7 Iustin Pop
      sys.exit(1)
241 2ed6a7d6 Iustin Pop
  else:
242 2ed6a7d6 Iustin Pop
    ssl_params = None
243 2ed6a7d6 Iustin Pop
244 5675cd1f Iustin Pop
  ssconf.CheckMaster(options.debug)
245 5675cd1f Iustin Pop
246 8c229cc7 Oleksiy Mishchenko
  if options.fork:
247 8c229cc7 Oleksiy Mishchenko
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
248 3cd62121 Michael Hanselmann
249 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
250 441e7cfd Oleksiy Mishchenko
                     stderr_logging=not options.fork)
251 441e7cfd Oleksiy Mishchenko
252 99e88451 Iustin Pop
  utils.WritePidFile(constants.RAPI_PID)
253 3cd62121 Michael Hanselmann
  try:
254 16a8967d Michael Hanselmann
    mainloop = daemon.Mainloop()
255 2ed6a7d6 Iustin Pop
    server = RemoteApiHttpServer(mainloop, "", options.port,
256 1f8588f6 Iustin Pop
                                 ssl_params=ssl_params, ssl_verify_peer=False,
257 1f8588f6 Iustin Pop
                                 request_executor_class=
258 1f8588f6 Iustin Pop
                                 JsonErrorRequestExecutor)
259 16a8967d Michael Hanselmann
    server.Start()
260 3cd62121 Michael Hanselmann
    try:
261 16a8967d Michael Hanselmann
      mainloop.Run()
262 3cd62121 Michael Hanselmann
    finally:
263 16a8967d Michael Hanselmann
      server.Stop()
264 3cd62121 Michael Hanselmann
  finally:
265 16a8967d Michael Hanselmann
    utils.RemovePidFile(constants.RAPI_PID)
266 8c229cc7 Oleksiy Mishchenko
267 8c229cc7 Oleksiy Mishchenko
268 8c229cc7 Oleksiy Mishchenko
if __name__ == '__main__':
269 8c229cc7 Oleksiy Mishchenko
  main()