VMware: raise more specific exceptions

In certain cases the exception handling for backend errors
would be handled too broadly. This patch elevates the backend exception
to the application so that it can treat specific errors.

In the application we can now handle the following specific exceptions:
- FileAlreadyExists - for example when moving a directory to a directory
that already exists
- InvalidProperty - for example when using neutron and opaque networks
are not supported
- AlreadyExists - for example a port group already exists
- NotAuthenticated - for example the operation is denied as because a
session is not established
- CannotDeleteFile - the file cannot be deleted
- FileFault - a file access exception
- FileLocked - an attempt is made to lock a file that is
already in use
- FileNotFound - the specific file does not exist

Change-Id: I789165f3879ec5df73f72c2737377e0fcfc99656
This commit is contained in:
Gary Kotton 2014-02-20 06:52:59 -08:00 committed by Davanum Srinivas
parent 1150ed22f9
commit 4eadacdb75
5 changed files with 152 additions and 10 deletions

View File

@ -251,7 +251,7 @@ class VMwareAPISession(object):
except exceptions.VimFaultException as excep:
# If this is due to an inactive session, we should re-create
# the session and retry.
if exceptions.NOT_AUTHENTICATED_FAULT in excep.fault_list:
if exceptions.NOT_AUTHENTICATED in excep.fault_list:
# The NotAuthenticated fault is set by the fault checker
# due to an empty response. An empty response could be a
# valid response; for e.g., response for the query to
@ -281,6 +281,10 @@ class VMwareAPISession(object):
else:
# no need to retry for other VIM faults like
# InvalidArgument
# Raise specific exceptions here if possible
if excep.fault_list:
LOG.debug(_("Fault list: %s"), excep.fault_list)
raise exceptions.get_fault_class(excep.fault_list[0])
raise
except exceptions.VimConnectionException:
@ -369,7 +373,10 @@ class VMwareAPISession(object):
"%(error)s.") % {'task': task,
'error': error_msg}
LOG.error(excep_msg)
raise exceptions.VimException(excep_msg)
error = task_info.error
name = error.fault.__class__.__name__
task_ex = exceptions.get_fault_class(name)(error_msg)
raise task_ex
def wait_for_lease_ready(self, lease):
"""Waits for the given lease to be ready.

View File

@ -17,7 +17,21 @@
Exception definitions.
"""
NOT_AUTHENTICATED_FAULT = "NotAuthenticated"
import six
from oslo.vmware.openstack.common.gettextutils import _
from oslo.vmware.openstack.common import log as logging
LOG = logging.getLogger(__name__)
ALREADY_EXISTS = 'AlreadyExists'
CANNOT_DELETE_FILE = 'CannotDeleteFile'
FILE_ALREADY_EXISTS = 'FileAlreadyExists'
FILE_FAULT = 'FileFault'
FILE_LOCKED = 'FileLocked'
FILE_NOT_FOUND = 'FileNotFound'
INVALID_PROPERTY = 'InvalidProperty'
NOT_AUTHENTICATED = 'NotAuthenticated'
class VimException(Exception):
@ -66,3 +80,88 @@ class VimFaultException(VimException):
class ImageTransferException(VimException):
"""Thrown when there is an error during image transfer."""
pass
class VMwareDriverException(Exception):
"""Base VMware Driver Exception
To correctly use this class, inherit from it and define
a 'msg_fmt' property. That msg_fmt will get printf'd
with the keyword arguments provided to the constructor.
"""
msg_fmt = _("An unknown exception occurred.")
def __init__(self, message=None, **kwargs):
self.kwargs = kwargs
if not message:
try:
message = self.msg_fmt % kwargs
except Exception:
# kwargs doesn't match a variable in the message
# log the issue and the kwargs
LOG.exception(_('Exception in string format operation'))
for name, value in six.iteritems(kwargs):
LOG.error("%s: %s" % (name, value))
# at least get the core message out if something happened
message = self.msg_fmt
super(VMwareDriverException, self).__init__(message)
class AlreadyExistsException(VMwareDriverException):
msg_fmt = _("Resource already exists.")
class CannotDeleteFileException(VMwareDriverException):
msg_fmt = _("Cannot delete file.")
class FileAlreadyExistsException(VMwareDriverException):
msg_fmt = _("File already exists.")
class FileFaultException(VMwareDriverException):
msg_fmt = _("File fault.")
class FileLockedException(VMwareDriverException):
msg_fmt = _("File locked.")
class FileNotFoundException(VMwareDriverException):
msg_fmt = _("File not found.")
class InvalidPropertyException(VMwareDriverException):
msg_fmt = _("Invalid property.")
class NotAuthenticatedException(VMwareDriverException):
msg_fmt = _("Not Authenticated.")
# Populate the fault registry with the exceptions that have
# special treatment.
_fault_classes_registry = {
ALREADY_EXISTS: AlreadyExistsException,
CANNOT_DELETE_FILE: CannotDeleteFileException,
FILE_ALREADY_EXISTS: FileAlreadyExistsException,
FILE_FAULT: FileFaultException,
FILE_LOCKED: FileLockedException,
FILE_NOT_FOUND: FileNotFoundException,
INVALID_PROPERTY: InvalidPropertyException,
NOT_AUTHENTICATED: NotAuthenticatedException,
}
def get_fault_class(name):
"""Get a named subclass of NovaException."""
name = str(name)
fault_class = _fault_classes_registry.get(name)
if not fault_class:
LOG.debug(_('Fault %s not matched.'), name)
fault_class = VMwareDriverException
return fault_class

View File

@ -135,8 +135,8 @@ class Vim(object):
# fault.
LOG.debug(_("RetrievePropertiesEx API response is empty; setting "
"fault to %s."),
exceptions.NOT_AUTHENTICATED_FAULT)
fault_list = [exceptions.NOT_AUTHENTICATED_FAULT]
exceptions.NOT_AUTHENTICATED)
fault_list = [exceptions.NOT_AUTHENTICATED]
else:
for obj_cont in response.objects:
if hasattr(obj_cont, 'missingSet'):
@ -195,8 +195,9 @@ class Vim(object):
doc = excep.document
detail = doc.childAtPath('/Envelope/Body/Fault/detail')
fault_list = []
for child in detail.getChildren():
fault_list.append(child.get('type'))
if detail:
for child in detail.getChildren():
fault_list.append(child.get('type'))
raise exceptions.VimFaultException(
fault_list, _("Web fault in %s.") % attr_name, excep)

View File

@ -200,7 +200,7 @@ class VMwareAPISessionTest(base.TestCase):
def api(*args, **kwargs):
raise exceptions.VimFaultException(
[exceptions.NOT_AUTHENTICATED_FAULT], None)
[exceptions.NOT_AUTHENTICATED], None)
module = mock.Mock()
module.api = api
@ -249,7 +249,7 @@ class VMwareAPISessionTest(base.TestCase):
api_session.invoke_api = mock.Mock(side_effect=invoke_api_side_effect)
task = mock.Mock()
self.assertRaises(exceptions.VimException,
self.assertRaises(exceptions.VMwareDriverException,
lambda: api_session.wait_for_task(task))
api_session.invoke_api.assert_called_with(vim_util,
'get_object_property',
@ -329,3 +329,38 @@ class VMwareAPISessionTest(base.TestCase):
api_session.invoke_api.assert_called_once_with(
vim_util, 'get_object_property', api_session.vim, lease,
'state')
def _poll_task_well_known_exceptions(self, fault,
expected_exception):
api_session = self._create_api_session(False)
def fake_invoke_api(self, module, method, *args, **kwargs):
task_info = mock.Mock()
task_info.progress = -1
task_info.state = 'error'
error = mock.Mock()
error.localizedMessage = "Error message"
error_fault = mock.Mock()
error_fault.__class__.__name__ = fault
error.fault = error_fault
task_info.error = error
return task_info
with (
mock.patch.object(api_session, 'invoke_api', fake_invoke_api)
):
self.assertRaises(expected_exception,
api_session._poll_task, 'fake-task')
def test_poll_task_well_known_exceptions(self):
for k, v in exceptions._fault_classes_registry.iteritems():
self._poll_task_well_known_exceptions(k, v)
def test_poll_task_unknown_exception(self):
_unknown_exceptions = {
'NoDiskSpace': exceptions.VMwareDriverException,
'RuntimeFault': exceptions.VMwareDriverException
}
for k, v in _unknown_exceptions.iteritems():
self._poll_task_well_known_exceptions(k, v)

View File

@ -71,7 +71,7 @@ class VimTest(base.TestCase):
vim.Vim._retrieve_properties_ex_fault_checker(None)
assert False
except exceptions.VimFaultException as ex:
self.assertEqual([exceptions.NOT_AUTHENTICATED_FAULT],
self.assertEqual([exceptions.NOT_AUTHENTICATED],
ex.fault_list)
def test_retrieve_properties_ex_fault_checker(self):