diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py
index 9d1e5c1504..3b55cb56c1 100644
--- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py
+++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py
@@ -1498,6 +1498,32 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
api_args = {'volume': volume_name, 'snapshot': snapshot_name}
self.send_request('snapshot-delete', api_args)
+ @na_utils.trace
+ def create_cg_snapshot(self, volume_names, snapshot_name):
+ """Creates a consistency group snapshot of one or more flexvols."""
+ cg_id = self._start_cg_snapshot(volume_names, snapshot_name)
+ if not cg_id:
+ msg = _('Could not start consistency group snapshot %s.')
+ raise exception.NetAppException(msg % snapshot_name)
+ self._commit_cg_snapshot(cg_id)
+
+ @na_utils.trace
+ def _start_cg_snapshot(self, volume_names, snapshot_name):
+ api_args = {
+ 'snapshot': snapshot_name,
+ 'timeout': 'relaxed',
+ 'volumes': [
+ {'volume-name': volume_name} for volume_name in volume_names
+ ],
+ }
+ result = self.send_request('cg-start', api_args)
+ return result.get_child_content('cg-id')
+
+ @na_utils.trace
+ def _commit_cg_snapshot(self, cg_id):
+ api_args = {'cg-id': cg_id}
+ self.send_request('cg-commit', api_args)
+
@na_utils.trace
def create_cifs_share(self, share_name):
share_path = '/%s' % share_name
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
index bce85b2c6e..0ce4dd0fd6 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py
@@ -67,6 +67,25 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def shrink_share(self, share, new_size, **kwargs):
self.library.shrink_share(share, new_size, **kwargs)
+ def create_consistency_group(self, context, cg_dict, **kwargs):
+ return self.library.create_consistency_group(context, cg_dict,
+ **kwargs)
+
+ def create_consistency_group_from_cgsnapshot(self, context, cg_dict,
+ cgsnapshot_dict, **kwargs):
+ return self.library.create_consistency_group_from_cgsnapshot(
+ context, cg_dict, cgsnapshot_dict, **kwargs)
+
+ def delete_consistency_group(self, context, cg_dict, **kwargs):
+ return self.library.delete_consistency_group(context, cg_dict,
+ **kwargs)
+
+ def create_cgsnapshot(self, context, snap_dict, **kwargs):
+ return self.library.create_cgsnapshot(context, snap_dict, **kwargs)
+
+ def delete_cgsnapshot(self, context, snap_dict, **kwargs):
+ return self.library.delete_cgsnapshot(context, snap_dict, **kwargs)
+
def ensure_share(self, context, share, **kwargs):
pass
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
index c9531e0c97..adc507a01f 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py
@@ -67,6 +67,25 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def shrink_share(self, share, new_size, **kwargs):
self.library.shrink_share(share, new_size, **kwargs)
+ def create_consistency_group(self, context, cg_dict, **kwargs):
+ return self.library.create_consistency_group(context, cg_dict,
+ **kwargs)
+
+ def create_consistency_group_from_cgsnapshot(self, context, cg_dict,
+ cgsnapshot_dict, **kwargs):
+ return self.library.create_consistency_group_from_cgsnapshot(
+ context, cg_dict, cgsnapshot_dict, **kwargs)
+
+ def delete_consistency_group(self, context, cg_dict, **kwargs):
+ return self.library.delete_consistency_group(context, cg_dict,
+ **kwargs)
+
+ def create_cgsnapshot(self, context, snap_dict, **kwargs):
+ return self.library.create_cgsnapshot(context, snap_dict, **kwargs)
+
+ def delete_cgsnapshot(self, context, snap_dict, **kwargs):
+ return self.library.delete_cgsnapshot(context, snap_dict, **kwargs)
+
def ensure_share(self, context, share, **kwargs):
pass
diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
index cdb70f4aeb..3628a64c91 100644
--- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
+++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py
@@ -171,17 +171,19 @@ class NetAppCmodeFileStorageLibrary(object):
housekeeping_periodic_task.start(
interval=self.HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0)
- @na_utils.trace
def _get_valid_share_name(self, share_id):
"""Get share name according to share name template."""
return self.configuration.netapp_volume_name_template % {
'share_id': share_id.replace('-', '_')}
- @na_utils.trace
def _get_valid_snapshot_name(self, snapshot_id):
"""Get snapshot name according to snapshot name template."""
return 'share_snapshot_' + snapshot_id.replace('-', '_')
+ def _get_valid_cg_snapshot_name(self, snapshot_id):
+ """Get snapshot name according to snapshot name template."""
+ return 'share_cg_snapshot_' + snapshot_id.replace('-', '_')
+
@na_utils.trace
def _get_aggregate_space(self):
aggregates = self._find_matching_aggregates()
@@ -203,6 +205,7 @@ class NetAppCmodeFileStorageLibrary(object):
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': 0.0,
'free_capacity_gb': 0.0,
+ 'consistency_group_support': 'host',
'pools': self._get_pools(),
}
return data
@@ -503,12 +506,13 @@ class NetAppCmodeFileStorageLibrary(object):
raise exception.NetAppException(msg % msg_args)
@na_utils.trace
- def _allocate_container_from_snapshot(self, share, snapshot,
- vserver_client):
+ def _allocate_container_from_snapshot(
+ self, share, snapshot, vserver_client,
+ snapshot_name_func=_get_valid_snapshot_name):
"""Clones existing share."""
share_name = self._get_valid_share_name(share['id'])
parent_share_name = self._get_valid_share_name(snapshot['share_id'])
- parent_snapshot_name = self._get_valid_snapshot_name(snapshot['id'])
+ parent_snapshot_name = snapshot_name_func(self, snapshot['id'])
LOG.debug('Creating share from snapshot %s', snapshot['id'])
vserver_client.create_volume_clone(share_name, parent_share_name,
@@ -747,6 +751,148 @@ class NetAppCmodeFileStorageLibrary(object):
msg_args = {'volume': volume['name']}
raise exception.ManageInvalidShare(reason=msg % msg_args)
+ @na_utils.trace
+ def create_consistency_group(self, context, cg_dict, share_server=None):
+ """Creates a consistency group.
+
+ cDOT has no persistent CG object, so apart from validating the
+ share_server info is passed correctly, this method has nothing to do.
+ """
+ vserver, vserver_client = self._get_vserver(share_server=share_server)
+
+ @na_utils.trace
+ def create_consistency_group_from_cgsnapshot(
+ self, context, cg_dict, cgsnapshot_dict, share_server=None):
+ """Creates a consistency group from an existing CG snapshot."""
+ vserver, vserver_client = self._get_vserver(share_server=share_server)
+
+ # Ensure there is something to do
+ if not cgsnapshot_dict['cgsnapshot_members']:
+ return None, None
+
+ clone_list = self._collate_cg_snapshot_info(cg_dict, cgsnapshot_dict)
+ share_update_list = []
+
+ LOG.debug('Creating consistency group from CG snapshot %s.',
+ cgsnapshot_dict['id'])
+
+ for clone in clone_list:
+
+ self._allocate_container_from_snapshot(
+ clone['share'], clone['snapshot'], vserver_client,
+ NetAppCmodeFileStorageLibrary._get_valid_cg_snapshot_name)
+
+ export_locations = self._create_export(clone['share'],
+ vserver,
+ vserver_client)
+ share_update_list.append({
+ 'id': clone['share']['id'],
+ 'export_locations': export_locations,
+ })
+
+ return None, share_update_list
+
+ def _collate_cg_snapshot_info(self, cg_dict, cgsnapshot_dict):
+ """Collate the data for a clone of a CG snapshot.
+
+ Given two data structures, a CG snapshot (cgsnapshot_dict) and a new
+ CG to be cloned from the snapshot (cg_dict), match up both structures
+ into a list of dicts (share & snapshot) suitable for use by existing
+ driver methods that clone individual share snapshots.
+ """
+
+ clone_list = list()
+
+ for share in cg_dict['shares']:
+
+ clone_info = {'share': share}
+
+ for cgsnapshot_member in cgsnapshot_dict['cgsnapshot_members']:
+ if (share['source_cgsnapshot_member_id'] ==
+ cgsnapshot_member['id']):
+ clone_info['snapshot'] = {
+ 'share_id': cgsnapshot_member['share_id'],
+ 'id': cgsnapshot_member['cgsnapshot_id']
+ }
+ break
+
+ else:
+ msg = _("Invalid data supplied for creating consistency group "
+ "from CG snapshot %s.") % cgsnapshot_dict['id']
+ raise exception.InvalidConsistencyGroup(reason=msg)
+
+ clone_list.append(clone_info)
+
+ return clone_list
+
+ @na_utils.trace
+ def delete_consistency_group(self, context, cg_dict, share_server=None):
+ """Deletes a consistency group.
+
+ cDOT has no persistent CG object, so apart from validating the
+ share_server info is passed correctly, this method has nothing to do.
+ """
+ try:
+ vserver, vserver_client = self._get_vserver(
+ share_server=share_server)
+ except (exception.InvalidInput,
+ exception.VserverNotSpecified,
+ exception.VserverNotFound) as error:
+ LOG.warning(_LW("Could not determine share server for consistency "
+ "group being deleted: %(cg)s. Deletion of CG "
+ "record will proceed anyway. Error: %(error)s"),
+ {'cg': cg_dict['id'], 'error': error})
+
+ @na_utils.trace
+ def create_cgsnapshot(self, context, snap_dict, share_server=None):
+ """Creates a consistency group snapshot."""
+ vserver, vserver_client = self._get_vserver(share_server=share_server)
+
+ share_names = [self._get_valid_share_name(member['share_id'])
+ for member in snap_dict.get('cgsnapshot_members', [])]
+ snapshot_name = self._get_valid_cg_snapshot_name(snap_dict['id'])
+
+ if share_names:
+ LOG.debug('Creating CG snapshot %s.', snapshot_name)
+ vserver_client.create_cg_snapshot(share_names, snapshot_name)
+
+ return None, None
+
+ @na_utils.trace
+ def delete_cgsnapshot(self, context, snap_dict, share_server=None):
+ """Deletes a consistency group snapshot."""
+ try:
+ vserver, vserver_client = self._get_vserver(
+ share_server=share_server)
+ except (exception.InvalidInput,
+ exception.VserverNotSpecified,
+ exception.VserverNotFound) as error:
+ LOG.warning(_LW("Could not determine share server for CG snapshot "
+ "being deleted: %(snap)s. Deletion of CG snapshot "
+ "record will proceed anyway. Error: %(error)s"),
+ {'snap': snap_dict['id'], 'error': error})
+ return None, None
+
+ share_names = [self._get_valid_share_name(member['share_id'])
+ for member in snap_dict.get('cgsnapshot_members', [])]
+ snapshot_name = self._get_valid_cg_snapshot_name(snap_dict['id'])
+
+ for share_name in share_names:
+ try:
+ self._handle_busy_snapshot(vserver_client, share_name,
+ snapshot_name)
+ except exception.SnapshotNotFound:
+ LOG.info(_LI("Snapshot %(snap)s does not exist for share "
+ "%(share)s."),
+ {'snap': snapshot_name, 'share': share_name})
+ continue
+
+ LOG.debug("Deleting snapshot %(snap)s for share %(share)s.",
+ {'snap': snapshot_name, 'share': share_name})
+ vserver_client.delete_snapshot(share_name, snapshot_name)
+
+ return None, None
+
@na_utils.trace
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share."""
diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
index e336fff269..6c02d98c8d 100644
--- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
+++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py
@@ -36,7 +36,9 @@ SHARE_AGGREGATE_RAID_TYPES = ('raid4', 'raid_dp')
SHARE_AGGREGATE_DISK_TYPE = 'FCAL'
SHARE_NAME = 'fake_share'
SHARE_SIZE = '1000000000'
+SHARE_NAME_2 = 'fake_share_2'
SNAPSHOT_NAME = 'fake_snapshot'
+CG_SNAPSHOT_ID = 'fake_cg_id'
PARENT_SHARE_NAME = 'fake_parent_share'
PARENT_SNAPSHOT_NAME = 'fake_parent_snapshot'
MAX_FILES = 5000
@@ -84,6 +86,10 @@ NO_RECORDS_RESPONSE = etree.XML("""
""")
+PASSED_RESPONSE = etree.XML("""
+
+""")
+
VSERVER_GET_ITER_RESPONSE = etree.XML("""
@@ -1170,6 +1176,18 @@ SNAPSHOT_GET_ITER_OTHER_ERROR_RESPONSE = etree.XML("""
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
+SNAPSHOT_MULTIDELETE_ERROR_RESPONSE = etree.XML("""
+
+
+
+ 13021
+ %(volume)s
+ No such snapshot.
+
+
+
+""" % {'volume': SHARE_NAME})
+
NFS_EXPORT_RULES = ('10.10.10.10', '10.10.10.20')
NFS_EXPORTFS_LIST_RULES_2_NO_RULES_RESPONSE = etree.XML("""
diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
index e6f7956d50..baddee80a8 100644
--- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
+++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py
@@ -2631,6 +2631,67 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_has_calls([
mock.call('snapshot-delete', snapshot_delete_args)])
+ def test_create_cg_snapshot(self):
+
+ mock_start_cg_snapshot = self.mock_object(
+ self.client, '_start_cg_snapshot',
+ mock.Mock(return_value=fake.CG_SNAPSHOT_ID))
+ mock_commit_cg_snapshot = self.mock_object(
+ self.client, '_commit_cg_snapshot')
+
+ self.client.create_cg_snapshot([fake.SHARE_NAME, fake.SHARE_NAME_2],
+ fake.SNAPSHOT_NAME)
+
+ mock_start_cg_snapshot.assert_called_once_with(
+ [fake.SHARE_NAME, fake.SHARE_NAME_2], fake.SNAPSHOT_NAME)
+ mock_commit_cg_snapshot.assert_called_once_with(fake.CG_SNAPSHOT_ID)
+
+ def test_create_cg_snapshot_no_id(self):
+
+ mock_start_cg_snapshot = self.mock_object(
+ self.client, '_start_cg_snapshot', mock.Mock(return_value=None))
+ mock_commit_cg_snapshot = self.mock_object(
+ self.client, '_commit_cg_snapshot')
+
+ self.assertRaises(exception.NetAppException,
+ self.client.create_cg_snapshot,
+ [fake.SHARE_NAME, fake.SHARE_NAME_2],
+ fake.SNAPSHOT_NAME)
+
+ mock_start_cg_snapshot.assert_called_once_with(
+ [fake.SHARE_NAME, fake.SHARE_NAME_2], fake.SNAPSHOT_NAME)
+ self.assertFalse(mock_commit_cg_snapshot.called)
+
+ def test_start_cg_snapshot(self):
+
+ self.mock_object(self.client, 'send_request')
+
+ self.client._start_cg_snapshot([fake.SHARE_NAME, fake.SHARE_NAME_2],
+ fake.SNAPSHOT_NAME)
+
+ cg_start_args = {
+ 'snapshot': fake.SNAPSHOT_NAME,
+ 'timeout': 'relaxed',
+ 'volumes': [
+ {'volume-name': fake.SHARE_NAME},
+ {'volume-name': fake.SHARE_NAME_2},
+ ],
+ }
+
+ self.client.send_request.assert_has_calls([
+ mock.call('cg-start', cg_start_args)])
+
+ def test_commit_cg_snapshot(self):
+
+ self.mock_object(self.client, 'send_request')
+
+ self.client._commit_cg_snapshot(fake.CG_SNAPSHOT_ID)
+
+ cg_commit_args = {'cg-id': fake.CG_SNAPSHOT_ID}
+
+ self.client.send_request.assert_has_calls([
+ mock.call('cg-commit', cg_commit_args)])
+
def test_create_cifs_share(self):
self.mock_object(self.client, 'send_request')
diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
index 7942136d64..8823c848b6 100644
--- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
+++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_base.py
@@ -301,6 +301,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'storage_protocol': 'NFS_CIFS',
'total_capacity_gb': 0.0,
'free_capacity_gb': 0.0,
+ 'consistency_group_support': 'host',
'pools': fake.POOLS,
}
self.assertDictEqual(expected, result)
@@ -1334,6 +1335,324 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.FLEXVOL_TO_MANAGE,
vserver_client)
+ def test_create_consistency_group(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+
+ result = self.library.create_consistency_group(
+ self.context, fake.EMPTY_CONSISTENCY_GROUP,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertIsNone(result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ @ddt.data(exception.InvalidInput(reason='fake_reason'),
+ exception.VserverNotSpecified(),
+ exception.VserverNotFound(vserver='fake_vserver'))
+ def test_create_consistency_group_no_share_server(self,
+ get_vserver_exception):
+
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(side_effect=get_vserver_exception))
+
+ self.assertRaises(type(get_vserver_exception),
+ self.library.create_consistency_group,
+ self.context,
+ fake.EMPTY_CONSISTENCY_GROUP,
+ share_server=fake.SHARE_SERVER)
+
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_create_consistency_group_from_cgsnapshot(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+ mock_allocate_container_from_snapshot = self.mock_object(
+ self.library, '_allocate_container_from_snapshot')
+ mock_create_export = self.mock_object(
+ self.library, '_create_export',
+ mock.Mock(side_effect=[['loc3'], ['loc4']]))
+
+ result = self.library.create_consistency_group_from_cgsnapshot(
+ self.context,
+ fake.CONSISTENCY_GROUP_DEST,
+ fake.CG_SNAPSHOT,
+ share_server=fake.SHARE_SERVER)
+
+ share_update_list = [
+ {'id': fake.SHARE_ID3, 'export_locations': ['loc3']},
+ {'id': fake.SHARE_ID4, 'export_locations': ['loc4']}
+ ]
+ expected = (None, share_update_list)
+ self.assertEqual(expected, result)
+
+ mock_allocate_container_from_snapshot.assert_has_calls([
+ mock.call(fake.COLLATED_CGSNAPSHOT_INFO[0]['share'],
+ fake.COLLATED_CGSNAPSHOT_INFO[0]['snapshot'],
+ vserver_client,
+ mock.ANY),
+ mock.call(fake.COLLATED_CGSNAPSHOT_INFO[1]['share'],
+ fake.COLLATED_CGSNAPSHOT_INFO[1]['snapshot'],
+ vserver_client,
+ mock.ANY),
+ ])
+ mock_create_export.assert_has_calls([
+ mock.call(fake.COLLATED_CGSNAPSHOT_INFO[0]['share'],
+ fake.VSERVER1,
+ vserver_client),
+ mock.call(fake.COLLATED_CGSNAPSHOT_INFO[1]['share'],
+ fake.VSERVER1,
+ vserver_client),
+ ])
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_create_consistency_group_from_cgsnapshot_no_members(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+ mock_allocate_container_from_snapshot = self.mock_object(
+ self.library, '_allocate_container_from_snapshot')
+ mock_create_export = self.mock_object(
+ self.library, '_create_export',
+ mock.Mock(side_effect=[['loc3'], ['loc4']]))
+
+ fake_cg_snapshot = copy.deepcopy(fake.CG_SNAPSHOT)
+ fake_cg_snapshot['cgsnapshot_members'] = []
+
+ result = self.library.create_consistency_group_from_cgsnapshot(
+ self.context,
+ fake.CONSISTENCY_GROUP_DEST,
+ fake_cg_snapshot,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertEqual((None, None), result)
+
+ self.assertFalse(mock_allocate_container_from_snapshot.called)
+ self.assertFalse(mock_create_export.called)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_collate_cg_snapshot_info(self):
+
+ result = self.library._collate_cg_snapshot_info(
+ fake.CONSISTENCY_GROUP_DEST, fake.CG_SNAPSHOT)
+
+ self.assertEqual(fake.COLLATED_CGSNAPSHOT_INFO, result)
+
+ def test_collate_cg_snapshot_info_invalid(self):
+
+ fake_cg_snapshot = copy.deepcopy(fake.CG_SNAPSHOT)
+ fake_cg_snapshot['cgsnapshot_members'] = []
+
+ self.assertRaises(exception.InvalidConsistencyGroup,
+ self.library._collate_cg_snapshot_info,
+ fake.CONSISTENCY_GROUP_DEST, fake_cg_snapshot)
+
+ def test_delete_consistency_group(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+
+ result = self.library.delete_consistency_group(
+ self.context,
+ fake.EMPTY_CONSISTENCY_GROUP,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertIsNone(result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ @ddt.data(exception.InvalidInput(reason='fake_reason'),
+ exception.VserverNotSpecified(),
+ exception.VserverNotFound(vserver='fake_vserver'))
+ def test_delete_consistency_group_no_share_server(self,
+ get_vserver_exception):
+
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(side_effect=get_vserver_exception))
+
+ result = self.library.delete_consistency_group(
+ self.context,
+ fake.EMPTY_CONSISTENCY_GROUP,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertIsNone(result)
+ self.assertEqual(1, lib_base.LOG.warning.call_count)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_create_cgsnapshot(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+
+ result = self.library.create_cgsnapshot(
+ self.context,
+ fake.CG_SNAPSHOT,
+ share_server=fake.SHARE_SERVER)
+
+ share_names = [
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_1['share_id']),
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_2['share_id'])
+ ]
+ snapshot_name = self.library._get_valid_cg_snapshot_name(
+ fake.CG_SNAPSHOT['id'])
+ vserver_client.create_cg_snapshot.assert_called_once_with(
+ share_names, snapshot_name)
+ self.assertEqual((None, None), result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_create_cgsnapshot_no_members(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+
+ fake_cg_snapshot = copy.deepcopy(fake.CG_SNAPSHOT)
+ fake_cg_snapshot['cgsnapshot_members'] = []
+
+ result = self.library.create_cgsnapshot(
+ self.context,
+ fake_cg_snapshot,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertFalse(vserver_client.create_cg_snapshot.called)
+ self.assertEqual((None, None), result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_delete_cgsnapshot(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+ mock_handle_busy_snapshot = self.mock_object(self.library,
+ '_handle_busy_snapshot')
+
+ result = self.library.delete_cgsnapshot(
+ self.context,
+ fake.CG_SNAPSHOT,
+ share_server=fake.SHARE_SERVER)
+
+ share_names = [
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_1['share_id']),
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_2['share_id'])
+ ]
+ snapshot_name = self.library._get_valid_cg_snapshot_name(
+ fake.CG_SNAPSHOT['id'])
+
+ mock_handle_busy_snapshot.assert_has_calls([
+ mock.call(vserver_client, share_names[0], snapshot_name),
+ mock.call(vserver_client, share_names[1], snapshot_name)
+ ])
+ vserver_client.delete_snapshot.assert_has_calls([
+ mock.call(share_names[0], snapshot_name),
+ mock.call(share_names[1], snapshot_name)
+ ])
+ self.assertEqual((None, None), result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_delete_cgsnapshot_no_members(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+ mock_handle_busy_snapshot = self.mock_object(self.library,
+ '_handle_busy_snapshot')
+
+ fake_cg_snapshot = copy.deepcopy(fake.CG_SNAPSHOT)
+ fake_cg_snapshot['cgsnapshot_members'] = []
+
+ result = self.library.delete_cgsnapshot(
+ self.context,
+ fake_cg_snapshot,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertFalse(mock_handle_busy_snapshot.called)
+ self.assertFalse(vserver_client.delete_snapshot.called)
+ self.assertEqual((None, None), result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ def test_delete_cgsnapshot_snapshots_not_found(self):
+
+ vserver_client = mock.Mock()
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
+ mock_handle_busy_snapshot = self.mock_object(
+ self.library,
+ '_handle_busy_snapshot',
+ mock.Mock(side_effect=exception.SnapshotNotFound(name='fake')))
+
+ result = self.library.delete_cgsnapshot(
+ self.context,
+ fake.CG_SNAPSHOT,
+ share_server=fake.SHARE_SERVER)
+
+ share_names = [
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_1['share_id']),
+ self.library._get_valid_share_name(
+ fake.CG_SNAPSHOT_MEMBER_2['share_id'])
+ ]
+ snapshot_name = self.library._get_valid_cg_snapshot_name(
+ fake.CG_SNAPSHOT['id'])
+
+ mock_handle_busy_snapshot.assert_has_calls([
+ mock.call(vserver_client, share_names[0], snapshot_name),
+ mock.call(vserver_client, share_names[1], snapshot_name)
+ ])
+ self.assertFalse(vserver_client.delete_snapshot.called)
+ self.assertEqual((None, None), result)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
+ @ddt.data(exception.InvalidInput(reason='fake_reason'),
+ exception.VserverNotSpecified(),
+ exception.VserverNotFound(vserver='fake_vserver'))
+ def test_delete_cgsnapshot_no_share_server(self,
+ get_vserver_exception):
+
+ mock_get_vserver = self.mock_object(
+ self.library, '_get_vserver',
+ mock.Mock(side_effect=get_vserver_exception))
+
+ result = self.library.delete_cgsnapshot(
+ self.context,
+ fake.EMPTY_CONSISTENCY_GROUP,
+ share_server=fake.SHARE_SERVER)
+
+ self.assertEqual((None, None), result)
+ self.assertEqual(1, lib_base.LOG.warning.call_count)
+ mock_get_vserver.assert_called_once_with(
+ share_server=fake.SHARE_SERVER)
+
def test_extend_share(self):
vserver_client = mock.Mock()
diff --git a/manila/tests/share/drivers/netapp/dataontap/fakes.py b/manila/tests/share/drivers/netapp/dataontap/fakes.py
index 19c4877a36..368c23c2ee 100644
--- a/manila/tests/share/drivers/netapp/dataontap/fakes.py
+++ b/manila/tests/share/drivers/netapp/dataontap/fakes.py
@@ -35,11 +35,20 @@ FLEXVOL_NAME = 'fake_volume'
JUNCTION_PATH = '/%s' % FLEXVOL_NAME
EXPORT_LOCATION = '%s:%s' % (HOST_NAME, JUNCTION_PATH)
SNAPSHOT_NAME = 'fake_snapshot'
+CONSISTENCY_GROUP_NAME = 'fake_consistency_group'
SHARE_SIZE = 10
TENANT_ID = '24cb2448-13d8-4f41-afd9-eff5c4fd2a57'
SHARE_ID = '7cf7c200-d3af-4e05-b87e-9167c95dfcad'
+SHARE_ID2 = 'b51c5a31-aa5b-4254-9ee8-7d39fa4c8c38'
+SHARE_ID3 = '1379991d-037b-4897-bf3a-81b4aac72eff'
+SHARE_ID4 = '1cb41aad-fd9b-4964-8059-646f69de925e'
PARENT_SHARE_ID = '585c3935-2aa9-437c-8bad-5abae1076555'
SNAPSHOT_ID = 'de4c9050-e2f9-4ce1-ade4-5ed0c9f26451'
+CONSISTENCY_GROUP_ID = '65bfa2c9-dc6c-4513-951a-b8d15b453ad8'
+CONSISTENCY_GROUP_ID2 = '35f5c1ea-45fb-40c4-98ae-2a2a17554159'
+CG_SNAPSHOT_ID = '6ddd8a6b-5df7-417b-a2ae-3f6e449f4eea'
+CG_SNAPSHOT_MEMBER_ID1 = '629f79ef-b27e-4596-9737-30f084e5ba29'
+CG_SNAPSHOT_MEMBER_ID2 = 'e876aa9c-a322-4391-bd88-9266178262be'
FREE_CAPACITY = 10000000000
TOTAL_CAPACITY = 20000000000
AGGREGATES = ('manila_aggr_1', 'manila_aggr_2')
@@ -224,6 +233,113 @@ CDOT_SNAPSHOT_BUSY_VOLUME_CLONE = {
'owners': {'volume clone'},
}
+SHARE_FOR_CG1 = {
+ 'id': SHARE_ID,
+ 'host': '%(host)s@%(backend)s#%(pool)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME},
+ 'name': 'share1',
+ 'share_proto': 'NFS',
+ 'source_cgsnapshot_member_id': None,
+}
+
+SHARE_FOR_CG2 = {
+ 'id': SHARE_ID2,
+ 'host': '%(host)s@%(backend)s#%(pool)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME},
+ 'name': 'share2',
+ 'share_proto': 'NFS',
+ 'source_cgsnapshot_member_id': None,
+}
+
+# Clone dest of SHARE_FOR_CG1
+SHARE_FOR_CG3 = {
+ 'id': SHARE_ID3,
+ 'host': '%(host)s@%(backend)s#%(pool)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME},
+ 'name': 'share3',
+ 'share_proto': 'NFS',
+ 'source_cgsnapshot_member_id': CG_SNAPSHOT_MEMBER_ID1,
+}
+
+# Clone dest of SHARE_FOR_CG2
+SHARE_FOR_CG4 = {
+ 'id': SHARE_ID4,
+ 'host': '%(host)s@%(backend)s#%(pool)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME},
+ 'name': 'share4',
+ 'share_proto': 'NFS',
+ 'source_cgsnapshot_member_id': CG_SNAPSHOT_MEMBER_ID2,
+}
+
+EMPTY_CONSISTENCY_GROUP = {
+ 'cgsnapshots': [],
+ 'description': 'fake description',
+ 'host': '%(host)s@%(backend)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME},
+ 'id': CONSISTENCY_GROUP_ID,
+ 'name': CONSISTENCY_GROUP_NAME,
+ 'shares': [],
+}
+
+CONSISTENCY_GROUP = {
+ 'cgsnapshots': [],
+ 'description': 'fake description',
+ 'host': '%(host)s@%(backend)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME},
+ 'id': CONSISTENCY_GROUP_ID,
+ 'name': CONSISTENCY_GROUP_NAME,
+ 'shares': [SHARE_FOR_CG1, SHARE_FOR_CG2],
+}
+
+CONSISTENCY_GROUP_DEST = {
+ 'cgsnapshots': [],
+ 'description': 'fake description',
+ 'host': '%(host)s@%(backend)s' % {
+ 'host': HOST_NAME, 'backend': BACKEND_NAME},
+ 'id': CONSISTENCY_GROUP_ID,
+ 'name': CONSISTENCY_GROUP_NAME,
+ 'shares': [SHARE_FOR_CG3, SHARE_FOR_CG4],
+}
+
+CG_SNAPSHOT_MEMBER_1 = {
+ 'cgsnapshot_id': CG_SNAPSHOT_ID,
+ 'id': CG_SNAPSHOT_MEMBER_ID1,
+ 'share_id': SHARE_ID,
+ 'share_proto': 'NFS',
+}
+
+CG_SNAPSHOT_MEMBER_2 = {
+ 'cgsnapshot_id': CG_SNAPSHOT_ID,
+ 'id': CG_SNAPSHOT_MEMBER_ID2,
+ 'share_id': SHARE_ID2,
+ 'share_proto': 'NFS',
+}
+
+CG_SNAPSHOT = {
+ 'cgsnapshot_members': [CG_SNAPSHOT_MEMBER_1, CG_SNAPSHOT_MEMBER_2],
+ 'consistency_group': CONSISTENCY_GROUP,
+ 'consistency_group_id': CONSISTENCY_GROUP_ID,
+ 'id': CG_SNAPSHOT_ID,
+ 'project_id': TENANT_ID,
+}
+
+COLLATED_CGSNAPSHOT_INFO = [
+ {
+ 'share': SHARE_FOR_CG3,
+ 'snapshot': {
+ 'share_id': SHARE_ID,
+ 'id': CG_SNAPSHOT_ID
+ }
+ },
+ {
+ 'share': SHARE_FOR_CG4,
+ 'snapshot': {
+ 'share_id': SHARE_ID2,
+ 'id': CG_SNAPSHOT_ID
+ }
+ },
+]
+
LIF_NAMES = []
LIF_ADDRESSES = ['10.10.10.10', '10.10.10.20']
LIFS = (