Consistency groups in NetApp cDOT drivers

With the consistency groups feature in Manila core, we can support
that in the NetApp cDOT drivers.  Note that cDOT does not have a
persistent CG concept, so some of the driver methods don't have much
to do; the backend merely takes the synchronized snapshots across
multiple shares as needed.

DocImpact
Implements bp: consistency-groups-in-netapp-cdot-drivers

Depends-On: Id6e56426b212435366a36122dbeddc52910d9828
Change-Id: I27b707ea8ce34d899d300c9a2b207738effe069a
This commit is contained in:
Clinton Knight 2015-08-07 10:17:18 -04:00 committed by Ben Swartzlander
parent 333474047c
commit 58c82eda2f
8 changed files with 729 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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("""
</results>
""")
PASSED_RESPONSE = etree.XML("""
<results status="passed" />
""")
VSERVER_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
@ -1170,6 +1176,18 @@ SNAPSHOT_GET_ITER_OTHER_ERROR_RESPONSE = etree.XML("""
</results>
""" % {'volume': SHARE_NAME, 'vserver': VSERVER_NAME})
SNAPSHOT_MULTIDELETE_ERROR_RESPONSE = etree.XML("""
<results status="passed">
<volume-errors>
<volume-error>
<errno>13021</errno>
<name>%(volume)s</name>
<reason>No such snapshot.</reason>
</volume-error>
</volume-errors>
</results>
""" % {'volume': SHARE_NAME})
NFS_EXPORT_RULES = ('10.10.10.10', '10.10.10.20')
NFS_EXPORTFS_LIST_RULES_2_NO_RULES_RESPONSE = etree.XML("""

View File

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

View File

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

View File

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