summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorMichael Gundlach <michael.gundlach@rackspace.com>2010-08-31 10:03:51 -0400
committerMichael Gundlach <michael.gundlach@rackspace.com>2010-08-31 10:03:51 -0400
commitc54d6c3d1fcb0210e9f52097f1a1e85550c84bf6 (patch)
tree575c30d0ab456f9f49cf2012ddb4f1e2c1997f22 /nova/api
parent1ef59040aa1304a4682c6bcdaa3333372e7f8629 (diff)
downloadnova-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__.py4
-rw-r--r--nova/api/ec2/__init__.py151
-rw-r--r--nova/api/ec2/apirequesthandler.py126
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()