Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ 6906a9d8

History | View | Annotate | Download (6 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) = self._resmap.getController(req.request_path)
80

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

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

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

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

    
96
      req.private = ctx
97

    
98
    return req.private
99

    
100
  def Authenticate(self, req, username, password):
101
    """Checks whether a user can access a resource.
102

    
103
    """
104
    ctx = self._GetRequestContext(req)
105

    
106
    # Check username and password
107
    valid_user = False
108
    if self._users:
109
      user = self._users.get(username, None)
110
      if user and user.password == password:
111
        valid_user = True
112

    
113
    if not valid_user:
114
      # Unknown user or password wrong
115
      return False
116

    
117
    if (not ctx.handler_access or
118
        set(user.options).intersection(ctx.handler_access)):
119
      # Allow access
120
      return True
121

    
122
    # Access forbidden
123
    raise http.HttpForbidden()
124

    
125
  def HandleRequest(self, req):
126
    """Handles a request.
127

    
128
    """
129
    ctx = self._GetRequestContext(req)
130

    
131
    try:
132
      result = ctx.handler_fn()
133
      sn = ctx.handler.getSerialNumber()
134
      if sn:
135
        req.response_headers[http.HTTP_ETAG] = str(sn)
136
    except:
137
      method = req.request_method.upper()
138
      logging.exception("Error while handling the %s request", method)
139
      raise
140

    
141
    return result
142

    
143

    
144
def ParseOptions():
145
  """Parse the command line options.
146

    
147
  @return: (options, args) as from OptionParser.parse_args()
148

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

    
174
  options, args = parser.parse_args()
175

    
176
  if len(args) != 0:
177
    print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
178
    sys.exit(1)
179

    
180
  if options.ssl and not (options.ssl_cert and options.ssl_key):
181
    print >> sys.stderr, ("For secure mode please provide "
182
                         "--ssl-key and --ssl-cert arguments")
183
    sys.exit(1)
184

    
185
  return options, args
186

    
187

    
188
def main():
189
  """Main function.
190

    
191
  """
192
  options, args = ParseOptions()
193

    
194
  if options.fork:
195
    utils.CloseFDs()
196

    
197
  ssconf.CheckMaster(options.debug)
198

    
199
  if options.fork:
200
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
201

    
202
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
203
                     stderr_logging=not options.fork)
204

    
205
  utils.WritePidFile(constants.RAPI_PID)
206
  try:
207
    mainloop = daemon.Mainloop()
208
    server = RemoteApiHttpServer(mainloop, "", options.port)
209
    server.Start()
210
    try:
211
      mainloop.Run()
212
    finally:
213
      server.Stop()
214
  finally:
215
    utils.RemovePidFile(constants.RAPI_PID)
216

    
217

    
218
if __name__ == '__main__':
219
  main()