Merge "NetApp cDOT driver fails Tempest cleanup on clone workflows"
This commit is contained in:
commit
9f2fd82304
|
@ -17,6 +17,7 @@
|
|||
|
||||
import copy
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import strutils
|
||||
|
@ -901,7 +902,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
raise
|
||||
|
||||
@na_utils.trace
|
||||
def unmount_volume(self, volume_name, force=False):
|
||||
def _unmount_volume(self, volume_name, force=False):
|
||||
"""Unmounts a volume."""
|
||||
api_args = {
|
||||
'volume-name': volume_name,
|
||||
|
@ -914,6 +915,44 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||
return
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
def unmount_volume(self, volume_name, force=False, wait_seconds=30):
|
||||
"""Unmounts a volume, retrying if a clone split is ongoing.
|
||||
|
||||
NOTE(cknight): While unlikely to happen in normal operation, any client
|
||||
that tries to delete volumes immediately after creating volume clones
|
||||
is likely to experience failures if cDOT isn't quite ready for the
|
||||
delete. The volume unmount is the first operation in the delete
|
||||
path that fails in this case, and there is no proactive check we can
|
||||
use to reliably predict the failure. And there isn't a specific error
|
||||
code from volume-unmount, so we have to check for a generic error code
|
||||
plus certain language in the error code. It's ugly, but it works, and
|
||||
it's better than hard-coding a fixed delay.
|
||||
"""
|
||||
|
||||
# Do the unmount, handling split-related errors with retries.
|
||||
retry_interval = 3 # seconds
|
||||
for retry in range(wait_seconds / retry_interval):
|
||||
try:
|
||||
self._unmount_volume(volume_name, force=force)
|
||||
LOG.debug('Volume %s unmounted.', volume_name)
|
||||
return
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code == netapp_api.EAPIERROR and 'job ID' in e.message:
|
||||
msg = _LW('Could not unmount volume %(volume)s due to '
|
||||
'ongoing volume operation: %(exception)s')
|
||||
msg_args = {'volume': volume_name, 'exception': e}
|
||||
LOG.warning(msg, msg_args)
|
||||
time.sleep(retry_interval)
|
||||
continue
|
||||
raise
|
||||
|
||||
msg = _('Failed to unmount volume %(volume)s after '
|
||||
'waiting for %(wait_seconds)s seconds.')
|
||||
msg_args = {'volume': volume_name, 'wait_seconds': wait_seconds}
|
||||
LOG.error(msg, msg_args)
|
||||
raise exception.NetAppException(msg % msg_args)
|
||||
|
||||
@na_utils.trace
|
||||
def delete_volume(self, volume_name):
|
||||
"""Deletes a volume."""
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import copy
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
|
@ -1615,11 +1616,11 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
self.client.offline_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
def test_unmount_volume(self):
|
||||
def test__unmount_volume(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
||||
self.client.unmount_volume(fake.SHARE_NAME)
|
||||
self.client._unmount_volume(fake.SHARE_NAME)
|
||||
|
||||
volume_unmount_args = {
|
||||
'volume-name': fake.SHARE_NAME,
|
||||
|
@ -1629,42 +1630,100 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-unmount', volume_unmount_args)])
|
||||
|
||||
def test_unmount_volume_force(self):
|
||||
def test__unmount_volume_force(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
||||
self.client.unmount_volume(fake.SHARE_NAME, force=True)
|
||||
self.client._unmount_volume(fake.SHARE_NAME, force=True)
|
||||
|
||||
volume_unmount_args = {'volume-name': fake.SHARE_NAME, 'force': 'true'}
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-unmount', volume_unmount_args)])
|
||||
|
||||
def test_unmount_volume_already_unmounted(self):
|
||||
def test__unmount_volume_already_unmounted(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(side_effect=self._mock_api_error(
|
||||
netapp_api.EVOL_NOT_MOUNTED)))
|
||||
|
||||
self.client.unmount_volume(fake.SHARE_NAME, force=True)
|
||||
self.client._unmount_volume(fake.SHARE_NAME, force=True)
|
||||
|
||||
volume_unmount_args = {'volume-name': fake.SHARE_NAME, 'force': 'true'}
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-unmount', volume_unmount_args)])
|
||||
|
||||
def test_unmount_volume_api_error(self):
|
||||
def test__unmount_volume_api_error(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(side_effect=self._mock_api_error()))
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.client.unmount_volume,
|
||||
self.client._unmount_volume,
|
||||
fake.SHARE_NAME,
|
||||
force=True)
|
||||
|
||||
def test_unmount_volume(self):
|
||||
|
||||
self.mock_object(self.client, '_unmount_volume')
|
||||
|
||||
self.client.unmount_volume(fake.SHARE_NAME)
|
||||
|
||||
self.client._unmount_volume.assert_called_once_with(fake.SHARE_NAME,
|
||||
force=False)
|
||||
self.assertEqual(1, client_cmode.LOG.debug.call_count)
|
||||
self.assertEqual(0, client_cmode.LOG.warning.call_count)
|
||||
|
||||
def test_unmount_volume_api_error(self):
|
||||
|
||||
self.mock_object(self.client,
|
||||
'_unmount_volume',
|
||||
self._mock_api_error())
|
||||
|
||||
self.assertRaises(netapp_api.NaApiError,
|
||||
self.client.unmount_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
self.assertEqual(1, self.client._unmount_volume.call_count)
|
||||
self.assertEqual(0, client_cmode.LOG.debug.call_count)
|
||||
self.assertEqual(0, client_cmode.LOG.warning.call_count)
|
||||
|
||||
def test_unmount_volume_with_retries(self):
|
||||
|
||||
side_effect = [netapp_api.NaApiError(code=netapp_api.EAPIERROR,
|
||||
message='...job ID...')] * 5
|
||||
side_effect.append(None)
|
||||
self.mock_object(self.client,
|
||||
'_unmount_volume',
|
||||
mock.Mock(side_effect=side_effect))
|
||||
self.mock_object(time, 'sleep')
|
||||
|
||||
self.client.unmount_volume(fake.SHARE_NAME)
|
||||
|
||||
self.assertEqual(6, self.client._unmount_volume.call_count)
|
||||
self.assertEqual(1, client_cmode.LOG.debug.call_count)
|
||||
self.assertEqual(5, client_cmode.LOG.warning.call_count)
|
||||
|
||||
def test_unmount_volume_with_max_retries(self):
|
||||
|
||||
side_effect = [netapp_api.NaApiError(code=netapp_api.EAPIERROR,
|
||||
message='...job ID...')] * 30
|
||||
self.mock_object(self.client,
|
||||
'_unmount_volume',
|
||||
mock.Mock(side_effect=side_effect))
|
||||
self.mock_object(time, 'sleep')
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.unmount_volume,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
self.assertEqual(10, self.client._unmount_volume.call_count)
|
||||
self.assertEqual(0, client_cmode.LOG.debug.call_count)
|
||||
self.assertEqual(10, client_cmode.LOG.warning.call_count)
|
||||
|
||||
def test_delete_volume(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
|
Loading…
Reference in New Issue