Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 85414b69

History | View | Annotate | Download (6.3 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.rapi import connector
39

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

    
43

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

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

    
53

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

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

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

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

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

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

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

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

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

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

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

    
97
      req.private = ctx
98

    
99
    return req.private
100

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

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

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

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

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

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

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

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

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

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

    
142
    try:
143
      result = ctx.handler_fn()
144
      sn = ctx.handler.getSerialNumber()
145
      if sn:
146
        req.response_headers[http.HTTP_ETAG] = str(sn)
147
    except:
148
      method = req.request_method.upper()
149
      logging.exception("Error while handling the %s request", method)
150
      raise
151

    
152
    return result
153

    
154

    
155
def ParseOptions():
156
  """Parse the command line options.
157

    
158
  @return: (options, args) as from OptionParser.parse_args()
159

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

    
185
  options, args = parser.parse_args()
186

    
187
  if len(args) != 0:
188
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
189
    sys.exit(1)
190

    
191
  if options.ssl and not (options.ssl_cert and options.ssl_key):
192
    print >> sys.stderr, ("For secure mode please provide "
193
                         "--ssl-key and --ssl-cert arguments")
194
    sys.exit(1)
195

    
196
  return options, args
197

    
198

    
199
def main():
200
  """Main function.
201

    
202
  """
203
  options, args = ParseOptions()
204

    
205
  if options.fork:
206
    utils.CloseFDs()
207

    
208
  ssconf.CheckMaster(options.debug)
209

    
210
  if options.fork:
211
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
212

    
213
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
214
                     stderr_logging=not options.fork)
215

    
216
  utils.WritePidFile(constants.RAPI_PID)
217
  try:
218
    mainloop = daemon.Mainloop()
219
    server = RemoteApiHttpServer(mainloop, "", options.port)
220
    server.Start()
221
    try:
222
      mainloop.Run()
223
    finally:
224
      server.Stop()
225
  finally:
226
    utils.RemovePidFile(constants.RAPI_PID)
227

    
228

    
229
if __name__ == '__main__':
230
  main()