Adds a method for overriding specific API messages.

Also provides a specific error message for dependent snapshots
during volume deletion.

Fixes bug 1037241. Fixes bug 1020326.

Change-Id: I40e5f537bc1806ec97c21f3eeea1d74beca04250
This commit is contained in:
Tihomir Trifonov 2012-09-05 15:10:10 +03:00 committed by Gabriel Hurley
parent eb21d51f75
commit ceb22f13b9
5 changed files with 86 additions and 4 deletions

View File

@ -37,7 +37,15 @@ class DeleteVolume(tables.DeleteAction):
data_type_plural = _("Volumes")
def delete(self, request, obj_id):
api.volume_delete(request, obj_id)
obj = self.table.get_object_by_id(obj_id)
name = self.table.get_object_display(obj)
try:
api.volume_delete(request, obj_id)
except:
msg = _('Unable to delete volume "%s". One or more snapshots '
'depend on it.')
exceptions.check_message(["snapshots", "dependent"], msg % name)
raise
def allowed(self, request, volume=None):
if volume:

View File

@ -180,6 +180,57 @@ class VolumeViewTests(test.TestCase):
' volumes.']
self.assertEqual(res.context['form'].errors['__all__'], expected_error)
@test.create_stubs({api: ('volume_list',
'volume_delete',
'server_list')})
def test_delete_volume(self):
volume = self.volumes.first()
formData = {'action':
'volumes__delete__%s' % volume.id}
api.volume_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn(self.volumes.list())
api.volume_delete(IsA(http.HttpRequest), volume.id)
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.volume_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
self.mox.ReplayAll()
url = reverse('horizon:nova:volumes:index')
res = self.client.post(url, formData, follow=True)
self.assertMessageCount(res, count=0)
@test.create_stubs({api: ('volume_list',
'volume_delete',
'server_list')})
def test_delete_volume_error_existing_snapshot(self):
volume = self.volumes.first()
formData = {'action':
'volumes__delete__%s' % volume.id}
exc = self.exceptions.cinder.__class__(400,
"error: dependent snapshots")
api.volume_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn(self.volumes.list())
api.volume_delete(IsA(http.HttpRequest), volume.id). \
AndRaise(exc)
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.volume_list(IsA(http.HttpRequest), search_opts=None).\
AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
self.mox.ReplayAll()
url = reverse('horizon:nova:volumes:index')
res = self.client.post(url, formData, follow=True)
self.assertMessageCount(res, error=1)
self.assertEqual(list(res.context['messages'])[0].message,
u'Unable to delete volume "%s". '
u'One or more snapshots depend on it.' %
volume.display_name)
@test.create_stubs({api: ('volume_get',), api.nova: ('server_list',)})
def test_edit_attachments(self):
volume = self.volumes.first()

View File

@ -206,6 +206,18 @@ def error_color(msg):
return termcolors.colorize(msg, **PALETTE['ERROR'])
def check_message(keywords, message):
"""
Checks an exception for given keywords and raises a new ``ActionError``
with the desired message if the keywords are found. This allows selective
control over API error messages.
"""
exc_type, exc_value, exc_traceback = sys.exc_info()
if set(str(exc_value).split(" ")).issuperset(set(keywords)):
exc_value._safe_message = message
raise
def handle(request, message=None, redirect=None, ignore=False,
escalate=False, log_level=None, force_log=None):
""" Centralized error handling for Horizon.
@ -254,6 +266,9 @@ def handle(request, message=None, redirect=None, ignore=False,
# We trust messages from our own exceptions
if issubclass(exc_type, HorizonException):
message = exc_value
# Check for an override message
elif getattr(exc_value, "_safe_message", None):
message = exc_value._safe_message
# If the message has a placeholder for the exception, fill it in
elif message and "%(exc)s" in message:
message = message % {"exc": exc_value}

View File

@ -513,12 +513,16 @@ class BatchAction(Action):
self.success_ids.append(datum_id)
LOG.info('%s: "%s"' %
(self._conjugate(past=True), datum_display))
except:
except Exception, ex:
# Handle the exception but silence it since we'll display
# an aggregate error message later. Otherwise we'd get
# multiple error messages displayed to the user.
exceptions.handle(request, ignore=True)
action_failure.append(datum_display)
if getattr(ex, "_safe_message", None):
ignore = False
else:
ignore = True
action_failure.append(datum_display)
exceptions.handle(request, ignore=ignore)
#Begin with success message class, downgrade to info if problems
success_message_level = messages.success

View File

@ -17,6 +17,7 @@ from keystoneclient import exceptions as keystone_exceptions
from novaclient import exceptions as nova_exceptions
from quantumclient.common import exceptions as quantum_exceptions
from swiftclient import client as swift_exceptions
from cinderclient import exceptions as cinder_exceptions
from .utils import TestDataContainer
@ -61,3 +62,6 @@ def data(TEST):
swift_exception = swift_exceptions.ClientException
TEST.exceptions.swift = create_stubbed_exception(swift_exception)
cinder_exception = cinder_exceptions.BadRequest
TEST.exceptions.cinder = create_stubbed_exception(cinder_exception)