Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-rapi @ b5b67ef9

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
      logging.exception("Error while handling the %s request", method)
138
      raise
139

    
140
    return result
141

    
142

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

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

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

    
173
  options, args = parser.parse_args()
174

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

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

    
184
  return options, args
185

    
186

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

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

    
193
  ssconf.CheckMaster(options.debug)
194

    
195
  if options.fork:
196
    utils.Daemonize(logfile=constants.LOG_RAPISERVER)
197

    
198
  utils.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
199
                     stderr_logging=not options.fork)
200

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

    
213

    
214
if __name__ == '__main__':
215
  main()