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:
Kiran Pawar 2023-07-19 14:05:59 +00:00
parent 48f1c1c83e
commit 54c4f91aa1
8 changed files with 147 additions and 26 deletions

View File

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

View File

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

View File

@ -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(

View File

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

View File

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

View File

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

View File

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

View File

@ -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>`_