Merge "NetApp cDOT driver fails Tempest cleanup on clone workflows"

This commit is contained in:
Jenkins 2015-05-12 18:44:37 +00:00 committed by Gerrit Code Review
commit 9f2fd82304
2 changed files with 107 additions and 9 deletions

View File

@ -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."""

View File

@ -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')