diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 216f27b86cd3..af175990fe3b 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -18,20 +18,23 @@ use = egg:Paste#urlmap /2009-04-04: ec2metadata [pipeline:ec2cloud] -pipeline = logrequest ec2noauth cloudrequest authorizer ec2executor +pipeline = ec2faultwrap logrequest ec2noauth cloudrequest authorizer ec2executor # NOTE(vish): use the following pipeline for deprecated auth -#pipeline = logrequest authenticate cloudrequest authorizer ec2executor +#pipeline = ec2faultwrap logrequest authenticate cloudrequest authorizer ec2executor [pipeline:ec2admin] -pipeline = logrequest ec2noauth adminrequest authorizer ec2executor +pipeline = ec2faultwrap logrequest ec2noauth adminrequest authorizer ec2executor # NOTE(vish): use the following pipeline for deprecated auth -#pipeline = logrequest authenticate adminrequest authorizer ec2executor +#pipeline = ec2faultwrap logrequest authenticate adminrequest authorizer ec2executor [pipeline:ec2metadata] -pipeline = logrequest ec2md +pipeline = ec2faultwrap logrequest ec2md [pipeline:ec2versions] -pipeline = logrequest ec2ver +pipeline = ec2faultwrap logrequest ec2ver + +[filter:ec2faultwrap] +paste.filter_factory = nova.api.ec2:FaultWrapper.factory [filter:logrequest] paste.filter_factory = nova.api.ec2:RequestLogging.factory diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index ebb8c7bff380..db92ca053558 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -36,6 +36,7 @@ from nova import utils from nova import wsgi from nova.api.ec2 import apirequest from nova.api.ec2 import ec2utils +from nova.api.ec2 import faults from nova.auth import manager FLAGS = flags.FLAGS @@ -49,6 +50,19 @@ flags.DEFINE_integer('lockout_window', 15, flags.DECLARE('use_forwarded_for', 'nova.api.auth') +## Fault Wrapper around all EC2 requests ## +class FaultWrapper(wsgi.Middleware): + """Calls the middleware stack, captures any exceptions into faults.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + try: + return req.get_response(self.application) + except Exception as ex: + LOG.exception(_("FaultWrapper: %s"), unicode(ex)) + return faults.Fault(webob.exc.HTTPInternalServerError()) + + class RequestLogging(wsgi.Middleware): """Access-Log akin logging for all EC2 API requests.""" diff --git a/nova/api/ec2/faults.py b/nova/api/ec2/faults.py new file mode 100644 index 000000000000..9e47702d9293 --- /dev/null +++ b/nova/api/ec2/faults.py @@ -0,0 +1,64 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +import webob.dec +import webob.exc + +from nova import utils +from nova import context +from nova import flags + +FLAGS = flags.FLAGS + + +class Fault(webob.exc.HTTPException): + """Captures exception and return REST Response.""" + + def __init__(self, exception): + """Create a response for the given webob.exc.exception.""" + self.wrapped_exc = exception + + @webob.dec.wsgify + def __call__(self, req): + """Generate a WSGI response based on the exception passed to ctor.""" + code = self.wrapped_exc.status_int + message = self.wrapped_exc.explanation + + if code == 501: + message = "The requested function is not supported" + code = str(code) + + if 'AWSAccessKeyId' not in req.params: + raise webob.exc.HTTPBadRequest() + user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':') + project_id = project_id or user_id + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + + ctxt = context.RequestContext(user_id, + project_id, + remote_address=remote_address) + + resp = webob.Response() + resp.status = self.wrapped_exc.status_int + resp.headers['Content-Type'] = 'text/xml' + resp.body = str('\n' + '%s' + '%s' + '%s' % + (utils.utf8(code), utils.utf8(message), + utils.utf8(ctxt.request_id))) + + return resp diff --git a/nova/tests/api/ec2/test_faults.py b/nova/tests/api/ec2/test_faults.py new file mode 100644 index 000000000000..be2b5ffe2fb6 --- /dev/null +++ b/nova/tests/api/ec2/test_faults.py @@ -0,0 +1,34 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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. + +import webob + +from nova import test +from nova.api.ec2 import faults + + +class TestFaults(test.TestCase): + """Tests covering ec2 Fault class.""" + + def test_fault_exception(self): + """Ensure the status_int is set correctly on faults""" + fault = faults.Fault(webob.exc.HTTPBadRequest( + explanation='test')) + self.assertTrue(isinstance(fault.wrapped_exc, + webob.exc.HTTPBadRequest)) + + def test_fault_exception_status_int(self): + """Ensure the status_int is set correctly on faults""" + fault = faults.Fault(webob.exc.HTTPNotFound(explanation='test')) + self.assertEquals(fault.wrapped_exc.status_int, 404)