Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 6906a9d8

History | View | Annotate | Download (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 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 7e9760c3 Michael Hanselmann
      (HandlerClass, items, args) = self._resmap.getController(req.request_path)
80 7e9760c3 Michael Hanselmann
81 7e9760c3 Michael Hanselmann
      ctx = RemoteApiRequestContext()
82 7e9760c3 Michael Hanselmann
      ctx.handler = HandlerClass(items, args, req)
83 7e9760c3 Michael Hanselmann
84 7e9760c3 Michael Hanselmann
      method = req.request_method.upper()
85 7e9760c3 Michael Hanselmann
      try:
86 7e9760c3 Michael Hanselmann
        ctx.handler_fn = getattr(ctx.handler, method)
87 7e9760c3 Michael Hanselmann
      except AttributeError, err:
88 7e9760c3 Michael Hanselmann
        raise http.HttpBadRequest()
89 7e9760c3 Michael Hanselmann
90 b5b67ef9 Michael Hanselmann
      ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
91 b5b67ef9 Michael Hanselmann
92 b5b67ef9 Michael Hanselmann
      # Require permissions definition (usually in the base class)
93 b5b67ef9 Michael Hanselmann
      if ctx.handler_access is None:
94 b5b67ef9 Michael Hanselmann
        raise AssertionError("Permissions definition missing")
95 b5b67ef9 Michael Hanselmann
96 7e9760c3 Michael Hanselmann
      req.private = ctx
97 7e9760c3 Michael Hanselmann
98 7e9760c3 Michael Hanselmann
    return req.private
99 7e9760c3 Michael Hanselmann
100 b5b67ef9 Michael Hanselmann
  def Authenticate(self, req, username, password):
101 b5b67ef9 Michael Hanselmann
    """Checks whether a user can access a resource.
102 b5b67ef9 Michael Hanselmann
103 b5b67ef9 Michael Hanselmann
    """
104 b5b67ef9 Michael Hanselmann
    ctx = self._GetRequestContext(req)
105 b5b67ef9 Michael Hanselmann
106 b5b67ef9 Michael Hanselmann
    # Check username and password
107 b5b67ef9 Michael Hanselmann
    valid_user = False
108 b5b67ef9 Michael Hanselmann
    if self._users:
109 b5b67ef9 Michael Hanselmann
      user = self._users.get(username, None)
110 b5b67ef9 Michael Hanselmann
      if user and user.password == password:
111 b5b67ef9 Michael Hanselmann
        valid_user = True
112 b5b67ef9 Michael Hanselmann
113 b5b67ef9 Michael Hanselmann
    if not valid_user:
114 b5b67ef9 Michael Hanselmann
      # Unknown user or password wrong
115 b5b67ef9 Michael Hanselmann
      return False
116 b5b67ef9 Michael Hanselmann
117 b5b67ef9 Michael Hanselmann
    if (not ctx.handler_access or
118 b5b67ef9 Michael Hanselmann
        set(user.options).intersection(ctx.handler_access)):
119 b5b67ef9 Michael Hanselmann
      # Allow access
120 b5b67ef9 Michael Hanselmann
      return True
121 b5b67ef9 Michael Hanselmann
122 b5b67ef9 Michael Hanselmann
    # Access forbidden
123 b5b67ef9 Michael Hanselmann
    raise http.HttpForbidden()
124 b5b67ef9 Michael Hanselmann
125 16a8967d Michael Hanselmann
  def HandleRequest(self, req):
126 16a8967d Michael Hanselmann
    """Handles a request.
127 3cd62121 Michael Hanselmann
128 3cd62121 Michael Hanselmann
    """
129 7e9760c3 Michael Hanselmann
    ctx = self._GetRequestContext(req)
130 3cd62121 Michael Hanselmann
131 3cd62121 Michael Hanselmann
    try:
132 7e9760c3 Michael Hanselmann
      result = ctx.handler_fn()
133 7e9760c3 Michael Hanselmann
      sn = ctx.handler.getSerialNumber()
134 713faea6 Oleksiy Mishchenko
      if sn:
135 713faea6 Oleksiy Mishchenko
        req.response_headers[http.HTTP_ETAG] = str(sn)
136 16a8967d Michael Hanselmann
    except:
137 e09fdcfa Iustin Pop
      method = req.request_method.upper()
138 16a8967d Michael Hanselmann
      logging.exception("Error while handling the %s request", method)
139 16a8967d Michael Hanselmann
      raise
140 3cd62121 Michael Hanselmann
141 3cd62121 Michael Hanselmann
    return result
142 8c229cc7 Oleksiy Mishchenko
143 8c229cc7 Oleksiy Mishchenko
144 8c229cc7 Oleksiy Mishchenko
def ParseOptions():
145 8c229cc7 Oleksiy Mishchenko
  """Parse the command line options.
146 8c229cc7 Oleksiy Mishchenko
147 c41eea6e Iustin Pop
  @return: (options, args) as from OptionParser.parse_args()
148 8c229cc7 Oleksiy Mishchenko
149 8c229cc7 Oleksiy Mishchenko
  """
150 8c229cc7 Oleksiy Mishchenko
  parser = optparse.OptionParser(description="Ganeti Remote API",
151 8c229cc7 Oleksiy Mishchenko
                    usage="%prog [-d] [-p port]",
152 8c229cc7 Oleksiy Mishchenko
                    version="%%prog (ganeti) %s" %
153 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_VERSION)
154 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-d", "--debug", dest="debug",
155 8c229cc7 Oleksiy Mishchenko
                    help="Enable some debug messages",
156 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
157 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-p", "--port", dest="port",
158 8c229cc7 Oleksiy Mishchenko
                    help="Port to run API (%s default)." %
159 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_PORT,
160 8c229cc7 Oleksiy Mishchenko
                    default=constants.RAPI_PORT, type="int")
161 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-S", "--https", dest="ssl",
162 8c229cc7 Oleksiy Mishchenko
                    help="Secure HTTP protocol with SSL",
163 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
164 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-K", "--ssl-key", dest="ssl_key",
165 8c229cc7 Oleksiy Mishchenko
                    help="SSL key",
166 8c229cc7 Oleksiy Mishchenko
                    default=None, type="string")
167 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
168 8c229cc7 Oleksiy Mishchenko
                    help="SSL certificate",
169 8c229cc7 Oleksiy Mishchenko
                    default=None, type="string")
170 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-f", "--foreground", dest="fork",
171 8c229cc7 Oleksiy Mishchenko
                    help="Don't detach from the current terminal",
172 8c229cc7 Oleksiy Mishchenko
                    default=True, action="store_false")
173 8c229cc7 Oleksiy Mishchenko
174 8c229cc7 Oleksiy Mishchenko
  options, args = parser.parse_args()
175 8c229cc7 Oleksiy Mishchenko
176 8c229cc7 Oleksiy Mishchenko
  if len(args) != 0:
177 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
178 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
179 8c229cc7 Oleksiy Mishchenko
180 8c229cc7 Oleksiy Mishchenko
  if options.ssl and not (options.ssl_cert and options.ssl_key):
181 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, ("For secure mode please provide "
182 8c229cc7 Oleksiy Mishchenko
                         "--ssl-key and --ssl-cert arguments")
183 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
184 8c229cc7 Oleksiy Mishchenko
185 8c229cc7 Oleksiy Mishchenko
  return options, args
186 8c229cc7 Oleksiy Mishchenko
187 8c229cc7 Oleksiy Mishchenko
188 8c229cc7 Oleksiy Mishchenko
def main():
189 8c229cc7 Oleksiy Mishchenko
  """Main function.
190 8c229cc7 Oleksiy Mishchenko
191 8c229cc7 Oleksiy Mishchenko
  """
192 8c229cc7 Oleksiy Mishchenko
  options, args = ParseOptions()
193 3cd62121 Michael Hanselmann
194 7d88772a Iustin Pop
  if options.fork:
195 7d88772a Iustin Pop
    utils.CloseFDs()
196 7d88772a Iustin Pop
197 5675cd1f Iustin Pop
  ssconf.CheckMaster(options.debug)
198 5675cd1f Iustin Pop
199 8c229cc7 Oleksiy Mishchenko
  if options.fork:
200 8c229cc7 Oleksiy Mishchenko
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
201 3cd62121 Michael Hanselmann
202 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
203 441e7cfd Oleksiy Mishchenko
                     stderr_logging=not options.fork)
204 441e7cfd Oleksiy Mishchenko
205 99e88451 Iustin Pop
  utils.WritePidFile(constants.RAPI_PID)
206 3cd62121 Michael Hanselmann
  try:
207 16a8967d Michael Hanselmann
    mainloop = daemon.Mainloop()
208 23e46494 Michael Hanselmann
    server = RemoteApiHttpServer(mainloop, "", options.port)
209 16a8967d Michael Hanselmann
    server.Start()
210 3cd62121 Michael Hanselmann
    try:
211 16a8967d Michael Hanselmann
      mainloop.Run()
212 3cd62121 Michael Hanselmann
    finally:
213 16a8967d Michael Hanselmann
      server.Stop()
214 3cd62121 Michael Hanselmann
  finally:
215 16a8967d Michael Hanselmann
    utils.RemovePidFile(constants.RAPI_PID)
216 8c229cc7 Oleksiy Mishchenko
217 8c229cc7 Oleksiy Mishchenko
218 8c229cc7 Oleksiy Mishchenko
if __name__ == '__main__':
219 8c229cc7 Oleksiy Mishchenko
  main()