Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 77e1d753

History | View | Annotate | Download (6.9 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
""" Ganeti Remote API master script.
22
"""
23

    
24
import glob
25
import logging
26
import optparse
27
import sys
28
import os
29
import os.path
30
import signal
31

    
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import http
35
from ganeti import daemon
36
from ganeti import ssconf
37
from ganeti import utils
38
from ganeti import luxi
39
from ganeti.rapi import connector
40

    
41
import ganeti.http.auth
42
import ganeti.http.server
43

    
44

    
45
class RemoteApiRequestContext(object):
46
  """Data structure for Remote API requests.
47

    
48
  """
49
  def __init__(self):
50
    self.handler = None
51
    self.handler_fn = None
52
    self.handler_access = None
53

    
54

    
55
class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
56
                          http.server.HttpServer):
57
  """REST Request Handler Class.
58

    
59
  """
60
  AUTH_REALM = "Ganeti Remote API"
61

    
62
  def __init__(self, *args, **kwargs):
63
    http.server.HttpServer.__init__(self, *args, **kwargs)
64
    http.auth.HttpServerRequestAuthentication.__init__(self)
65
    self._resmap = connector.Mapper()
66

    
67
    # Load password file
68
    if os.path.isfile(constants.RAPI_USERS_FILE):
69
      self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
70
    else:
71
      self._users = None
72

    
73
  def _GetRequestContext(self, req):
74
    """Returns the context for a request.
75

    
76
    The context is cached in the req.private variable.
77

    
78
    """
79
    if req.private is None:
80
      (HandlerClass, items, args) = \
81
                     self._resmap.getController(req.request_path)
82

    
83
      ctx = RemoteApiRequestContext()
84
      ctx.handler = HandlerClass(items, args, req)
85

    
86
      method = req.request_method.upper()
87
      try:
88
        ctx.handler_fn = getattr(ctx.handler, method)
89
      except AttributeError, err:
90
        raise http.HttpBadRequest()
91

    
92
      ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None)
93

    
94
      # Require permissions definition (usually in the base class)
95
      if ctx.handler_access is None:
96
        raise AssertionError("Permissions definition missing")
97

    
98
      req.private = ctx
99

    
100
    return req.private
101

    
102
  def GetAuthRealm(self, req):
103
    """Override the auth realm for queries.
104

    
105
    """
106
    ctx = self._GetRequestContext(req)
107
    if ctx.handler_access:
108
      return self.AUTH_REALM
109
    else:
110
      return None
111

    
112
  def Authenticate(self, req, username, password):
113
    """Checks whether a user can access a resource.
114

    
115
    """
116
    ctx = self._GetRequestContext(req)
117

    
118
    # Check username and password
119
    valid_user = False
120
    if self._users:
121
      user = self._users.get(username, None)
122
      if user and user.password == password:
123
        valid_user = True
124

    
125
    if not valid_user:
126
      # Unknown user or password wrong
127
      return False
128

    
129
    if (not ctx.handler_access or
130
        set(user.options).intersection(ctx.handler_access)):
131
      # Allow access
132
      return True
133

    
134
    # Access forbidden
135
    raise http.HttpForbidden()
136

    
137
  def HandleRequest(self, req):
138
    """Handles a request.
139

    
140
    """
141
    ctx = self._GetRequestContext(req)
142

    
143
    try:
144
      result = ctx.handler_fn()
145
      sn = ctx.handler.getSerialNumber()
146
      if sn:
147
        req.response_headers[http.HTTP_ETAG] = str(sn)
148
    except luxi.TimeoutError:
149
      raise http.HttpGatewayTimeout()
150
    except luxi.ProtocolError, err:
151
      raise http.HttpBadGateway(str(err))
152
    except:
153
      method = req.request_method.upper()
154
      logging.exception("Error while handling the %s request", method)
155
      raise
156

    
157
    return result
158

    
159

    
160
def ParseOptions():
161
  """Parse the command line options.
162

    
163
  @return: (options, args) as from OptionParser.parse_args()
164

    
165
  """
166
  parser = optparse.OptionParser(description="Ganeti Remote API",
167
                    usage="%prog [-d] [-p port]",
168
                    version="%%prog (ganeti) %s" %
169
                                 constants.RAPI_VERSION)
170
  parser.add_option("-d", "--debug", dest="debug",
171
                    help="Enable some debug messages",
172
                    default=False, action="store_true")
173
  parser.add_option("-p", "--port", dest="port",
174
                    help="Port to run API (%s default)." %
175
                                 constants.RAPI_PORT,
176
                    default=constants.RAPI_PORT, type="int")
177
  parser.add_option("--no-ssl", dest="ssl",
178
                    help="Do not secure HTTP protocol with SSL",
179
                    default=True, action="store_false")
180
  parser.add_option("-K", "--ssl-key", dest="ssl_key",
181
                    help="SSL key",
182
                    default=constants.RAPI_CERT_FILE, type="string")
183
  parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
184
                    help="SSL certificate",
185
                    default=constants.RAPI_CERT_FILE, type="string")
186
  parser.add_option("-f", "--foreground", dest="fork",
187
                    help="Don't detach from the current terminal",
188
                    default=True, action="store_false")
189

    
190
  options, args = parser.parse_args()
191

    
192
  if len(args) != 0:
193
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
194
    sys.exit(1)
195

    
196
  if options.ssl and not (options.ssl_cert and options.ssl_key):
197
    print >> sys.stderr, ("For secure mode please provide "
198
                         "--ssl-key and --ssl-cert arguments")
199
    sys.exit(1)
200

    
201
  return options, args
202

    
203

    
204
def main():
205
  """Main function.
206

    
207
  """
208
  options, args = ParseOptions()
209

    
210
  if options.fork:
211
    utils.CloseFDs()
212

    
213
  if options.ssl:
214
    # Read SSL certificate
215
    try:
216
      ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key,
217
                                      ssl_cert_path=options.ssl_cert)
218
    except Exception, err:
219
      sys.stderr.write("Can't load the SSL certificate/key: %s\n" % (err,))
220
      sys.exit(1)
221
  else:
222
    ssl_params = None
223

    
224
  ssconf.CheckMaster(options.debug)
225

    
226
  if options.fork:
227
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
228

    
229
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
230
                     stderr_logging=not options.fork)
231

    
232
  utils.WritePidFile(constants.RAPI_PID)
233
  try:
234
    mainloop = daemon.Mainloop()
235
    server = RemoteApiHttpServer(mainloop, "", options.port,
236
                                 ssl_params=ssl_params, ssl_verify_peer=False)
237
    server.Start()
238
    try:
239
      mainloop.Run()
240
    finally:
241
      server.Stop()
242
  finally:
243
    utils.RemovePidFile(constants.RAPI_PID)
244

    
245

    
246
if __name__ == '__main__':
247
  main()