Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ b5b67ef9

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 16a8967d Michael Hanselmann
      logging.exception("Error while handling the %s request", method)
138 16a8967d Michael Hanselmann
      raise
139 3cd62121 Michael Hanselmann
140 3cd62121 Michael Hanselmann
    return result
141 8c229cc7 Oleksiy Mishchenko
142 8c229cc7 Oleksiy Mishchenko
143 8c229cc7 Oleksiy Mishchenko
def ParseOptions():
144 8c229cc7 Oleksiy Mishchenko
  """Parse the command line options.
145 8c229cc7 Oleksiy Mishchenko
146 c41eea6e Iustin Pop
  @return: (options, args) as from OptionParser.parse_args()
147 8c229cc7 Oleksiy Mishchenko
148 8c229cc7 Oleksiy Mishchenko
  """
149 8c229cc7 Oleksiy Mishchenko
  parser = optparse.OptionParser(description="Ganeti Remote API",
150 8c229cc7 Oleksiy Mishchenko
                    usage="%prog [-d] [-p port]",
151 8c229cc7 Oleksiy Mishchenko
                    version="%%prog (ganeti) %s" %
152 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_VERSION)
153 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-d", "--debug", dest="debug",
154 8c229cc7 Oleksiy Mishchenko
                    help="Enable some debug messages",
155 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
156 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-p", "--port", dest="port",
157 8c229cc7 Oleksiy Mishchenko
                    help="Port to run API (%s default)." %
158 8c229cc7 Oleksiy Mishchenko
                                 constants.RAPI_PORT,
159 8c229cc7 Oleksiy Mishchenko
                    default=constants.RAPI_PORT, type="int")
160 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-S", "--https", dest="ssl",
161 8c229cc7 Oleksiy Mishchenko
                    help="Secure HTTP protocol with SSL",
162 8c229cc7 Oleksiy Mishchenko
                    default=False, action="store_true")
163 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-K", "--ssl-key", dest="ssl_key",
164 8c229cc7 Oleksiy Mishchenko
                    help="SSL key",
165 8c229cc7 Oleksiy Mishchenko
                    default=None, type="string")
166 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
167 8c229cc7 Oleksiy Mishchenko
                    help="SSL certificate",
168 8c229cc7 Oleksiy Mishchenko
                    default=None, type="string")
169 8c229cc7 Oleksiy Mishchenko
  parser.add_option("-f", "--foreground", dest="fork",
170 8c229cc7 Oleksiy Mishchenko
                    help="Don't detach from the current terminal",
171 8c229cc7 Oleksiy Mishchenko
                    default=True, action="store_false")
172 8c229cc7 Oleksiy Mishchenko
173 8c229cc7 Oleksiy Mishchenko
  options, args = parser.parse_args()
174 8c229cc7 Oleksiy Mishchenko
175 8c229cc7 Oleksiy Mishchenko
  if len(args) != 0:
176 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
177 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
178 8c229cc7 Oleksiy Mishchenko
179 8c229cc7 Oleksiy Mishchenko
  if options.ssl and not (options.ssl_cert and options.ssl_key):
180 8c229cc7 Oleksiy Mishchenko
    print >> sys.stderr, ("For secure mode please provide "
181 8c229cc7 Oleksiy Mishchenko
                         "--ssl-key and --ssl-cert arguments")
182 8c229cc7 Oleksiy Mishchenko
    sys.exit(1)
183 8c229cc7 Oleksiy Mishchenko
184 8c229cc7 Oleksiy Mishchenko
  return options, args
185 8c229cc7 Oleksiy Mishchenko
186 8c229cc7 Oleksiy Mishchenko
187 8c229cc7 Oleksiy Mishchenko
def main():
188 8c229cc7 Oleksiy Mishchenko
  """Main function.
189 8c229cc7 Oleksiy Mishchenko
190 8c229cc7 Oleksiy Mishchenko
  """
191 8c229cc7 Oleksiy Mishchenko
  options, args = ParseOptions()
192 3cd62121 Michael Hanselmann
193 5675cd1f Iustin Pop
  ssconf.CheckMaster(options.debug)
194 5675cd1f Iustin Pop
195 8c229cc7 Oleksiy Mishchenko
  if options.fork:
196 8c229cc7 Oleksiy Mishchenko
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
197 3cd62121 Michael Hanselmann
198 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
199 441e7cfd Oleksiy Mishchenko
                     stderr_logging=not options.fork)
200 441e7cfd Oleksiy Mishchenko
201 99e88451 Iustin Pop
  utils.WritePidFile(constants.RAPI_PID)
202 3cd62121 Michael Hanselmann
  try:
203 16a8967d Michael Hanselmann
    mainloop = daemon.Mainloop()
204 23e46494 Michael Hanselmann
    server = RemoteApiHttpServer(mainloop, "", options.port)
205 16a8967d Michael Hanselmann
    server.Start()
206 3cd62121 Michael Hanselmann
    try:
207 16a8967d Michael Hanselmann
      mainloop.Run()
208 3cd62121 Michael Hanselmann
    finally:
209 16a8967d Michael Hanselmann
      server.Stop()
210 3cd62121 Michael Hanselmann
  finally:
211 16a8967d Michael Hanselmann
    utils.RemovePidFile(constants.RAPI_PID)
212 8c229cc7 Oleksiy Mishchenko
213 8c229cc7 Oleksiy Mishchenko
214 8c229cc7 Oleksiy Mishchenko
if __name__ == '__main__':
215 8c229cc7 Oleksiy Mishchenko
  main()