Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 6bb258a7

History | View | Annotate | Download (6.7 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 3cd62121 Michael Hanselmann
from ganeti.rapi import connector
39 3cd62121 Michael Hanselmann
40 b5b67ef9 Michael Hanselmann
import ganeti.http.auth
41 bc2929fc Michael Hanselmann
import ganeti.http.server
42 3cd62121 Michael Hanselmann
43 bc2929fc Michael Hanselmann
44 7e9760c3 Michael Hanselmann
class RemoteApiRequestContext(object):
45 7e9760c3 Michael Hanselmann
  """Data structure for Remote API requests.
46 7e9760c3 Michael Hanselmann
47 7e9760c3 Michael Hanselmann
  """
48 7e9760c3 Michael Hanselmann
  def __init__(self):
49 7e9760c3 Michael Hanselmann
    self.handler = None
50 7e9760c3 Michael Hanselmann
    self.handler_fn = None
51 b5b67ef9 Michael Hanselmann
    self.handler_access = None
52 7e9760c3 Michael Hanselmann
53 7e9760c3 Michael Hanselmann
54 b5b67ef9 Michael Hanselmann
class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
55 b5b67ef9 Michael Hanselmann
                          http.server.HttpServer):
56 3cd62121 Michael Hanselmann
  """REST Request Handler Class.
57 3cd62121 Michael Hanselmann
58 3cd62121 Michael Hanselmann
  """
59 b5b67ef9 Michael Hanselmann
  AUTH_REALM = "Ganeti Remote API"
60 b5b67ef9 Michael Hanselmann
61 16a8967d Michael Hanselmann
  def __init__(self, *args, **kwargs):
62 bc2929fc Michael Hanselmann
    http.server.HttpServer.__init__(self, *args, **kwargs)
63 b5b67ef9 Michael Hanselmann
    http.auth.HttpServerRequestAuthentication.__init__(self)
64 3cd62121 Michael Hanselmann
    self._resmap = connector.Mapper()
65 3cd62121 Michael Hanselmann
66 b5b67ef9 Michael Hanselmann
    # Load password file
67 b5b67ef9 Michael Hanselmann
    if os.path.isfile(constants.RAPI_USERS_FILE):
68 b5b67ef9 Michael Hanselmann
      self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
69 b5b67ef9 Michael Hanselmann
    else:
70 b5b67ef9 Michael Hanselmann
      self._users = None
71 b5b67ef9 Michael Hanselmann
72 7e9760c3 Michael Hanselmann
  def _GetRequestContext(self, req):
73 7e9760c3 Michael Hanselmann
    """Returns the context for a request.
74 7e9760c3 Michael Hanselmann
75 7e9760c3 Michael Hanselmann
    The context is cached in the req.private variable.
76 7e9760c3 Michael Hanselmann
77 7e9760c3 Michael Hanselmann
    """
78 7e9760c3 Michael Hanselmann
    if req.private is None:
79 85414b69 Iustin Pop
      (HandlerClass, items, args) = \
80 85414b69 Iustin Pop
                     self._resmap.getController(req.request_path)
81 7e9760c3 Michael Hanselmann
82 7e9760c3 Michael Hanselmann
      ctx = RemoteApiRequestContext()
83 7e9760c3 Michael Hanselmann
      ctx.handler = HandlerClass(items, args, req)
84 7e9760c3 Michael Hanselmann
85 7e9760c3 Michael Hanselmann
      method = req.request_method.upper()
86 7e9760c3 Michael Hanselmann
      try:
87 7e9760c3 Michael Hanselmann
        ctx.handler_fn = getattr(ctx.handler, method)
88 7e9760c3 Michael Hanselmann
      except AttributeError, err:
89 7e9760c3 Michael Hanselmann
        raise http.HttpBadRequest()
90 7e9760c3 Michael Hanselmann
91 b5b67ef9 Michael Hanselmann
      ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
92 b5b67ef9 Michael Hanselmann
93 b5b67ef9 Michael Hanselmann
      # Require permissions definition (usually in the base class)
94 b5b67ef9 Michael Hanselmann
      if ctx.handler_access is None:
95 b5b67ef9 Michael Hanselmann
        raise AssertionError("Permissions definition missing")
96 b5b67ef9 Michael Hanselmann
97 7e9760c3 Michael Hanselmann
      req.private = ctx
98 7e9760c3 Michael Hanselmann
99 7e9760c3 Michael Hanselmann
    return req.private
100 7e9760c3 Michael Hanselmann
101 85414b69 Iustin Pop
  def GetAuthRealm(self, req):
102 85414b69 Iustin Pop
    """Override the auth realm for queries.
103 85414b69 Iustin Pop
104 85414b69 Iustin Pop
    """
105 85414b69 Iustin Pop
    ctx = self._GetRequestContext(req)
106 85414b69 Iustin Pop
    if ctx.handler_access:
107 85414b69 Iustin Pop
      return self.AUTH_REALM
108 85414b69 Iustin Pop
    else:
109 85414b69 Iustin Pop
      return None
110 85414b69 Iustin Pop
111 b5b67ef9 Michael Hanselmann
  def Authenticate(self, req, username, password):
112 b5b67ef9 Michael Hanselmann
    """Checks whether a user can access a resource.
113 b5b67ef9 Michael Hanselmann
114 b5b67ef9 Michael Hanselmann
    """
115 b5b67ef9 Michael Hanselmann
    ctx = self._GetRequestContext(req)
116 b5b67ef9 Michael Hanselmann
117 b5b67ef9 Michael Hanselmann
    # Check username and password
118 b5b67ef9 Michael Hanselmann
    valid_user = False
119 b5b67ef9 Michael Hanselmann
    if self._users:
120 b5b67ef9 Michael Hanselmann
      user = self._users.get(username, None)
121 b5b67ef9 Michael Hanselmann
      if user and user.password == password:
122 b5b67ef9 Michael Hanselmann
        valid_user = True
123 b5b67ef9 Michael Hanselmann
124 b5b67ef9 Michael Hanselmann
    if not valid_user:
125 b5b67ef9 Michael Hanselmann
      # Unknown user or password wrong
126 b5b67ef9 Michael Hanselmann
      return False
127 b5b67ef9 Michael Hanselmann
128 b5b67ef9 Michael Hanselmann
    if (not ctx.handler_access or
129 b5b67ef9 Michael Hanselmann
        set(user.options).intersection(ctx.handler_access)):
130 b5b67ef9 Michael Hanselmann
      # Allow access
131 b5b67ef9 Michael Hanselmann
      return True
132 b5b67ef9 Michael Hanselmann
133 b5b67ef9 Michael Hanselmann
    # Access forbidden
134 b5b67ef9 Michael Hanselmann
    raise http.HttpForbidden()
135 b5b67ef9 Michael Hanselmann
136 16a8967d Michael Hanselmann
  def HandleRequest(self, req):
137 16a8967d Michael Hanselmann
    """Handles a request.
138 3cd62121 Michael Hanselmann
139 3cd62121 Michael Hanselmann
    """
140 7e9760c3 Michael Hanselmann
    ctx = self._GetRequestContext(req)
141 3cd62121 Michael Hanselmann
142 3cd62121 Michael Hanselmann
    try:
143 7e9760c3 Michael Hanselmann
      result = ctx.handler_fn()
144 7e9760c3 Michael Hanselmann
      sn = ctx.handler.getSerialNumber()
145 713faea6 Oleksiy Mishchenko
      if sn:
146 713faea6 Oleksiy Mishchenko
        req.response_headers[http.HTTP_ETAG] = str(sn)
147 16a8967d Michael Hanselmann
    except:
148 e09fdcfa Iustin Pop
      method = req.request_method.upper()
149 16a8967d Michael Hanselmann
      logging.exception("Error while handling the %s request", method)
150 16a8967d Michael Hanselmann
      raise
151 3cd62121 Michael Hanselmann
152 3cd62121 Michael Hanselmann
    return result
153 8c229cc7 Oleksiy Mishchenko
154 8c229cc7 Oleksiy Mishchenko
155 8c229cc7 Oleksiy Mishchenko
def ParseOptions():
156 8c229cc7 Oleksiy Mishchenko
  """Parse the command line options.
157 8c229cc7 Oleksiy Mishchenko
158 c41eea6e Iustin Pop
  @return: (options, args) as from OptionParser.parse_args()
159 8c229cc7 Oleksiy Mishchenko
160 8c229cc7 Oleksiy Mishchenko
  """
161 8c229cc7 Oleksiy Mishchenko
  parser = optparse.OptionParser(description="Ganeti Remote API",
162 8c229cc7 Oleksiy Mishchenko
                    usage="%prog [-d] [-p port]",
163 8c229cc7 Oleksiy Mishchenko
                    version="%%prog (ganeti) %s" %
164 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_VERSION)
165 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-d", "--debug", dest="debug",
166 8c229cc7 Oleksiy Mishchenko
                    help="Enable some debug messages",
167 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
168 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-p", "--port", dest="port",
169 8c229cc7 Oleksiy Mishchenko
                    help="Port to run API (%s default)." %
170 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_PORT,
171 8c229cc7 Oleksiy Mishchenko
                    default=constants.RAPI_PORT, type="int")
172 2ed6a7d6 Iustin Pop
  parser.add_option("--no-ssl", dest="ssl",
173 2ed6a7d6 Iustin Pop
                    help="Do not secure HTTP protocol with SSL",
174 2ed6a7d6 Iustin Pop
                    default=True, action="store_false")
175 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-K", "--ssl-key", dest="ssl_key",
176 8c229cc7 Oleksiy Mishchenko
                    help="SSL key",
177 2ed6a7d6 Iustin Pop
                    default=constants.RAPI_CERT_FILE, type="string")
178 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
179 8c229cc7 Oleksiy Mishchenko
                    help="SSL certificate",
180 2ed6a7d6 Iustin Pop
                    default=constants.RAPI_CERT_FILE, type="string")
181 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-f", "--foreground", dest="fork",
182 8c229cc7 Oleksiy Mishchenko
                    help="Don't detach from the current terminal",
183 8c229cc7 Oleksiy Mishchenko
                    default=True, action="store_false")
184 8c229cc7 Oleksiy Mishchenko
185 8c229cc7 Oleksiy Mishchenko
  options, args = parser.parse_args()
186 8c229cc7 Oleksiy Mishchenko
187 8c229cc7 Oleksiy Mishchenko
  if len(args) != 0:
188 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
189 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
190 8c229cc7 Oleksiy Mishchenko
191 8c229cc7 Oleksiy Mishchenko
  if options.ssl and not (options.ssl_cert and options.ssl_key):
192 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, ("For secure mode please provide "
193 8c229cc7 Oleksiy Mishchenko
                         "--ssl-key and --ssl-cert arguments")
194 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
195 8c229cc7 Oleksiy Mishchenko
196 8c229cc7 Oleksiy Mishchenko
  return options, args
197 8c229cc7 Oleksiy Mishchenko
198 8c229cc7 Oleksiy Mishchenko
199 8c229cc7 Oleksiy Mishchenko
def main():
200 8c229cc7 Oleksiy Mishchenko
  """Main function.
201 8c229cc7 Oleksiy Mishchenko
202 8c229cc7 Oleksiy Mishchenko
  """
203 8c229cc7 Oleksiy Mishchenko
  options, args = ParseOptions()
204 3cd62121 Michael Hanselmann
205 7d88772a Iustin Pop
  if options.fork:
206 7d88772a Iustin Pop
    utils.CloseFDs()
207 7d88772a Iustin Pop
208 2ed6a7d6 Iustin Pop
  if options.ssl:
209 2ed6a7d6 Iustin Pop
    # Read SSL certificate
210 6bb258a7 Iustin Pop
    try:
211 6bb258a7 Iustin Pop
      ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
212 6bb258a7 Iustin Pop
                                      ssl_cert_path=options.ssl_cert)
213 6bb258a7 Iustin Pop
    except Exception, err:
214 6bb258a7 Iustin Pop
      sys.stderr.write("Can't load the SSL certificate/key: %s\n" % (err,))
215 6bb258a7 Iustin Pop
      sys.exit(1)
216 2ed6a7d6 Iustin Pop
  else:
217 2ed6a7d6 Iustin Pop
    ssl_params = None
218 2ed6a7d6 Iustin Pop
219 5675cd1f Iustin Pop
  ssconf.CheckMaster(options.debug)
220 5675cd1f Iustin Pop
221 8c229cc7 Oleksiy Mishchenko
  if options.fork:
222 8c229cc7 Oleksiy Mishchenko
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
223 3cd62121 Michael Hanselmann
224 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
225 441e7cfd Oleksiy Mishchenko
                     stderr_logging=not options.fork)
226 441e7cfd Oleksiy Mishchenko
227 99e88451 Iustin Pop
  utils.WritePidFile(constants.RAPI_PID)
228 3cd62121 Michael Hanselmann
  try:
229 16a8967d Michael Hanselmann
    mainloop = daemon.Mainloop()
230 2ed6a7d6 Iustin Pop
    server = RemoteApiHttpServer(mainloop, "", options.port,
231 2ed6a7d6 Iustin Pop
                                 ssl_params=ssl_params, ssl_verify_peer=False)
232 16a8967d Michael Hanselmann
    server.Start()
233 3cd62121 Michael Hanselmann
    try:
234 16a8967d Michael Hanselmann
      mainloop.Run()
235 3cd62121 Michael Hanselmann
    finally:
236 16a8967d Michael Hanselmann
      server.Stop()
237 3cd62121 Michael Hanselmann
  finally:
238 16a8967d Michael Hanselmann
    utils.RemovePidFile(constants.RAPI_PID)
239 8c229cc7 Oleksiy Mishchenko
240 8c229cc7 Oleksiy Mishchenko
241 8c229cc7 Oleksiy Mishchenko
if __name__ == '__main__':
242 8c229cc7 Oleksiy Mishchenko
  main()