Merge "Fix backup creation errors with NetApp driver"

This commit is contained in:
Zuul 2024-06-05 15:14:17 +00:00 committed by Gerrit Code Review
commit d773353502
9 changed files with 225 additions and 48 deletions

View File

@ -2315,7 +2315,9 @@ class NetAppRestClient(object):
fields = ['state', 'source.svm.name', 'source.path',
'destination.svm.name', 'destination.path',
'transfer.end_time', 'uuid', 'policy.type',
'transfer_schedule.name', 'transfer.state']
'transfer_schedule.name', 'transfer.state',
'last_transfer_type', 'transfer.bytes_transferred',
'healthy']
query = {}
query['fields'] = ','.join(fields)
@ -2365,7 +2367,14 @@ class NetAppRestClient(object):
(self._parse_timestamp(record['transfer']['end_time']) if
record.get('transfer', {}).get('end_time') else 0),
'uuid': record['uuid'],
'policy-type': record.get('policy', {}).get('type')
'policy-type': record.get('policy', {}).get('type'),
'is-healthy': (
'true'
if record.get('healthy', {}) is True else 'false'),
'last-transfer-type': record.get('last_transfer_type', None),
'last-transfer-size': record.get('transfer',
{}).get('bytes_transferred'),
})
return snapmirrors

View File

@ -73,20 +73,20 @@ def get_backend_configuration(backend_name):
return config
def get_backup_configuration(backup_name):
def get_backup_configuration(backup_type):
config_stanzas = CONF.list_all_sections()
if backup_name not in config_stanzas:
msg = _("Could not find backend stanza %(backup_name)s in "
if backup_type not in config_stanzas:
msg = _("Could not find backup_type stanza %(backup_type)s in "
"configuration which is required for backup workflows "
"with the source share. Available stanzas are "
"%(stanzas)s")
params = {
"stanzas": config_stanzas,
"backend_name": backup_name,
"backup_type": backup_type,
}
raise exception.BadConfigurationException(reason=msg % params)
config = configuration.Configuration(driver.share_opts,
config_group=backup_name)
config_group=backup_type)
config.append_config_values(na_opts.netapp_backup_opts)
return config

View File

@ -64,9 +64,9 @@ class Backup(Enum):
BACKUP_TYPE = "backup_type"
BACKEND_NAME = "netapp_backup_backend_section_name"
DES_VSERVER = "netapp_backup_vserver"
DES_VOLUME = "netapp_backup_share"
DES_VOLUME = "netapp_backup_volume"
SM_LABEL = "backup"
DES_VSERVER_PREFIX = "backup_vserver"
DES_VSERVER_PREFIX = "backup"
DES_VOLUME_PREFIX = "backup_volume"
VOLUME_TYPE = "dp"
SM_POLICY = "os_backup_policy"
@ -4350,13 +4350,24 @@ class NetAppCmodeFileStorageLibrary(object):
" from command line or API.")
# check the backend is related to NetApp
backup_config = data_motion.get_backup_configuration(backup_type)
try:
backup_config = data_motion.get_backup_configuration(backup_type)
except exception.BadConfigurationException:
msg = _("Could not find backup_type '%(backup_type)s' stanza"
" in config file") % {'backup_type': backup_type}
raise exception.BadConfigurationException(reason=msg)
backend_name = backup_config.safe_get(Backup.BACKEND_NAME.value)
backend_config = data_motion.get_backend_configuration(
backend_name)
try:
backend_config = data_motion.get_backend_configuration(
backend_name
)
except exception.BadConfigurationException:
msg = _("Could not find backend '%(backend_name)s' stanza"
" in config file") % {'backend_name': backend_name}
raise exception.BadConfigurationException(reason=msg)
if (backend_config.safe_get("netapp_storage_family")
!= 'ontap_cluster'):
err_msg = _("Wrong vendor backend %s is provided, provide"
err_msg = _("Wrong vendor backend '%s' is provided, provide"
" only NetApp backend.") % backend_name
raise exception.BackupException(err_msg)
@ -4393,16 +4404,17 @@ class NetAppCmodeFileStorageLibrary(object):
# Get the destination vserver and volume for relationship
source_path = f"{src_vserver}:{src_vol}"
snapmirror_info = src_vserver_client.get_snapmirror_destinations(
source_path=source_path)
source_path=source_path
)
if len(snapmirror_info) > 1:
err_msg = _("Source path %(path)s has more than one relationships."
" To create the share backup, delete the all source"
" volume's SnapMirror relationships using 'snapmirror'"
" ONTAP CLI or System Manger.")
msg = _("Source path '%(path)s' has more than one relationships."
" To create the share backup, delete the all source"
" volume's SnapMirror relationships using 'snapmirror'"
" ONTAP CLI or System Manager.")
msg_args = {
'path': source_path
}
raise exception.NetAppException(err_msg % msg_args)
raise exception.NetAppException(msg % msg_args)
elif len(snapmirror_info) == 1:
des_vserver, des_volume = self._get_destination_vserver_and_vol(
src_vserver_client, source_path, False)
@ -4431,7 +4443,8 @@ class NetAppCmodeFileStorageLibrary(object):
if share_server:
self._delete_backup_vserver(backup, des_vserver)
msg = _("Failed to create a volume in vserver %(des_vserver)s")
msg = _("Failed to create a volume in vserver '%(des_vserver)s"
"'")
msg_args = {'des_vserver': des_vserver}
raise exception.NetAppException(msg % msg_args)
@ -4451,8 +4464,7 @@ class NetAppCmodeFileStorageLibrary(object):
Backup.SM_LABEL.value)
]
if len(snap_list_with_backup) == 1:
self.is_volume_backup_before = True
self._set_volume_has_backup_before(True)
policy_name = f"{Backup.SM_POLICY.value}_{share_instance['id']}"
try:
des_vserver_client.create_snapmirror_policy(
@ -4490,9 +4502,9 @@ class NetAppCmodeFileStorageLibrary(object):
des_volume,
share_server=share_server)
msg = _("SnapVault relationship creation or initialization"
" failed between source %(source_vserver)s:"
"%(source_volume)s and destination %(des_vserver)s:"
"%(des_volume)s for share id %(share_id)s.")
" failed between source '%(source_vserver)s:"
"%(source_volume)s' and destination '%(des_vserver)s:"
"%(des_volume)s' for share id %(share_id)s.")
msg_args = {
'source_vserver': src_vserver,
@ -4564,7 +4576,7 @@ class NetAppCmodeFileStorageLibrary(object):
}
msg = _("There is no SnapMirror relationship available for"
" source path '%(source_path)s' and destination path"
" '%(des_path)s' yet.") % msg_args
" '%(des_path)s' yet.")
LOG.warning(msg, msg_args)
return progress_status
LOG.debug("SnapMirror details %s:", snapmirror_info)
@ -4582,6 +4594,22 @@ class NetAppCmodeFileStorageLibrary(object):
{'progress_status': progress_status})
return progress_status
if snapmirror_info[0].get("is-healthy") == 'false':
self._resource_cleanup_for_backup(backup,
share_instance,
des_vserver,
des_vol,
share_server=share_server)
msg_args = {
'source_path': source_path,
'des_path': des_path,
}
msg = _("There is an issue with SnapMirror relationship with"
" source path '%(source_path)s' and destination path"
" '%(des_path)s'. Make sure destination volume is or was "
"not part of any other SnapMirror relationship.")
raise exception.NetAppException(message=msg % msg_args)
# Verify that snapshot is transferred to destination volume
snap_name = self._get_backup_snapshot_name(backup,
share_instance['id'])
@ -4589,7 +4617,7 @@ class NetAppCmodeFileStorageLibrary(object):
des_vol,
snap_name)
LOG.debug("Snapshot '%(snap_name)s' transferred successfully to"
" destination", {'snap_name': snap_name})
" destination.", {'snap_name': snap_name})
# previously if volume was part of some relationship and if we delete
# all the backup of share then last snapshot will be left on
# destination volume, and we can't delete that snapshot due to ONTAP
@ -4611,11 +4639,11 @@ class NetAppCmodeFileStorageLibrary(object):
snap_to_delete = snap_list_with_backup[1]
else:
snap_to_delete = snap_list_with_backup[0]
self.is_volume_backup_before = False
self._set_volume_has_backup_before(False)
des_vserver_client.delete_snapshot(des_vol, snap_to_delete,
True)
LOG.debug("Previous snapshot %{snap_name}s deleted"
" successfully. ", {'snap_name': snap_to_delete})
" successfully.", {'snap_name': snap_to_delete})
return progress_status
@na_utils.trace
@ -4676,7 +4704,7 @@ class NetAppCmodeFileStorageLibrary(object):
"total_progress": Backup.TOTAL_PROGRESS_ZERO.value
}
return progress_status
LOG.debug("SnapMirror relationship of type RST is deleted")
LOG.debug("SnapMirror relationship of type RST is deleted.")
snap_name = self._get_backup_snapshot_name(backup,
share_instance['id'])
snapshot_list = src_vserver_client.list_volume_snapshots(src_vol_name)
@ -4699,7 +4727,7 @@ class NetAppCmodeFileStorageLibrary(object):
share_server=share_server,
)
except exception.VserverNotFound:
LOG.warning("Vserver associated with share %s was not found.",
LOG.warning("Vserver associated with share '%s' was not found.",
share_instance['id'])
return
src_vol_name = self._get_backend_share_name(share_instance['id'])
@ -4762,7 +4790,7 @@ class NetAppCmodeFileStorageLibrary(object):
des_vserver_client.get_snapshot(des_vol, snap_name)
is_snapshot_deleted = self._is_snapshot_deleted(False)
except (SnapshotResourceNotFound, netapp_api.NaApiError):
LOG.debug("Snapshot %s deleted successfully.", snap_name)
LOG.debug("Snapshot '%s' deleted successfully.", snap_name)
if not is_snapshot_deleted:
err_msg = _("Snapshot '%(snapshot_name)s' is not deleted"
" successfully on ONTAP."
@ -4770,6 +4798,10 @@ class NetAppCmodeFileStorageLibrary(object):
LOG.exception(err_msg)
raise exception.NetAppException(err_msg)
@na_utils.trace
def _set_volume_has_backup_before(self, value):
self.is_volume_backup_before = value
@na_utils.trace
def _is_snapshot_deleted(self, is_deleted):
return is_deleted
@ -4839,14 +4871,15 @@ class NetAppCmodeFileStorageLibrary(object):
des_vserver_client)
if not des_aggr:
msg = _("Not able to find any aggregate from ONTAP"
" to create the volume")
" to create the volume. Make sure aggregates are"
" added to destination vserver")
raise exception.NetAppException(msg)
src_vol = self._get_backend_share_name(share_instance['id'])
vol_attr = src_vserver_client.get_volume(src_vol)
source_vol_size = vol_attr.get('size')
vol_size_in_gb = int(source_vol_size) / units.Gi
share_id = share_instance['id'].replace('-', '_')
des_volume = f"backup_volume_{share_id}"
des_volume = f"{Backup.DES_VOLUME_PREFIX.value}_{share_id}"
des_vserver_client.create_volume(des_aggr, des_volume,
vol_size_in_gb, volume_type='dp')
return des_volume
@ -4860,7 +4893,7 @@ class NetAppCmodeFileStorageLibrary(object):
snapmirror_info = src_vserver_client.get_snapmirror_destinations(
source_path=source_path)
if validate_relation and len(snapmirror_info) != 1:
msg = _("There are more then one relationship with the source."
msg = _("There are more then one relationship with the source"
" '%(source_path)s'." % {'source_path': source_path})
raise exception.NetAppException(msg)
if len(snapmirror_info) == 1:

View File

@ -21,6 +21,7 @@ as needed to provision shares.
"""
import re
from manila.share.drivers.netapp.dataontap.cluster_mode.lib_base import Backup
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import excutils
@ -2393,7 +2394,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
]
aggr_list = aggr_matching_list
share_server_id = share_server['id']
des_vserver = f"backup_{share_server_id}"
des_vserver = f"{Backup.DES_VSERVER_PREFIX.value}_{share_server_id}"
LOG.debug("Creating vserver %s:", des_vserver)
try:
des_cluster_api_client.create_vserver(

View File

@ -309,7 +309,7 @@ netapp_backup_opts = [
'are enabled, create separate config sections for each '
'backup type specifying the "netapp_backup_vserver", '
'"netapp_backup_backend_section_name", '
'"netapp_backup_share", and '
'"netapp_backup_volume", and '
'"netapp_snapmirror_job_timeout" as appropriate.'
' Example- netapp_enabled_backup_types = eng_backup,'
' finance_backup'),
@ -322,7 +322,7 @@ netapp_backup_opts = [
help='vserver name of backend that is use for backup the share.'
' When user provide vserver value then backup volume will '
' be created under this vserver '),
cfg.StrOpt('netapp_backup_share',
cfg.StrOpt('netapp_backup_volume',
default='',
help='Specify backup share name in case user wanted to backup '
'the share. Some case user has dedicated volume for backup'

View File

@ -3994,8 +3994,10 @@ SNAPMIRROR_GET_ITER_RESPONSE_REST = {
"name": "hourly",
},
"transfer": {
"state": "success"
}
"state": "success",
"bytes_transferred": "3352",
},
"last_transfer_type": "update",
}
],
"num_records": 1,
@ -4012,7 +4014,10 @@ REST_GET_SNAPMIRRORS_RESPONSE = [{
'uuid': FAKE_UUID,
'policy-type': 'async',
'schedule': 'hourly',
'transferring-state': 'success'
'transferring-state': 'success',
'is-healthy': 'true',
'last-transfer-size': '3352',
'last-transfer-type': 'update',
}]
FAKE_CIFS_RECORDS = {

View File

@ -2220,7 +2220,8 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
':' + fake.SM_DEST_VOLUME),
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
'destination.path,transfer.end_time,uuid,policy.type,'
'transfer_schedule.name,transfer.state'
'transfer_schedule.name,transfer.state,'
'last_transfer_type,transfer.bytes_transferred,healthy'
}
mock_send_request.assert_called_once_with('/snapmirror/relationships',
@ -2252,7 +2253,8 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
':' + fake.SM_DEST_VOLUME),
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
'destination.path,transfer.end_time,uuid,policy.type,'
'transfer_schedule.name,transfer.state'
'transfer_schedule.name,transfer.state,'
'last_transfer_type,transfer.bytes_transferred,healthy'
}
mock_send_request.assert_called_once_with('/snapmirror/relationships',
@ -2280,7 +2282,8 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
':' + fake.SM_DEST_VOLUME),
'fields': 'state,source.svm.name,source.path,destination.svm.name,'
'destination.path,transfer.end_time,uuid,policy.type,'
'transfer_schedule.name,transfer.state'
'transfer_schedule.name,transfer.state,'
'last_transfer_type,transfer.bytes_transferred,healthy'
}
mock_send_request.assert_called_once_with('/snapmirror/relationships',

View File

@ -81,7 +81,7 @@ def _get_config():
CONF.set_override("netapp_backup_vserver",
"fake_backup_share",
group=backup_config)
CONF.set_override("netapp_backup_share",
CONF.set_override("netapp_backup_volume",
"fake_share_server",
group=backup_config)
return config
@ -8023,7 +8023,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
backup_config = 'backup_config'
fake_config = configuration.Configuration(driver.share_opts,
config_group=backup_config)
CONF.set_override("netapp_backup_share", "",
CONF.set_override("netapp_backup_volume", "",
group=backup_config)
CONF.set_override("netapp_backup_vserver", "",
group=backup_config)
@ -8045,7 +8045,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
backup_config = 'backup_config'
fake_config = configuration.Configuration(driver.share_opts,
config_group=backup_config)
CONF.set_override("netapp_backup_share", "",
CONF.set_override("netapp_backup_volume", "",
group=backup_config)
CONF.set_override("netapp_backup_vserver", "",
group=backup_config)
@ -8240,7 +8240,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self._backup_mock_common_method(mock_des_vserver_client)
backup_config = 'backup_config'
CONF.set_override("netapp_backup_share", "",
CONF.set_override("netapp_backup_volume", "",
group=backup_config)
CONF.set_override("netapp_backup_vserver", "",
group=backup_config)
@ -8469,6 +8469,48 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE_BACKUP,
)
def test_create_backup_when_invalid_backup_type_negative(self):
mock_src_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock_src_client)))
self.mock_object(self.mock_dm_session,
'get_backup_configuration',
mock.Mock(
side_effect=exception.BadConfigurationException,
))
self.assertRaises(
exception.BadConfigurationException,
self.library.create_backup,
self.context,
fake.SHARE_INSTANCE,
fake.SHARE_BACKUP,
)
def test_create_backup_when_invalid_backend_negative(self):
mock_src_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock_src_client)))
self.mock_object(data_motion,
'get_backup_configuration',
mock.Mock(return_value=_get_config()))
self.mock_object(self.mock_dm_session,
'get_backend_configuration',
mock.Mock(
side_effect=exception.BadConfigurationException,
))
self.assertRaises(
exception.BadConfigurationException,
self.library.create_backup,
self.context,
fake.SHARE_INSTANCE,
fake.SHARE_BACKUP,
)
def test_create_backup_continue_with_status_inprogress(self):
vserver_client = mock.Mock()
mock_dest_client = mock.Mock()
@ -8578,6 +8620,60 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE_BACKUP,
)
def test_create_backup_continue_snapshot_left_from_old_relationship(self):
vserver_client = mock.Mock()
mock_dest_client = mock.Mock()
self._backup_mock_common_method(mock_dest_client)
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
snapmirror_info = [fake.SNAP_MIRROR_INFO]
self.mock_object(mock_dest_client,
'get_snapmirrors',
mock.Mock(return_value=snapmirror_info))
snap_list = ["snap1", "snap2"]
self.mock_object(self.library,
'_get_des_volume_backup_snapshots',
mock.Mock(return_value=snap_list))
self.library._set_volume_has_backup_before(True)
share_instance = fake.SHARE_INSTANCE
backup = fake.SHARE_BACKUP
self.library.create_backup_continue(self.context, share_instance,
backup)
self.library._set_volume_has_backup_before(False)
def test_create_backup_continue_relationship_not_healthy_negative(self):
vserver_client = mock.Mock()
mock_dest_client = mock.Mock()
self._backup_mock_common_method(mock_dest_client)
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
snapmirror_info = [
{
'source-vserver': fake.VSERVER1,
'source-volume': fake.FLEXVOL_NAME,
'destination-vserver': fake.VSERVER2,
'destination-volume': fake.FLEXVOL_NAME_1,
'relationship-status': "idle",
'last-transfer-type': "update",
'is-healthy': "false",
}
]
self.mock_object(mock_dest_client,
'get_snapmirrors',
mock.Mock(return_value=snapmirror_info))
self.assertRaises(
exception.NetAppException,
self.library.create_backup_continue,
self.context,
fake.SHARE_INSTANCE,
fake.SHARE_BACKUP,
)
def test_restore_backup_with_vserver_volume_none(self):
vserver_client = mock.Mock()
self.mock_object(self.library,
@ -8727,6 +8823,27 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE_BACKUP,
)
def test_delete_backup_snapshot_not_exist(self):
vserver_client = mock.Mock()
mock_des_client = mock.Mock()
self._backup_mock_common_method(mock_des_client)
self._backup_mock_common_method_for_negative(vserver_client,
mock_des_client)
self.mock_object(self.library,
'_get_des_volume_backup_snapshots',
mock.Mock(return_value=['fake_snapshot1',
'fake_snapshot2']))
self.mock_object(self.library,
'_is_snapshot_deleted',
mock.Mock(return_value=True))
msg = "entry doesn't exist"
self.mock_object(mock_des_client,
'delete_snapshot',
mock.Mock(side_effect=netapp_api.NaApiError(
message=msg)))
self.library.delete_backup(self.context, fake.SHARE_BACKUP,
fake.SHARE_INSTANCE)
def test__get_backup_progress_status(self):
mock_dest_client = mock.Mock()
vol_attr = {'name': 'fake_vol', 'size-used': '123454'}

View File

@ -0,0 +1,9 @@
---
fixes:
- |
NetApp driver `bug #2058027
<https://bugs.launchpad.net/manila/+bug/2058027>`_:
Fix the issue for NetApp driver for backup feature. Backup status
update used to fail when administrator misconfigured backup settings
configuration or SnapMirror relationship created during backup
creation was not in healthy state.