Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ f961e2ba

History | View | Annotate | Download (6.5 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 7260cfbe Iustin Pop
"""Ganeti Remote API master script.
22 7260cfbe Iustin Pop
23 8c229cc7 Oleksiy Mishchenko
"""
24 8c229cc7 Oleksiy Mishchenko
25 7260cfbe Iustin Pop
# pylint: disable-msg=C0103,W0142
26 7260cfbe Iustin Pop
27 7260cfbe Iustin Pop
# C0103: Invalid name ganeti-watcher
28 7260cfbe Iustin Pop
29 441e7cfd Oleksiy Mishchenko
import logging
30 8c229cc7 Oleksiy Mishchenko
import optparse
31 8c229cc7 Oleksiy Mishchenko
import sys
32 8c229cc7 Oleksiy Mishchenko
import os
33 b5b67ef9 Michael Hanselmann
import os.path
34 8c229cc7 Oleksiy Mishchenko
35 8c229cc7 Oleksiy Mishchenko
from ganeti import constants
36 3cd62121 Michael Hanselmann
from ganeti import http
37 16a8967d Michael Hanselmann
from ganeti import daemon
38 5675cd1f Iustin Pop
from ganeti import ssconf
39 77e1d753 Iustin Pop
from ganeti import luxi
40 1f8588f6 Iustin Pop
from ganeti import serializer
41 3cd62121 Michael Hanselmann
from ganeti.rapi import connector
42 3cd62121 Michael Hanselmann
43 30e4e741 Iustin Pop
import ganeti.http.auth   # pylint: disable-msg=W0611
44 bc2929fc Michael Hanselmann
import ganeti.http.server
45 3cd62121 Michael Hanselmann
46 bc2929fc Michael Hanselmann
47 7e9760c3 Michael Hanselmann
class RemoteApiRequestContext(object):
48 7e9760c3 Michael Hanselmann
  """Data structure for Remote API requests.
49 7e9760c3 Michael Hanselmann
50 7e9760c3 Michael Hanselmann
  """
51 7e9760c3 Michael Hanselmann
  def __init__(self):
52 7e9760c3 Michael Hanselmann
    self.handler = None
53 7e9760c3 Michael Hanselmann
    self.handler_fn = None
54 b5b67ef9 Michael Hanselmann
    self.handler_access = None
55 7e9760c3 Michael Hanselmann
56 7e9760c3 Michael Hanselmann
57 1f8588f6 Iustin Pop
class JsonErrorRequestExecutor(http.server.HttpServerRequestExecutor):
58 1f8588f6 Iustin Pop
  """Custom Request Executor class that formats HTTP errors in JSON.
59 1f8588f6 Iustin Pop
60 1f8588f6 Iustin Pop
  """
61 1f8588f6 Iustin Pop
  error_content_type = "application/json"
62 1f8588f6 Iustin Pop
63 1f8588f6 Iustin Pop
  def _FormatErrorMessage(self, values):
64 1f8588f6 Iustin Pop
    """Formats the body of an error message.
65 1f8588f6 Iustin Pop
66 1f8588f6 Iustin Pop
    @type values: dict
67 1f8588f6 Iustin Pop
    @param values: dictionary with keys code, message and explain.
68 1f8588f6 Iustin Pop
    @rtype: string
69 1f8588f6 Iustin Pop
    @return: the body of the message
70 1f8588f6 Iustin Pop
71 1f8588f6 Iustin Pop
    """
72 1f8588f6 Iustin Pop
    return serializer.DumpJson(values, indent=True)
73 1f8588f6 Iustin Pop
74 1f8588f6 Iustin Pop
75 b5b67ef9 Michael Hanselmann
class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
76 b5b67ef9 Michael Hanselmann
                          http.server.HttpServer):
77 3cd62121 Michael Hanselmann
  """REST Request Handler Class.
78 3cd62121 Michael Hanselmann
79 3cd62121 Michael Hanselmann
  """
80 b5b67ef9 Michael Hanselmann
  AUTH_REALM = "Ganeti Remote API"
81 b5b67ef9 Michael Hanselmann
82 16a8967d Michael Hanselmann
  def __init__(self, *args, **kwargs):
83 71d23b33 Iustin Pop
    # pylint: disable-msg=W0233
84 71d23b33 Iustin Pop
  # it seems pylint doesn't see the second parent class there
85 bc2929fc Michael Hanselmann
    http.server.HttpServer.__init__(self, *args, **kwargs)
86 b5b67ef9 Michael Hanselmann
    http.auth.HttpServerRequestAuthentication.__init__(self)
87 3cd62121 Michael Hanselmann
    self._resmap = connector.Mapper()
88 3cd62121 Michael Hanselmann
89 b5b67ef9 Michael Hanselmann
    # Load password file
90 b5b67ef9 Michael Hanselmann
    if os.path.isfile(constants.RAPI_USERS_FILE):
91 b5b67ef9 Michael Hanselmann
      self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
92 b5b67ef9 Michael Hanselmann
    else:
93 b5b67ef9 Michael Hanselmann
      self._users = None
94 b5b67ef9 Michael Hanselmann
95 7e9760c3 Michael Hanselmann
  def _GetRequestContext(self, req):
96 7e9760c3 Michael Hanselmann
    """Returns the context for a request.
97 7e9760c3 Michael Hanselmann
98 7e9760c3 Michael Hanselmann
    The context is cached in the req.private variable.
99 7e9760c3 Michael Hanselmann
100 7e9760c3 Michael Hanselmann
    """
101 7e9760c3 Michael Hanselmann
    if req.private is None:
102 85414b69 Iustin Pop
      (HandlerClass, items, args) = \
103 85414b69 Iustin Pop
                     self._resmap.getController(req.request_path)
104 7e9760c3 Michael Hanselmann
105 7e9760c3 Michael Hanselmann
      ctx = RemoteApiRequestContext()
106 7e9760c3 Michael Hanselmann
      ctx.handler = HandlerClass(items, args, req)
107 7e9760c3 Michael Hanselmann
108 7e9760c3 Michael Hanselmann
      method = req.request_method.upper()
109 7e9760c3 Michael Hanselmann
      try:
110 7e9760c3 Michael Hanselmann
        ctx.handler_fn = getattr(ctx.handler, method)
111 f4ad2ef0 Iustin Pop
      except AttributeError:
112 33664046 René Nussbaumer
        raise http.HttpNotImplemented("Method %s is unsupported for path %s" %
113 33664046 René Nussbaumer
                                      (method, req.request_path))
114 7e9760c3 Michael Hanselmann
115 b5b67ef9 Michael Hanselmann
      ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
116 b5b67ef9 Michael Hanselmann
117 b5b67ef9 Michael Hanselmann
      # Require permissions definition (usually in the base class)
118 b5b67ef9 Michael Hanselmann
      if ctx.handler_access is None:
119 b5b67ef9 Michael Hanselmann
        raise AssertionError("Permissions definition missing")
120 b5b67ef9 Michael Hanselmann
121 7e9760c3 Michael Hanselmann
      req.private = ctx
122 7e9760c3 Michael Hanselmann
123 23ccba04 Michael Hanselmann
    # Check for expected attributes
124 23ccba04 Michael Hanselmann
    assert req.private.handler
125 23ccba04 Michael Hanselmann
    assert req.private.handler_fn
126 23ccba04 Michael Hanselmann
    assert req.private.handler_access is not None
127 23ccba04 Michael Hanselmann
128 7e9760c3 Michael Hanselmann
    return req.private
129 7e9760c3 Michael Hanselmann
130 23ccba04 Michael Hanselmann
  def AuthenticationRequired(self, req):
131 23ccba04 Michael Hanselmann
    """Determine whether authentication is required.
132 85414b69 Iustin Pop
133 85414b69 Iustin Pop
    """
134 23ccba04 Michael Hanselmann
    return bool(self._GetRequestContext(req).handler_access)
135 85414b69 Iustin Pop
136 b5b67ef9 Michael Hanselmann
  def Authenticate(self, req, username, password):
137 b5b67ef9 Michael Hanselmann
    """Checks whether a user can access a resource.
138 b5b67ef9 Michael Hanselmann
139 b5b67ef9 Michael Hanselmann
    """
140 b5b67ef9 Michael Hanselmann
    ctx = self._GetRequestContext(req)
141 b5b67ef9 Michael Hanselmann
142 b5b67ef9 Michael Hanselmann
    # Check username and password
143 b5b67ef9 Michael Hanselmann
    valid_user = False
144 b5b67ef9 Michael Hanselmann
    if self._users:
145 b5b67ef9 Michael Hanselmann
      user = self._users.get(username, None)
146 0b08f096 Michael Hanselmann
      if user and self.VerifyBasicAuthPassword(req, username, password,
147 0b08f096 Michael Hanselmann
                                               user.password):
148 b5b67ef9 Michael Hanselmann
        valid_user = True
149 b5b67ef9 Michael Hanselmann
150 b5b67ef9 Michael Hanselmann
    if not valid_user:
151 b5b67ef9 Michael Hanselmann
      # Unknown user or password wrong
152 b5b67ef9 Michael Hanselmann
      return False
153 b5b67ef9 Michael Hanselmann
154 b5b67ef9 Michael Hanselmann
    if (not ctx.handler_access or
155 b5b67ef9 Michael Hanselmann
        set(user.options).intersection(ctx.handler_access)):
156 b5b67ef9 Michael Hanselmann
      # Allow access
157 b5b67ef9 Michael Hanselmann
      return True
158 b5b67ef9 Michael Hanselmann
159 b5b67ef9 Michael Hanselmann
    # Access forbidden
160 b5b67ef9 Michael Hanselmann
    raise http.HttpForbidden()
161 b5b67ef9 Michael Hanselmann
162 16a8967d Michael Hanselmann
  def HandleRequest(self, req):
163 16a8967d Michael Hanselmann
    """Handles a request.
164 3cd62121 Michael Hanselmann
165 3cd62121 Michael Hanselmann
    """
166 7e9760c3 Michael Hanselmann
    ctx = self._GetRequestContext(req)
167 3cd62121 Michael Hanselmann
168 3cd62121 Michael Hanselmann
    try:
169 7e9760c3 Michael Hanselmann
      result = ctx.handler_fn()
170 7e9760c3 Michael Hanselmann
      sn = ctx.handler.getSerialNumber()
171 713faea6 Oleksiy Mishchenko
      if sn:
172 713faea6 Oleksiy Mishchenko
        req.response_headers[http.HTTP_ETAG] = str(sn)
173 77e1d753 Iustin Pop
    except luxi.TimeoutError:
174 77e1d753 Iustin Pop
      raise http.HttpGatewayTimeout()
175 77e1d753 Iustin Pop
    except luxi.ProtocolError, err:
176 77e1d753 Iustin Pop
      raise http.HttpBadGateway(str(err))
177 16a8967d Michael Hanselmann
    except:
178 e09fdcfa Iustin Pop
      method = req.request_method.upper()
179 16a8967d Michael Hanselmann
      logging.exception("Error while handling the %s request", method)
180 16a8967d Michael Hanselmann
      raise
181 3cd62121 Michael Hanselmann
182 3cd62121 Michael Hanselmann
    return result
183 8c229cc7 Oleksiy Mishchenko
184 8c229cc7 Oleksiy Mishchenko
185 6c948699 Michael Hanselmann
def CheckRapi(options, args):
186 6c948699 Michael Hanselmann
  """Initial checks whether to run or exit with a failure.
187 8c229cc7 Oleksiy Mishchenko
188 8c229cc7 Oleksiy Mishchenko
  """
189 f93427cd Iustin Pop
  if args: # rapi doesn't take any arguments
190 f93427cd Iustin Pop
    print >> sys.stderr, ("Usage: %s [-f] [-d] [-p port] [-b ADDRESS]" %
191 f93427cd Iustin Pop
                          sys.argv[0])
192 be73fc79 Guido Trotter
    sys.exit(constants.EXIT_FAILURE)
193 8c229cc7 Oleksiy Mishchenko
194 04ccf5e9 Guido Trotter
  ssconf.CheckMaster(options.debug)
195 8c229cc7 Oleksiy Mishchenko
196 8c229cc7 Oleksiy Mishchenko
197 2d54e29c Iustin Pop
def ExecRapi(options, _):
198 6c948699 Michael Hanselmann
  """Main remote API function, executed with the PID file held.
199 8c229cc7 Oleksiy Mishchenko
200 8c229cc7 Oleksiy Mishchenko
  """
201 04ccf5e9 Guido Trotter
  # Read SSL certificate
202 2ed6a7d6 Iustin Pop
  if options.ssl:
203 04ccf5e9 Guido Trotter
    ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
204 04ccf5e9 Guido Trotter
                                    ssl_cert_path=options.ssl_cert)
205 2ed6a7d6 Iustin Pop
  else:
206 2ed6a7d6 Iustin Pop
    ssl_params = None
207 2ed6a7d6 Iustin Pop
208 04ccf5e9 Guido Trotter
  mainloop = daemon.Mainloop()
209 04ccf5e9 Guido Trotter
  server = RemoteApiHttpServer(mainloop, options.bind_address, options.port,
210 04ccf5e9 Guido Trotter
                               ssl_params=ssl_params, ssl_verify_peer=False,
211 04ccf5e9 Guido Trotter
                               request_executor_class=JsonErrorRequestExecutor)
212 71d23b33 Iustin Pop
  # pylint: disable-msg=E1101
213 71d23b33 Iustin Pop
  # it seems pylint doesn't see the second parent class there
214 04ccf5e9 Guido Trotter
  server.Start()
215 04ccf5e9 Guido Trotter
  try:
216 04ccf5e9 Guido Trotter
    mainloop.Run()
217 04ccf5e9 Guido Trotter
  finally:
218 04ccf5e9 Guido Trotter
    server.Stop()
219 5675cd1f Iustin Pop
220 3cd62121 Michael Hanselmann
221 04ccf5e9 Guido Trotter
def main():
222 04ccf5e9 Guido Trotter
  """Main function.
223 441e7cfd Oleksiy Mishchenko
224 04ccf5e9 Guido Trotter
  """
225 04ccf5e9 Guido Trotter
  parser = optparse.OptionParser(description="Ganeti Remote API",
226 04ccf5e9 Guido Trotter
                    usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
227 04ccf5e9 Guido Trotter
                    version="%%prog (ganeti) %s" % constants.RAPI_VERSION)
228 04ccf5e9 Guido Trotter
229 04ccf5e9 Guido Trotter
  dirs = [(val, constants.RUN_DIRS_MODE) for val in constants.SUB_RUN_DIRS]
230 04ccf5e9 Guido Trotter
  dirs.append((constants.LOG_OS_DIR, 0750))
231 6c948699 Michael Hanselmann
  daemon.GenericMain(constants.RAPI, parser, dirs, CheckRapi, ExecRapi)
232 8c229cc7 Oleksiy Mishchenko
233 8c229cc7 Oleksiy Mishchenko
234 6c948699 Michael Hanselmann
if __name__ == "__main__":
235 8c229cc7 Oleksiy Mishchenko
  main()