Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ f93427cd

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