NetApp: Stop clone split while deleting child share.
If share created from snapshot is deleted immediately after creation and if clone split operation is in progress, then delete call fails. Fix this issue by first stopping the clone split job and then continue with deletion. Closes-bug: #1960239 Change-Id: If9844b3da70cec427c6260ee239c8c6131ed77ed
This commit is contained in:
parent
48f1c1c83e
commit
54c4f91aa1
@ -41,11 +41,13 @@ EVOLOPNOTSUPP = '160'
|
||||
EAPIERROR = '13001'
|
||||
EAPINOTFOUND = '13005'
|
||||
ESNAPSHOTNOTALLOWED = '13023'
|
||||
EVOLUMEDOESNOTEXIST = '13040'
|
||||
EVOLUMEOFFLINE = '13042'
|
||||
EINTERNALERROR = '13114'
|
||||
EINVALIDINPUTERROR = '13115'
|
||||
EDUPLICATEENTRY = '13130'
|
||||
EVOLNOTCLONE = '13170'
|
||||
EVOLOPNOTUNDERWAY = '13171'
|
||||
EVOLMOVE_CANNOT_MOVE_TO_CFO = '13633'
|
||||
EAGGRDOESNOTEXIST = '14420'
|
||||
EVOL_NOT_MOUNTED = '14716'
|
||||
|
@ -3104,14 +3104,14 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
self.send_request('volume-clone-create', api_args)
|
||||
|
||||
if split:
|
||||
self.split_volume_clone(volume_name)
|
||||
self.volume_clone_split_start(volume_name)
|
||||
|
||||
if adaptive_qos_policy_group is not None:
|
||||
self.set_qos_adaptive_policy_group_for_volume(
|
||||
volume_name, adaptive_qos_policy_group)
|
||||
|
||||
@na_utils.trace
|
||||
def split_volume_clone(self, volume_name):
|
||||
def volume_clone_split_start(self, volume_name):
|
||||
"""Begins splitting a clone from its parent."""
|
||||
try:
|
||||
api_args = {'volume': volume_name}
|
||||
@ -3121,6 +3121,41 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
return
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
def volume_clone_split_status(self, volume_name):
|
||||
"""Status of splitting a clone from its parent."""
|
||||
try:
|
||||
api_args = {'volume': volume_name}
|
||||
result = self.send_request('volume-clone-split-status', api_args)
|
||||
except netapp_api.NaApiError:
|
||||
# any exception in status is considered either clone split is
|
||||
# completed or not triggred on this volume
|
||||
return 100
|
||||
|
||||
clone_split_details = result.get_child_by_name(
|
||||
'clone-split-details') or netapp_api.NaElement('none')
|
||||
for clone_split_details_info in clone_split_details.get_children():
|
||||
percentage = clone_split_details_info.get_child_content(
|
||||
'block-percentage-complete')
|
||||
try:
|
||||
return int(percentage)
|
||||
except Exception:
|
||||
return 100
|
||||
return 100
|
||||
|
||||
@na_utils.trace
|
||||
def volume_clone_split_stop(self, volume_name):
|
||||
"""Stop splitting a clone from its parent."""
|
||||
try:
|
||||
api_args = {'volume': volume_name}
|
||||
self.send_request('volume-clone-split-stop', api_args)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code in (netapp_api.EVOLUMEDOESNOTEXIST,
|
||||
netapp_api.EVOLNOTCLONE,
|
||||
netapp_api.EVOLOPNOTUNDERWAY):
|
||||
return
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
def check_volume_clone_split_completed(self, volume_name):
|
||||
"""Check if volume clone split operation already finished"""
|
||||
|
@ -1938,7 +1938,7 @@ class NetAppRestClient(object):
|
||||
for volume in result.get('records', [])]
|
||||
|
||||
@na_utils.trace
|
||||
def split_volume_clone(self, volume_name):
|
||||
def volume_clone_split_start(self, volume_name):
|
||||
"""Begins splitting a clone from its parent."""
|
||||
|
||||
volume = self._get_volume_by_args(vol_name=volume_name)
|
||||
@ -1949,6 +1949,45 @@ class NetAppRestClient(object):
|
||||
self.send_request(f'/storage/volumes/{uuid}', 'patch',
|
||||
body=body, wait_on_accepted=False)
|
||||
|
||||
@na_utils.trace
|
||||
def volume_clone_split_status(self, volume_name):
|
||||
"""Status of splitting a clone from its parent."""
|
||||
|
||||
query = {
|
||||
'name': volume_name,
|
||||
'fields': 'clone.split_complete_percent'
|
||||
}
|
||||
|
||||
try:
|
||||
res = self.send_request('/storage/volumes/', 'get', query=query)
|
||||
percent = res.get('clone.split_complete_percent')
|
||||
if not percent:
|
||||
return 100
|
||||
return percent
|
||||
except netapp_api.NaApiError:
|
||||
msg = ("Failed to get clone split status for volume %s ")
|
||||
LOG.warning(msg, volume_name)
|
||||
return 100
|
||||
|
||||
@na_utils.trace
|
||||
def volume_clone_split_stop(self, volume_name):
|
||||
"""Stops splitting a clone from its parent."""
|
||||
|
||||
volume = self._get_volume_by_args(vol_name=volume_name)
|
||||
uuid = volume['uuid']
|
||||
body = {
|
||||
'clone.split_initiated': 'false',
|
||||
}
|
||||
try:
|
||||
self.send_request(f'/storage/volumes/{uuid}', 'patch',
|
||||
body=body, wait_on_accepted=False)
|
||||
except netapp_api.NaApiError as e:
|
||||
if e.code in (netapp_api.EVOLUMEDOESNOTEXIST,
|
||||
netapp_api.EVOLNOTCLONE,
|
||||
netapp_api.EVOLOPNOTUNDERWAY):
|
||||
return
|
||||
raise
|
||||
|
||||
@na_utils.trace
|
||||
def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False):
|
||||
"""Deletes a volume snapshot."""
|
||||
@ -3150,7 +3189,7 @@ class NetAppRestClient(object):
|
||||
self.send_request(f'/storage/volumes/{uuid}', 'patch', body=body)
|
||||
|
||||
if split:
|
||||
self.split_volume_clone(volume_name)
|
||||
self.volume_clone_split_start(volume_name)
|
||||
|
||||
if adaptive_qos_policy_group is not None:
|
||||
self.set_qos_adaptive_policy_group_for_volume(
|
||||
|
@ -1591,7 +1591,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
|
||||
# split at the end: not be blocked by a busy volume
|
||||
if split is not None:
|
||||
vserver_client.split_volume_clone(share_name)
|
||||
vserver_client.volume_clone_split_start(share_name)
|
||||
|
||||
@na_utils.trace
|
||||
def _share_exists(self, share_name, vserver_client):
|
||||
@ -1605,6 +1605,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
self._delete_fpolicy_for_share(share, vserver, vserver_client)
|
||||
|
||||
if self._share_exists(share_name, vserver_client):
|
||||
if vserver_client.volume_clone_split_status(share_name) != 100:
|
||||
vserver_client.volume_clone_split_stop(share_name)
|
||||
if remove_export:
|
||||
self._remove_export(share, vserver_client)
|
||||
self._deallocate_container(share_name, vserver_client)
|
||||
@ -1838,7 +1840,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
snapshot_children = vserver_client.get_clone_children_for_snapshot(
|
||||
share_name, snapshot_name)
|
||||
for snapshot_child in snapshot_children:
|
||||
vserver_client.split_volume_clone(snapshot_child['name'])
|
||||
vserver_client.volume_clone_split_start(snapshot_child['name'])
|
||||
|
||||
if is_flexgroup:
|
||||
# NOTE(felipe_rodrigues): ONTAP does not allow rename a
|
||||
|
@ -4551,7 +4551,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
adaptive_qos_policy_group_name):
|
||||
self.client.features.add_feature('ADAPTIVE_QOS')
|
||||
self.mock_object(self.client, 'send_request')
|
||||
self.mock_object(self.client, 'split_volume_clone')
|
||||
self.mock_object(self.client, 'volume_clone_split_start')
|
||||
set_qos_adapt_mock = self.mock_object(
|
||||
self.client,
|
||||
'set_qos_adaptive_policy_group_for_volume')
|
||||
@ -4579,13 +4579,13 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
)
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-clone-create', volume_clone_create_args)])
|
||||
self.assertFalse(self.client.split_volume_clone.called)
|
||||
self.assertFalse(self.client.volume_clone_split_start.called)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_create_volume_clone_split(self, split):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
self.mock_object(self.client, 'split_volume_clone')
|
||||
self.mock_object(self.client, 'volume_clone_split_start')
|
||||
|
||||
self.client.create_volume_clone(fake.SHARE_NAME,
|
||||
fake.PARENT_SHARE_NAME,
|
||||
@ -4602,35 +4602,51 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-clone-create', volume_clone_create_args)])
|
||||
if split:
|
||||
self.client.split_volume_clone.assert_called_once_with(
|
||||
self.client.volume_clone_split_start.assert_called_once_with(
|
||||
fake.SHARE_NAME)
|
||||
else:
|
||||
self.assertFalse(self.client.split_volume_clone.called)
|
||||
self.assertFalse(self.client.volume_clone_split_start.called)
|
||||
|
||||
@ddt.data(None,
|
||||
mock.Mock(side_effect=netapp_api.NaApiError(
|
||||
code=netapp_api.EVOL_CLONE_BEING_SPLIT)))
|
||||
def test_split_volume_clone(self, side_effect):
|
||||
def test_volume_clone_split_start(self, side_effect):
|
||||
|
||||
self.mock_object(
|
||||
self.client, 'send_request',
|
||||
mock.Mock(side_effect=side_effect))
|
||||
|
||||
self.client.split_volume_clone(fake.SHARE_NAME)
|
||||
self.client.volume_clone_split_start(fake.SHARE_NAME)
|
||||
|
||||
volume_clone_split_args = {'volume': fake.SHARE_NAME}
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-clone-split-start', volume_clone_split_args)])
|
||||
|
||||
def test_split_volume_clone_api_error(self):
|
||||
@ddt.data(None,
|
||||
mock.Mock(side_effect=netapp_api.NaApiError(
|
||||
code=netapp_api.EVOLOPNOTUNDERWAY)))
|
||||
def test_volume_clone_split_stop(self, side_effect):
|
||||
|
||||
self.mock_object(
|
||||
self.client, 'send_request',
|
||||
mock.Mock(side_effect=side_effect))
|
||||
|
||||
self.client.volume_clone_split_stop(fake.SHARE_NAME)
|
||||
|
||||
volume_clone_split_args = {'volume': fake.SHARE_NAME}
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-clone-split-stop', volume_clone_split_args)])
|
||||
|
||||
def test_volume_clone_split_start_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.split_volume_clone,
|
||||
self.client.volume_clone_split_start,
|
||||
fake.SHARE_NAME)
|
||||
|
||||
def test_get_clone_children_for_snapshot(self):
|
||||
|
@ -1961,7 +1961,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
mock_get_records.assert_called_once_with(
|
||||
'/storage/volumes', query=query)
|
||||
|
||||
def test_split_volume_clone(self):
|
||||
def test_volume_clone_split_start(self):
|
||||
fake_resp_vol = fake.REST_SIMPLE_RESPONSE["records"][0]
|
||||
fake_uuid = fake_resp_vol['uuid']
|
||||
mock_get_unique_volume = self.mock_object(
|
||||
@ -1972,7 +1972,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.VOLUME_LIST_SIMPLE_RESPONSE_REST))
|
||||
|
||||
self.client.split_volume_clone(fake.VOLUME_NAMES[0])
|
||||
self.client.volume_clone_split_start(fake.VOLUME_NAMES[0])
|
||||
mock_get_unique_volume.assert_called_once()
|
||||
body = {
|
||||
'clone.split_initiated': 'true',
|
||||
@ -1981,6 +1981,26 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
f'/storage/volumes/{fake_uuid}', 'patch', body=body,
|
||||
wait_on_accepted=False)
|
||||
|
||||
def test_volume_clone_split_stop(self):
|
||||
fake_resp_vol = fake.REST_SIMPLE_RESPONSE["records"][0]
|
||||
fake_uuid = fake_resp_vol['uuid']
|
||||
mock_get_unique_volume = self.mock_object(
|
||||
self.client, "_get_volume_by_args",
|
||||
mock.Mock(return_value=fake_resp_vol)
|
||||
)
|
||||
mock_send_request = self.mock_object(
|
||||
self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.VOLUME_LIST_SIMPLE_RESPONSE_REST))
|
||||
|
||||
self.client.volume_clone_split_stop(fake.VOLUME_NAMES[0])
|
||||
mock_get_unique_volume.assert_called_once()
|
||||
body = {
|
||||
'clone.split_initiated': 'false',
|
||||
}
|
||||
mock_send_request.assert_called_once_with(
|
||||
f'/storage/volumes/{fake_uuid}', 'patch', body=body,
|
||||
wait_on_accepted=False)
|
||||
|
||||
def test_rename_snapshot(self):
|
||||
volume = fake.VOLUME_ITEM_SIMPLE_RESPONSE_REST
|
||||
mock_get_volume = self.mock_object(
|
||||
@ -3177,7 +3197,7 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
self.mock_object(self.client,
|
||||
'_get_volume_by_args',
|
||||
mock.Mock(return_value=volume))
|
||||
self.mock_object(self.client, 'split_volume_clone')
|
||||
self.mock_object(self.client, 'volume_clone_split_start')
|
||||
self.mock_object(
|
||||
self.client.connection, 'get_vserver',
|
||||
mock.Mock(return_value='fake_svm'))
|
||||
@ -3217,13 +3237,12 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
else:
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'/storage/volumes', 'post', body=body)
|
||||
|
||||
self.assertFalse(self.client.split_volume_clone.called)
|
||||
self.assertFalse(self.client.volume_clone_split_start.called)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_create_volume_split(self, split):
|
||||
self.mock_object(self.client, 'send_request')
|
||||
self.mock_object(self.client, 'split_volume_clone')
|
||||
self.mock_object(self.client, 'volume_clone_split_start')
|
||||
self.mock_object(
|
||||
self.client.connection, 'get_vserver',
|
||||
mock.Mock(return_value='fake_svm'))
|
||||
@ -3243,10 +3262,10 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
|
||||
split=split)
|
||||
|
||||
if split:
|
||||
self.client.split_volume_clone.assert_called_once_with(
|
||||
self.client.volume_clone_split_start.assert_called_once_with(
|
||||
fake.SHARE_NAME)
|
||||
else:
|
||||
self.assertFalse(self.client.split_volume_clone.called)
|
||||
self.assertFalse(self.client.volume_clone_split_start.called)
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'/storage/volumes', 'post', body=body)
|
||||
|
@ -2559,7 +2559,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
vserver_client.delete_snapshot.assert_called_once_with(
|
||||
fake.SHARE_NAME, fake.SNAPSHOT_NAME)
|
||||
self.assertFalse(vserver_client.get_clone_children_for_snapshot.called)
|
||||
self.assertFalse(vserver_client.split_volume_clone.called)
|
||||
self.assertFalse(vserver_client.volume_clone_split_start.called)
|
||||
self.assertFalse(vserver_client.soft_delete_snapshot.called)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@ -2581,7 +2581,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertFalse(vserver_client.delete_snapshot.called)
|
||||
vserver_client.get_clone_children_for_snapshot.assert_called_once_with(
|
||||
fake.SHARE_NAME, fake.SNAPSHOT_NAME)
|
||||
vserver_client.split_volume_clone.assert_has_calls([
|
||||
vserver_client.volume_clone_split_start.assert_has_calls([
|
||||
mock.call(fake.CDOT_CLONE_CHILD_1),
|
||||
mock.call(fake.CDOT_CLONE_CHILD_2),
|
||||
])
|
||||
@ -2610,7 +2610,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertFalse(vserver_client.delete_snapshot.called)
|
||||
self.assertFalse(vserver_client.get_clone_children_for_snapshot.called)
|
||||
self.assertFalse(vserver_client.split_volume_clone.called)
|
||||
self.assertFalse(vserver_client.volume_clone_split_start.called)
|
||||
self.assertFalse(mock_is_flexgroup_share.called)
|
||||
self.assertFalse(vserver_client.soft_delete_snapshot.called)
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
On NetApp ONTAP backend, if share created from snapshot is deleted while
|
||||
clone split job is in progress, the deletion fails due to unmount error.
|
||||
Fixed this issue by stopping clone split if its in progress and then move
|
||||
to unmount of share. For more details, please check
|
||||
`Launchpad Bug #1960239 <https://bugs.launchpad.net/manila/+bug/1960239>`_
|
Loading…
Reference in New Issue
Block a user