diff options
| author | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-08-31 10:03:51 -0400 |
|---|---|---|
| committer | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-08-31 10:03:51 -0400 |
| commit | c54d6c3d1fcb0210e9f52097f1a1e85550c84bf6 (patch) | |
| tree | 575c30d0ab456f9f49cf2012ddb4f1e2c1997f22 /nova/api | |
| parent | 1ef59040aa1304a4682c6bcdaa3333372e7f8629 (diff) | |
| download | nova-c54d6c3d1fcb0210e9f52097f1a1e85550c84bf6.tar.gz nova-c54d6c3d1fcb0210e9f52097f1a1e85550c84bf6.tar.xz nova-c54d6c3d1fcb0210e9f52097f1a1e85550c84bf6.zip | |
First steps in reworking EC2 APIRequestHandler into separate Authenticate() and Router() WSGI apps
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/__init__.py | 4 | ||||
| -rw-r--r-- | nova/api/ec2/__init__.py | 151 | ||||
| -rw-r--r-- | nova/api/ec2/apirequesthandler.py | 126 |
3 files changed, 155 insertions, 126 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py index b9b9e3988..0166b7fc1 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -32,6 +32,10 @@ class API(wsgi.Router): def __init__(self): mapper = routes.Mapper() + # TODO(gundlach): EC2 RootController is replaced by this class; + # MetadataRequestHandlers isn't part of the EC2 API and thus can + # be dropped; and I'm leaving off CloudPipeRequestHandler until + # I hear that we need it. mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API()) mapper.connect("/ec2/{path_info:.*}", controller=ec2.API()) super(API, self).__init__(mapper) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py new file mode 100644 index 000000000..a4d9b95f9 --- /dev/null +++ b/nova/api/ec2/__init__.py @@ -0,0 +1,151 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Starting point for routing EC2 requests +""" + +import logging +import routes +import webob.exc +from webob.dec import wsgify + +from nova.api.ec2 import admin +from nova.api.ec2 import cloud +from nova import exception +from nova import utils +from nova.auth import manager + + +_log = logging.getLogger("api") +_log.setLevel(logging.DEBUG) + + +class API(wsgi.Middleware): + """Routing for all EC2 API requests.""" + + def __init__(self): + self.application = Authenticate(Router()) + +class Authenticate(wsgi.Middleware): + """Authenticates an EC2 request.""" + + @webob.dec.wsgify + def __call__(self, req): + #TODO(gundlach): where do arguments come from? + args = self.request.arguments + + # Read request signature. + try: + signature = args.pop('Signature')[0] + except: + raise webob.exc.HTTPBadRequest() + + # Make a copy of args for authentication and signature verification. + auth_params = {} + for key, value in args.items(): + auth_params[key] = value[0] + + # Get requested action and remove authentication args for final request. + try: + action = args.pop('Action')[0] + access = args.pop('AWSAccessKeyId')[0] + args.pop('SignatureMethod') + args.pop('SignatureVersion') + args.pop('Version') + args.pop('Timestamp') + except: + raise webob.exc.HTTPBadRequest() + + # Authenticate the request. + try: + (user, project) = manager.AuthManager().authenticate( + access, + signature, + auth_params, + req.method, + req.host, + req.path + ) + + except exception.Error, ex: + logging.debug("Authentication Failure: %s" % ex) + raise webob.exc.HTTPForbidden() + + _log.debug('action: %s' % action) + + for key, value in args.items(): + _log.debug('arg: %s\t\tval: %s' % (key, value)) + + # Authenticated! + req.environ['ec2.action'] = action + req.environ['ec2.context'] = APIRequestContext(user, project) + return self.application + + +class Router(wsgi.Application): + """ + Finds controller for a request, executes environ['ec2.action'] upon it, and + returns a response. + """ + def __init__(self): + self.map = routes.Mapper() + self.map.connect("/{controller_name}/") + self.controllers = dict(Cloud=cloud.CloudController(), + Admin=admin.AdminController()) + + def __call__(self, req): + # Obtain the appropriate controller for this request. + match = self.map.match(req.path) + if not match: + raise webob.exc.HTTPNotFound() + controller_name = match['controller_name'] + + try: + controller = self.controllers[controller_name] + except KeyError: + self._error('unhandled', 'no controller named %s' % controller_name) + return + + request = APIRequest(controller, req.environ['ec2.action']) + context = req.environ['ec2.context'] + try: + data = request.send(context, **args) + req.headers['Content-Type'] = 'text/xml' + return data + #TODO(gundlach) under what conditions would _error_callbock used to + #be called? What was 'failure' that you could call .raiseException + #on it? + except Exception, ex: + try: + #TODO + failure.raiseException() + except exception.ApiError as ex: + self._error(req, type(ex).__name__ + "." + ex.code, ex.message) + # TODO(vish): do something more useful with unknown exceptions + except Exception as ex: + self._error(type(ex).__name__, str(ex)) + + def _error(self, req, code, message): + req.status = 400 + req.headers['Content-Type'] = 'text/xml' + req.response = ('<?xml version="1.0"?>\n' + '<Response><Errors><Error><Code>%s</Code>' + '<Message>%s</Message></Error></Errors>' + '<RequestID>?</RequestID></Response>') % (code, message)) + diff --git a/nova/api/ec2/apirequesthandler.py b/nova/api/ec2/apirequesthandler.py deleted file mode 100644 index bbba60c02..000000000 --- a/nova/api/ec2/apirequesthandler.py +++ /dev/null @@ -1,126 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -APIRequestHandler, pulled unmodified out of nova.endpoint.api -""" - -import logging - -import tornado.web - -from nova import exception -from nova import utils -from nova.auth import manager - - -_log = logging.getLogger("api") -_log.setLevel(logging.DEBUG) - - -class APIRequestHandler(tornado.web.RequestHandler): - def get(self, controller_name): - self.execute(controller_name) - - @tornado.web.asynchronous - def execute(self, controller_name): - # Obtain the appropriate controller for this request. - try: - controller = self.application.controllers[controller_name] - except KeyError: - self._error('unhandled', 'no controller named %s' % controller_name) - return - - args = self.request.arguments - - # Read request signature. - try: - signature = args.pop('Signature')[0] - except: - raise tornado.web.HTTPError(400) - - # Make a copy of args for authentication and signature verification. - auth_params = {} - for key, value in args.items(): - auth_params[key] = value[0] - - # Get requested action and remove authentication args for final request. - try: - action = args.pop('Action')[0] - access = args.pop('AWSAccessKeyId')[0] - args.pop('SignatureMethod') - args.pop('SignatureVersion') - args.pop('Version') - args.pop('Timestamp') - except: - raise tornado.web.HTTPError(400) - - # Authenticate the request. - try: - (user, project) = manager.AuthManager().authenticate( - access, - signature, - auth_params, - self.request.method, - self.request.host, - self.request.path - ) - - except exception.Error, ex: - logging.debug("Authentication Failure: %s" % ex) - raise tornado.web.HTTPError(403) - - _log.debug('action: %s' % action) - - for key, value in args.items(): - _log.debug('arg: %s\t\tval: %s' % (key, value)) - - request = APIRequest(controller, action) - context = APIRequestContext(self, user, project) - d = request.send(context, **args) - # d.addCallback(utils.debug) - - # TODO: Wrap response in AWS XML format - d.addCallbacks(self._write_callback, self._error_callback) - - def _write_callback(self, data): - self.set_header('Content-Type', 'text/xml') - self.write(data) - self.finish() - - def _error_callback(self, failure): - try: - failure.raiseException() - except exception.ApiError as ex: - self._error(type(ex).__name__ + "." + ex.code, ex.message) - # TODO(vish): do something more useful with unknown exceptions - except Exception as ex: - self._error(type(ex).__name__, str(ex)) - raise - - def post(self, controller_name): - self.execute(controller_name) - - def _error(self, code, message): - self._status_code = 400 - self.set_header('Content-Type', 'text/xml') - self.write('<?xml version="1.0"?>\n') - self.write('<Response><Errors><Error><Code>%s</Code>' - '<Message>%s</Message></Error></Errors>' - '<RequestID>?</RequestID></Response>' % (code, message)) - self.finish() |
