Merge "LVM: Fix delete volume error due to lvs failure"

This commit is contained in:
Zuul 2021-04-01 17:58:41 +00:00 committed by Gerrit Code Review
commit 6124c627d6
8 changed files with 78 additions and 11 deletions

View File

@ -286,6 +286,8 @@ class LVM(executor.Executor):
return LVM._supports_pvs_ignoreskippedcluster return LVM._supports_pvs_ignoreskippedcluster
@staticmethod @staticmethod
@utils.retry(retry=utils.retry_if_exit_code, retry_param=139, interval=0.5,
backoff_rate=0.5) # Bug#1901783
def get_lv_info(root_helper, vg_name=None, lv_name=None): def get_lv_info(root_helper, vg_name=None, lv_name=None):
"""Retrieve info about LVs (all, in a VG, or a single LV). """Retrieve info about LVs (all, in a VG, or a single LV).
@ -653,7 +655,7 @@ class LVM(executor.Executor):
# order to prevent a race condition. # order to prevent a race condition.
self._wait_for_volume_deactivation(name) self._wait_for_volume_deactivation(name)
@utils.retry(exceptions=exception.VolumeNotDeactivated, retries=5, @utils.retry(retry_param=exception.VolumeNotDeactivated, retries=5,
backoff_rate=2) backoff_rate=2)
def _wait_for_volume_deactivation(self, name): def _wait_for_volume_deactivation(self, name):
LOG.debug("Checking to see if volume %s has been deactivated.", LOG.debug("Checking to see if volume %s has been deactivated.",

View File

@ -231,6 +231,27 @@ class BrickLvmTestCase(test.TestCase):
'sudo', vg_name='fake-vg') 'sudo', vg_name='fake-vg')
) )
@mock.patch('tenacity.nap.sleep', mock.Mock())
@mock.patch.object(brick.putils, 'execute')
def test_get_lv_info_retry(self, exec_mock):
exec_mock.side_effect = (
processutils.ProcessExecutionError('', '', exit_code=139),
('vg name size', ''),
)
self.assertEqual(
[{'name': 'name', 'vg': 'vg', 'size': 'size'}],
self.vg.get_lv_info('sudo', vg_name='vg', lv_name='name')
)
self.assertEqual(2, exec_mock.call_count)
args = ['env', 'LC_ALL=C', 'lvs', '--noheadings', '--unit=g', '-o',
'vg_name,name,size', '--nosuffix', '--readonly', 'vg/name']
if self.configuration.lvm_suppress_fd_warnings:
args.insert(2, 'LVM_SUPPRESS_FD_WARNINGS=1')
lvs_call = mock.call(*args, root_helper='sudo', run_as_root=True)
exec_mock.assert_has_calls([lvs_call, lvs_call])
def test_get_all_physical_volumes(self): def test_get_all_physical_volumes(self):
# Filtered VG version # Filtered VG version
pvs = self.vg.get_all_physical_volumes('sudo', 'fake-vg') pvs = self.vg.get_all_physical_volumes('sudo', 'fake-vg')

View File

@ -1056,6 +1056,33 @@ class TestRetryDecorator(test.TestCase):
self.assertRaises(WrongException, raise_unexpected_error) self.assertRaises(WrongException, raise_unexpected_error)
self.assertFalse(mock_sleep.called) self.assertFalse(mock_sleep.called)
@mock.patch('tenacity.nap.sleep')
def test_retry_exit_code(self, sleep_mock):
exit_code = 5
exception = utils.processutils.ProcessExecutionError
@utils.retry(retry=utils.retry_if_exit_code, retry_param=exit_code)
def raise_retriable_exit_code():
raise exception(exit_code=exit_code)
self.assertRaises(exception, raise_retriable_exit_code)
self.assertEqual(2, sleep_mock.call_count)
sleep_mock.assert_has_calls([mock.call(1), mock.call(2)])
@mock.patch('tenacity.nap.sleep')
def test_retry_exit_code_non_retriable(self, sleep_mock):
exit_code = 5
exception = utils.processutils.ProcessExecutionError
@utils.retry(retry=utils.retry_if_exit_code, retry_param=exit_code)
def raise_non_retriable_exit_code():
raise exception(exit_code=exit_code + 1)
self.assertRaises(exception, raise_non_retriable_exit_code)
sleep_mock.assert_not_called()
@ddt.ddt @ddt.ddt
class TestCalculateVirtualFree(test.TestCase): class TestCalculateVirtualFree(test.TestCase):

View File

@ -575,8 +575,19 @@ class ComparableMixin(object):
return self._compare(other, lambda s, o: s != o) return self._compare(other, lambda s, o: s != o)
def retry(exceptions, interval=1, retries=3, backoff_rate=2, class retry_if_exit_code(tenacity.retry_if_exception):
wait_random=False): """Retry on ProcessExecutionError specific exit codes."""
def __init__(self, codes):
self.codes = (codes,) if isinstance(codes, int) else codes
super(retry_if_exit_code, self).__init__(self._check_exit_code)
def _check_exit_code(self, exc):
return (exc and isinstance(exc, processutils.ProcessExecutionError) and
exc.exit_code in self.codes)
def retry(retry_param, interval=1, retries=3, backoff_rate=2,
wait_random=False, retry=tenacity.retry_if_exception_type):
if retries < 1: if retries < 1:
raise ValueError('Retries must be greater than or ' raise ValueError('Retries must be greater than or '
@ -598,7 +609,7 @@ def retry(exceptions, interval=1, retries=3, backoff_rate=2,
after=tenacity.after_log(LOG, logging.DEBUG), after=tenacity.after_log(LOG, logging.DEBUG),
stop=tenacity.stop_after_attempt(retries), stop=tenacity.stop_after_attempt(retries),
reraise=True, reraise=True,
retry=tenacity.retry_if_exception_type(exceptions), retry=retry(retry_param),
wait=wait) wait=wait)
return r.call(f, *args, **kwargs) return r.call(f, *args, **kwargs)

View File

@ -232,8 +232,8 @@ class HttpClient(object):
raise exception.VolumeBackendAPIException(message=msg) raise exception.VolumeBackendAPIException(message=msg)
return rest_response return rest_response
@utils.retry(exceptions=(requests.ConnectionError, @utils.retry(retry_param=(requests.ConnectionError,
DellDriverRetryableException)) DellDriverRetryableException))
def get(self, url): def get(self, url):
LOG.debug('get: %(url)s', {'url': url}) LOG.debug('get: %(url)s', {'url': url})
rest_response = self.session.get(self.__formatUrl(url), rest_response = self.session.get(self.__formatUrl(url),
@ -247,7 +247,7 @@ class HttpClient(object):
raise DellDriverRetryableException() raise DellDriverRetryableException()
return rest_response return rest_response
@utils.retry(exceptions=(requests.ConnectionError,)) @utils.retry(retry_param=(requests.ConnectionError,))
def post(self, url, payload, async_call=False): def post(self, url, payload, async_call=False):
LOG.debug('post: %(url)s data: %(payload)s', LOG.debug('post: %(url)s data: %(payload)s',
{'url': url, {'url': url,
@ -261,7 +261,7 @@ class HttpClient(object):
self.asynctimeout if async_call else self.synctimeout)), self.asynctimeout if async_call else self.synctimeout)),
async_call) async_call)
@utils.retry(exceptions=(requests.ConnectionError,)) @utils.retry(retry_param=(requests.ConnectionError,))
def put(self, url, payload, async_call=False): def put(self, url, payload, async_call=False):
LOG.debug('put: %(url)s data: %(payload)s', LOG.debug('put: %(url)s data: %(payload)s',
{'url': url, {'url': url,
@ -275,7 +275,7 @@ class HttpClient(object):
self.asynctimeout if async_call else self.synctimeout)), self.asynctimeout if async_call else self.synctimeout)),
async_call) async_call)
@utils.retry(exceptions=(requests.ConnectionError,)) @utils.retry(retry_param=(requests.ConnectionError,))
def delete(self, url, payload=None, async_call=False): def delete(self, url, payload=None, async_call=False):
LOG.debug('delete: %(url)s data: %(payload)s', LOG.debug('delete: %(url)s data: %(payload)s',
{'url': url, 'payload': payload}) {'url': url, 'payload': payload})

View File

@ -200,7 +200,7 @@ class Client(object):
def modify_lun(self): def modify_lun(self):
pass pass
@cinder_utils.retry(exceptions=const.VNXTargetNotReadyError, @cinder_utils.retry(retry_param=const.VNXTargetNotReadyError,
interval=15, interval=15,
retries=5, backoff_rate=1) retries=5, backoff_rate=1)
def migrate_lun(self, src_id, dst_id, def migrate_lun(self, src_id, dst_id,

View File

@ -704,7 +704,7 @@ class AS13000Driver(san.SanISCSIDriver):
request_type=request_type) request_type=request_type)
@volume_utils.trace @volume_utils.trace
@utils.retry(exceptions=exception.VolumeDriverException, @utils.retry(retry_param=exception.VolumeDriverException,
interval=1, interval=1,
retries=3) retries=3)
def _add_lun_to_target(self, target_name, volume): def _add_lun_to_target(self, target_name, volume):

View File

@ -0,0 +1,6 @@
---
fixes:
- |
LVM driver `bug #1901783
<https://bugs.launchpad.net/cinder/+bug/1901783>`_: Fix unexpected delete
volume failure due to unexpected exit code 139 on ``lvs`` command call.