Package ganeti :: Package server :: Module rapi
[hide private]
[frames] | no frames]

Source Code for Module ganeti.server.rapi

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30  """Ganeti Remote API master script. 
 31   
 32  """ 
 33   
 34  # pylint: disable=C0103,W0142 
 35   
 36  # C0103: Invalid name ganeti-watcher 
 37   
 38  import logging 
 39  import optparse 
 40  import sys 
 41   
 42  from ganeti import constants 
 43  from ganeti import http 
 44  from ganeti import daemon 
 45  from ganeti import ssconf 
 46  import ganeti.rpc.errors as rpcerr 
 47  from ganeti import serializer 
 48  from ganeti import pathutils 
 49  from ganeti.rapi import connector 
 50  from ganeti.rapi import baserlib 
 51  from ganeti.rapi.auth import basic_auth 
 52  from ganeti.rapi.auth import pam 
 53   
 54  import ganeti.http.auth   # pylint: disable=W0611 
 55  import ganeti.http.server 
56 57 58 -class RemoteApiRequestContext(object):
59 """Data structure for Remote API requests. 60 61 """
62 - def __init__(self):
63 self.handler = None 64 self.handler_fn = None 65 self.handler_access = None 66 self.body_data = None
67
68 69 -class RemoteApiHandler(http.auth.HttpServerRequestAuthentication, 70 http.server.HttpServerHandler):
71 """REST Request Handler Class. 72 73 """ 74 AUTH_REALM = "Ganeti Remote API" 75
76 - def __init__(self, authenticator, reqauth, _client_cls=None):
77 """Initializes this class. 78 79 @type authenticator: an implementation of {RapiAuthenticator} interface 80 @param authenticator: a class containing an implementation of 81 ValidateRequest function 82 @type reqauth: bool 83 @param reqauth: Whether to require authentication 84 85 """ 86 # pylint: disable=W0233 87 # it seems pylint doesn't see the second parent class there 88 http.server.HttpServerHandler.__init__(self) 89 http.auth.HttpServerRequestAuthentication.__init__(self) 90 self._client_cls = _client_cls 91 self._resmap = connector.Mapper() 92 self._authenticator = authenticator 93 self._reqauth = reqauth
94 95 @staticmethod
96 - def FormatErrorMessage(values):
97 """Formats the body of an error message. 98 99 @type values: dict 100 @param values: dictionary with keys C{code}, C{message} and C{explain}. 101 @rtype: tuple; (string, string) 102 @return: Content-type and response body 103 104 """ 105 return (http.HTTP_APP_JSON, serializer.DumpJson(values))
106
107 - def _GetRequestContext(self, req):
108 """Returns the context for a request. 109 110 The context is cached in the req.private variable. 111 112 """ 113 if req.private is None: 114 (HandlerClass, items, args) = \ 115 self._resmap.getController(req.request_path) 116 117 ctx = RemoteApiRequestContext() 118 ctx.handler = HandlerClass(items, args, req, _client_cls=self._client_cls) 119 120 method = req.request_method.upper() 121 try: 122 ctx.handler_fn = getattr(ctx.handler, method) 123 except AttributeError: 124 raise http.HttpNotImplemented("Method %s is unsupported for path %s" % 125 (method, req.request_path)) 126 127 ctx.handler_access = baserlib.GetHandlerAccess(ctx.handler, method) 128 129 # Require permissions definition (usually in the base class) 130 if ctx.handler_access is None: 131 raise AssertionError("Permissions definition missing") 132 133 # This is only made available in HandleRequest 134 ctx.body_data = None 135 136 req.private = ctx 137 138 # Check for expected attributes 139 assert req.private.handler 140 assert req.private.handler_fn 141 assert req.private.handler_access is not None 142 143 return req.private
144
145 - def AuthenticationRequired(self, req):
146 """Determine whether authentication is required. 147 148 """ 149 return self._reqauth
150
151 - def Authenticate(self, req):
152 """Checks whether a user can access a resource. 153 154 @return: username of an authenticated user or None otherwise 155 """ 156 ctx = self._GetRequestContext(req) 157 auth_user = self._authenticator.ValidateRequest( 158 req, ctx.handler_access, self.GetAuthRealm(req)) 159 if auth_user is None: 160 return False 161 162 ctx.handler.auth_user = auth_user 163 return True
164
165 - def HandleRequest(self, req):
166 """Handles a request. 167 168 """ 169 ctx = self._GetRequestContext(req) 170 171 # Deserialize request parameters 172 if req.request_body: 173 # RFC2616, 7.2.1: Any HTTP/1.1 message containing an entity-body SHOULD 174 # include a Content-Type header field defining the media type of that 175 # body. [...] If the media type remains unknown, the recipient SHOULD 176 # treat it as type "application/octet-stream". 177 req_content_type = req.request_headers.get(http.HTTP_CONTENT_TYPE, 178 http.HTTP_APP_OCTET_STREAM) 179 if req_content_type.lower() != http.HTTP_APP_JSON.lower(): 180 raise http.HttpUnsupportedMediaType() 181 182 try: 183 ctx.body_data = serializer.LoadJson(req.request_body) 184 except Exception: 185 raise http.HttpBadRequest(message="Unable to parse JSON data") 186 else: 187 ctx.body_data = None 188 189 try: 190 result = ctx.handler_fn() 191 except rpcerr.TimeoutError: 192 raise http.HttpGatewayTimeout() 193 except rpcerr.ProtocolError, err: 194 raise http.HttpBadGateway(str(err)) 195 196 req.resp_headers[http.HTTP_CONTENT_TYPE] = http.HTTP_APP_JSON 197 198 return serializer.DumpJson(result)
199
200 201 -def CheckRapi(options, args):
202 """Initial checks whether to run or exit with a failure. 203 204 """ 205 if args: # rapi doesn't take any arguments 206 print >> sys.stderr, ("Usage: %s [-f] [-d] [-p port] [-b ADDRESS]" % 207 sys.argv[0]) 208 sys.exit(constants.EXIT_FAILURE) 209 210 ssconf.CheckMaster(options.debug) 211 212 # Read SSL certificate (this is a little hackish to read the cert as root) 213 if options.ssl: 214 options.ssl_params = http.HttpSslParams(ssl_key_path=options.ssl_key, 215 ssl_cert_path=options.ssl_cert) 216 else: 217 options.ssl_params = None
218
219 220 -def PrepRapi(options, _):
221 """Prep remote API function, executed with the PID file held. 222 223 """ 224 mainloop = daemon.Mainloop() 225 226 if options.pamauth: 227 options.reqauth = True 228 authenticator = pam.PamAuthenticator() 229 else: 230 authenticator = basic_auth.BasicAuthenticator() 231 232 handler = RemoteApiHandler(authenticator, options.reqauth) 233 234 server = \ 235 http.server.HttpServer(mainloop, options.bind_address, options.port, 236 handler, 237 ssl_params=options.ssl_params, ssl_verify_peer=False) 238 server.Start() 239 240 return (mainloop, server)
241
242 243 -def ExecRapi(options, args, prep_data): # pylint: disable=W0613
244 """Main remote API function, executed with the PID file held. 245 246 """ 247 248 (mainloop, server) = prep_data 249 try: 250 mainloop.Run() 251 finally: 252 logging.error("RAPI Daemon Failed") 253 server.Stop() 254
255 256 -def Main():
257 """Main function. 258 259 """ 260 parser = optparse.OptionParser(description="Ganeti Remote API", 261 usage=("%prog [-f] [-d] [-p port] [-b ADDRESS]" 262 " [-i INTERFACE]"), 263 version="%%prog (ganeti) %s" % 264 constants.RELEASE_VERSION) 265 parser.add_option("--require-authentication", dest="reqauth", 266 default=False, action="store_true", 267 help=("Disable anonymous HTTP requests and require" 268 " authentication")) 269 parser.add_option("--pam-authentication", dest="pamauth", 270 default=False, action="store_true", 271 help=("Enable RAPI authentication and authorization via" 272 " PAM")) 273 274 daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi, 275 default_ssl_cert=pathutils.RAPI_CERT_FILE, 276 default_ssl_key=pathutils.RAPI_CERT_FILE)
277