summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/compute/plugins/v3/fixed_ips.py2
-rw-r--r--nova/api/openstack/compute/plugins/v3/keypairs.py3
-rw-r--r--nova/api/openstack/extensions.py40
-rw-r--r--nova/tests/api/openstack/compute/test_v3_extensions.py37
4 files changed, 82 insertions, 0 deletions
diff --git a/nova/api/openstack/compute/plugins/v3/fixed_ips.py b/nova/api/openstack/compute/plugins/v3/fixed_ips.py
index e98b830bd..5fa4ae3c2 100644
--- a/nova/api/openstack/compute/plugins/v3/fixed_ips.py
+++ b/nova/api/openstack/compute/plugins/v3/fixed_ips.py
@@ -28,6 +28,7 @@ authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
class FixedIPController(object):
+ @extensions.expected_errors(404)
def show(self, req, id):
"""Return data about the given fixed ip."""
context = req.environ['nova.context']
@@ -55,6 +56,7 @@ class FixedIPController(object):
return fixed_ip_info
+ @extensions.expected_errors((400, 404))
def action(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
diff --git a/nova/api/openstack/compute/plugins/v3/keypairs.py b/nova/api/openstack/compute/plugins/v3/keypairs.py
index bf740641e..ab40b051c 100644
--- a/nova/api/openstack/compute/plugins/v3/keypairs.py
+++ b/nova/api/openstack/compute/plugins/v3/keypairs.py
@@ -55,6 +55,7 @@ class KeypairController(object):
self.api = compute_api.KeypairAPI()
@wsgi.serializers(xml=KeypairTemplate)
+ @extensions.expected_errors((400, 409, 413))
def create(self, req, body):
"""
Create or import keypair.
@@ -100,6 +101,7 @@ class KeypairController(object):
except exception.KeyPairExists as exc:
raise webob.exc.HTTPConflict(explanation=exc.format_message())
+ @extensions.expected_errors(404)
def delete(self, req, id):
"""
Delete a keypair with a given name
@@ -113,6 +115,7 @@ class KeypairController(object):
return webob.Response(status_int=202)
@wsgi.serializers(xml=KeypairTemplate)
+ @extensions.expected_errors(404)
def show(self, req, id):
"""Return data for the given key name."""
context = req.environ['nova.context']
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 6cbc5bb78..6fea5d35e 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -17,6 +17,7 @@
# under the License.
import abc
+import functools
import os
import webob.dec
@@ -451,3 +452,42 @@ class V3APIExtensionBase(object):
def version(self):
"""Version of the extension."""
pass
+
+
+def expected_errors(errors):
+ """Decorator for v3 API methods which specifies expected exceptions.
+
+ Specify which exceptions may occur when an API method is called. If an
+ unexpected exception occurs then return a 500 instead and ask the user
+ of the API to file a bug report.
+ """
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapped(*args, **kwargs):
+ try:
+ return f(*args, **kwargs)
+ except Exception as exc:
+ if isinstance(exc, webob.exc.WSGIHTTPException):
+ if isinstance(errors, int):
+ t_errors = (errors,)
+ else:
+ t_errors = errors
+ if exc.code in t_errors:
+ raise
+ elif isinstance(exc, exception.PolicyNotAuthorized):
+ # Note(cyeoh): Special case to handle
+ # PolicyNotAuthorized exceptions so every
+ # extension method does not need to wrap authorize
+ # calls. ResourceExceptionHandler silently
+ # converts NotAuthorized to HTTPForbidden
+ raise
+
+ LOG.exception(_("Unexpected exception in API method"))
+ msg = _('Unexpected API Error. Please report this at '
+ 'http://bugs.launchpad.net/nova/ and attach the Nova '
+ 'API log if possible.\n%s') % type(exc)
+ raise webob.exc.HTTPInternalServerError(explanation=msg)
+
+ return wrapped
+
+ return decorator
diff --git a/nova/tests/api/openstack/compute/test_v3_extensions.py b/nova/tests/api/openstack/compute/test_v3_extensions.py
index 97429ca45..ec472f38a 100644
--- a/nova/tests/api/openstack/compute/test_v3_extensions.py
+++ b/nova/tests/api/openstack/compute/test_v3_extensions.py
@@ -16,10 +16,12 @@
from oslo.config import cfg
import stevedore
+import webob.exc
from nova.api import openstack
from nova.api.openstack import compute
from nova.api.openstack.compute import plugins
+from nova.api.openstack import extensions
from nova import exception
from nova import test
@@ -139,3 +141,38 @@ class ExtensionLoadingTestCase(test.TestCase):
self.stubs.Set(plugins, 'LoadedExtensionInfo',
fake_loaded_extension_info)
self.assertRaises(exception.CoreAPIMissing, compute.APIRouterV3)
+
+ def test_extensions_expected_error(self):
+ @extensions.expected_errors(404)
+ def fake_func():
+ raise webob.exc.HTTPNotFound()
+
+ self.assertRaises(webob.exc.HTTPNotFound, fake_func)
+
+ def test_extensions_expected_error_from_list(self):
+ @extensions.expected_errors((404, 403))
+ def fake_func():
+ raise webob.exc.HTTPNotFound()
+
+ self.assertRaises(webob.exc.HTTPNotFound, fake_func)
+
+ def test_extensions_unexpected_error(self):
+ @extensions.expected_errors(404)
+ def fake_func():
+ raise webob.exc.HTTPConflict()
+
+ self.assertRaises(webob.exc.HTTPInternalServerError, fake_func)
+
+ def test_extensions_unexpected_error_from_list(self):
+ @extensions.expected_errors((404, 413))
+ def fake_func():
+ raise webob.exc.HTTPConflict()
+
+ self.assertRaises(webob.exc.HTTPInternalServerError, fake_func)
+
+ def test_extensions_unexpected_policy_not_authorized_error(self):
+ @extensions.expected_errors(404)
+ def fake_func():
+ raise exception.PolicyNotAuthorized(action="foo")
+
+ self.assertRaises(exception.PolicyNotAuthorized, fake_func)