Dell SC: Add support for consistency groups

Added support for consistency groups. Associated tests added to
test_dellsc.py and test_dellscapi.py. create_consistency_group_from_src
is not implemented in this patch.

Minor change to iscsi driver inheritance.

Change-Id: I485329883ba35b341f1b3e79897e5fe6680de1f3
Implements: blueprint dell-add-consistency-groups
This commit is contained in:
tswanson 2015-06-29 14:21:45 -05:00 committed by Tom Swanson
parent 869aad48aa
commit 55321e371f
6 changed files with 1471 additions and 11 deletions

View File

@ -176,6 +176,21 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
u'size': u'0.0 Bytes'
}
SCRPLAYPROFILE = {u'ruleCount': 0,
u'name': u'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
u'volumeCount': 0,
u'scName': u'Storage Center 64702',
u'notes': u'Created by Dell Cinder Driver',
u'scSerialNumber': 64702,
u'userCreated': True,
u'instanceName': u'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
u'instanceId': u'64702.11',
u'enforceReplayCreationTimeout': False,
u'replayCreationTimeout': 20,
u'objectType': u'ScReplayProfile',
u'type': u'Consistent',
u'expireIncompleteReplaySets': True}
IQN = 'iqn.2002-03.com.compellent:5000D31000000001'
ISCSI_PROPERTIES = {'access_mode': 'rw',
@ -1117,3 +1132,322 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
mock_find_sc.assert_called_once_with()
mock_find_volume.assert_called_once_with(None)
self.assertEqual({'_name_id': None}, rt)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_replay_profile',
return_value=SCRPLAYPROFILE)
def test_create_consistencygroup(self,
mock_create_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
self.driver.create_consistencygroup(context, group)
mock_create_replay_profile.assert_called_once_with(group['id'])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'create_replay_profile',
return_value=None)
def test_create_consistencygroup_fail(self,
mock_create_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_consistencygroup, context, group)
mock_create_replay_profile.assert_called_once_with(group['id'])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_replay_profile')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'delete_volume')
def test_delete_consistencygroup(self,
mock_delete_volume,
mock_find_replay_profile,
mock_delete_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.db = mock.Mock()
mock_volume = mock.MagicMock()
expected_volumes = [mock_volume]
self.driver.db.volume_get_all_by_group.return_value = expected_volumes
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'status': 'deleted'}
model_update, volumes = self.driver.delete_consistencygroup(context,
group)
mock_find_replay_profile.assert_called_once_with(group['id'])
mock_delete_replay_profile.assert_called_once_with(self.SCRPLAYPROFILE)
mock_delete_volume.assert_called_once_with(mock_volume)
self.assertEqual(group['status'], model_update['status'])
self.assertEqual(expected_volumes, volumes)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_replay_profile')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
@mock.patch.object(dell_storagecenter_iscsi.DellStorageCenterISCSIDriver,
'delete_volume')
def test_delete_consistencygroup_not_found(self,
mock_delete_volume,
mock_find_replay_profile,
mock_delete_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.db = mock.Mock()
mock_volume = mock.MagicMock()
expected_volumes = [mock_volume]
self.driver.db.volume_get_all_by_group.return_value = expected_volumes
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'status': 'deleted'}
model_update, volumes = self.driver.delete_consistencygroup(context,
group)
mock_find_replay_profile.assert_called_once_with(group['id'])
self.assertFalse(mock_delete_replay_profile.called)
mock_delete_volume.assert_called_once_with(mock_volume)
self.assertEqual(group['status'], model_update['status'])
self.assertEqual(expected_volumes, volumes)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'update_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_update_consistencygroup(self,
mock_find_replay_profile,
mock_update_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
add_volumes = [{'id': '101'}]
remove_volumes = [{'id': '102'}]
rt1, rt2, rt3 = self.driver.update_consistencygroup(context,
group,
add_volumes,
remove_volumes)
mock_update_cg_volumes.assert_called_once_with(self.SCRPLAYPROFILE,
add_volumes,
remove_volumes)
mock_find_replay_profile.assert_called_once_with(group['id'])
self.assertIsNone(rt1)
self.assertIsNone(rt2)
self.assertIsNone(rt3)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
def test_update_consistencygroup_not_found(self,
mock_find_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
add_volumes = [{'id': '101'}]
remove_volumes = [{'id': '102'}]
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.update_consistencygroup,
context,
group,
add_volumes,
remove_volumes)
mock_find_replay_profile.assert_called_once_with(group['id'])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'update_cg_volumes',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_update_consistencygroup_error(self,
mock_find_replay_profile,
mock_update_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
group = {'id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
add_volumes = [{'id': '101'}]
remove_volumes = [{'id': '102'}]
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.update_consistencygroup,
context,
group,
add_volumes,
remove_volumes)
mock_find_replay_profile.assert_called_once_with(group['id'])
mock_update_cg_volumes.assert_called_once_with(self.SCRPLAYPROFILE,
add_volumes,
remove_volumes)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'snap_cg_replay',
return_value={'instanceId': '100'})
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_create_cgsnapshot(self,
mock_find_replay_profile,
mock_snap_cg_replay,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.db = mock.Mock()
mock_snapshot = mock.MagicMock()
expected_snapshots = [mock_snapshot]
self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = (
expected_snapshots)
context = {}
cggrp = {'consistencygroup_id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100'}
model_update, snapshots = self.driver.create_cgsnapshot(context, cggrp)
mock_find_replay_profile.assert_called_once_with(
cggrp['consistencygroup_id'])
mock_snap_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
cggrp['id'],
0)
self.assertEqual('available', model_update['status'])
self.assertEqual(expected_snapshots, snapshots)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
def test_create_cgsnapshot_profile_not_found(self,
mock_find_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
cggrp = {'consistencygroup_id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100'}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cgsnapshot,
context,
cggrp)
mock_find_replay_profile.assert_called_once_with(
cggrp['consistencygroup_id'])
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'snap_cg_replay',
return_value=None)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_create_cgsnapshot_fail(self,
mock_find_replay_profile,
mock_snap_cg_replay,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
cggrp = {'consistencygroup_id': 'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100'}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cgsnapshot,
context,
cggrp)
mock_find_replay_profile.assert_called_once_with(
cggrp['consistencygroup_id'])
mock_snap_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
cggrp['id'],
0)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_cg_replay',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_delete_cgsnapshot(self,
mock_find_replay_profile,
mock_delete_cg_replay,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.db = mock.Mock()
mock_snapshot = mock.MagicMock()
expected_snapshots = [mock_snapshot]
self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = (
expected_snapshots)
context = {}
cgsnap = {'consistencygroup_id':
'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100',
'status': 'deleted'}
model_update, snapshots = self.driver.delete_cgsnapshot(context,
cgsnap)
mock_find_replay_profile.assert_called_once_with(
cgsnap['consistencygroup_id'])
mock_delete_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
cgsnap['id'])
self.assertEqual({'status': cgsnap['status']}, model_update)
self.assertEqual(expected_snapshots, snapshots)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_cg_replay')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
def test_delete_cgsnapshot_profile_not_found(self,
mock_find_replay_profile,
mock_delete_cg_replay,
mock_close_connection,
mock_open_connection,
mock_init):
self.driver.db = mock.Mock()
mock_snapshot = mock.MagicMock()
expected_snapshots = [mock_snapshot]
self.driver.db.snapshot_get_all_for_cgsnapshot.return_value = (
expected_snapshots)
context = {}
cgsnap = {'consistencygroup_id':
'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100',
'status': 'deleted'}
model_update, snapshots = self.driver.delete_cgsnapshot(context,
cgsnap)
mock_find_replay_profile.assert_called_once_with(
cgsnap['consistencygroup_id'])
self.assertFalse(mock_delete_cg_replay.called)
self.assertEqual({'status': cgsnap['status']}, model_update)
self.assertEqual(expected_snapshots, snapshots)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'delete_cg_replay',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=SCRPLAYPROFILE)
def test_delete_cgsnapshot_profile_failed_delete(self,
mock_find_replay_profile,
mock_delete_cg_replay,
mock_close_connection,
mock_open_connection,
mock_init):
context = {}
cgsnap = {'consistencygroup_id':
'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
'id': '100',
'status': 'available'}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_cgsnapshot,
context,
cgsnap)
mock_find_replay_profile.assert_called_once_with(
cgsnap['consistencygroup_id'])
mock_delete_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
cgsnap['id'])

View File

@ -1391,6 +1391,20 @@ class DellSCSanAPITestCase(test.TestCase):
u'storageAlertThreshold': 10,
u'objectType': u'StorageCenterStorageUsage'}
RPLAY_PROFILE = {u'name': u'fc8f2fec-fab2-4e34-9148-c094c913b9a3',
u'type': u'Consistent',
u'notes': u'Created by Dell Cinder Driver',
u'volumeCount': 0,
u'expireIncompleteReplaySets': True,
u'replayCreationTimeout': 20,
u'enforceReplayCreationTimeout': False,
u'ruleCount': 0,
u'userCreated': True,
u'scSerialNumber': 64702,
u'scName': u'Storage Center 64702',
u'objectType': u'ScReplayProfile',
u'instanceId': u'64702.11',
u'instanceName': u'fc8f2fec-fab2-4e34-9148-c094c913b9a3'}
STORAGE_PROFILE_LIST = [
{u'allowedForFlashOptimized': False,
u'allowedForNonFlashOptimized': True,
@ -1489,12 +1503,19 @@ class DellSCSanAPITestCase(test.TestCase):
response_created.reason = u'created'
RESPONSE_201 = response_created
# Create a Response object that indicates a failure (no content)
# Create a Response object that can indicate a failure. Although
# 204 can be a success with no return. (Know your calls!)
response_nc = models.Response()
response_nc.status_code = 204
response_nc.reason = u'duplicate'
RESPONSE_204 = response_nc
# Create a Response object is a pure error.
response_bad = models.Response()
response_bad.status_code = 400
response_bad.reason = u'bad request'
RESPONSE_400 = response_bad
def setUp(self):
super(DellSCSanAPITestCase, self).setUp()
@ -3994,6 +4015,680 @@ class DellSCSanAPITestCase(test.TestCase):
self.assertFalse(mock_delete.called)
self.assertIsNone(res, 'Expected None')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[RPLAY_PROFILE])
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_200)
def test_find_replay_profile(self,
mock_post,
mock_get_json,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.find_replay_profile('guid')
self.assertTrue(mock_post.called)
self.assertTrue(mock_get_json.called)
self.assertEqual(self.RPLAY_PROFILE, res, 'Unexpected Profile')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[RPLAY_PROFILE, RPLAY_PROFILE])
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_200)
def test_find_replay_profile_more_than_one(self,
mock_post,
mock_get_json,
mock_close_connection,
mock_open_connection,
mock_init):
self.assertRaises(exception.VolumeBackendAPIException,
self.scapi.find_replay_profile,
'guid')
self.assertTrue(mock_post.called)
self.assertTrue(mock_get_json.called)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=[])
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_200)
def test_find_replay_profile_empty_list(self,
mock_post,
mock_get_json,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.find_replay_profile('guid')
self.assertTrue(mock_post.called)
self.assertTrue(mock_get_json.called)
self.assertEqual(None, res, 'Unexpected return')
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_400)
def test_find_replay_profile_error(self,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.find_replay_profile('guid')
self.assertTrue(mock_post.called)
self.assertEqual(None, res, 'Unexpected return')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_first_result',
return_value=RPLAY_PROFILE)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_201)
def test_create_replay_profile(self,
mock_post,
mock_first_result,
mock_find_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.create_replay_profile('guid')
self.assertTrue(mock_find_replay_profile.called)
self.assertTrue(mock_post.called)
self.assertTrue(mock_first_result.called)
self.assertEqual(self.RPLAY_PROFILE, res, 'Unexpected Profile')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=RPLAY_PROFILE)
def test_create_replay_profile_exists(self,
mock_find_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.create_replay_profile('guid')
self.assertTrue(mock_find_replay_profile.called)
self.assertEqual(self.RPLAY_PROFILE, res, 'Unexpected Profile')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay_profile',
return_value=None)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_400)
def test_create_replay_profile_fail(self,
mock_post,
mock_find_replay_profile,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi.create_replay_profile('guid')
self.assertTrue(mock_find_replay_profile.called)
self.assertTrue(mock_post.called)
self.assertEqual(None, res, 'Unexpected return')
@mock.patch.object(dell_storagecenter_api.HttpClient,
'delete',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_delete_replay_profile(self,
mock_get_id,
mock_delete,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
self.scapi.delete_replay_profile(profile)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_delete.called)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'delete',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_delete_replay_profile_fail(self,
mock_get_id,
mock_delete,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
self.assertRaises(exception.VolumeBackendAPIException,
self.scapi.delete_replay_profile,
profile)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_delete.called)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_first_result',
return_value=VOLUME_CONFIG)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_get_volume_configuration(self,
mock_get_id,
mock_get,
mock_first_result,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi._get_volume_configuration({})
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get.called)
self.assertEqual(self.VOLUME_CONFIG, res, 'Unexpected config')
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_get_volume_configuration_bad_response(self,
mock_get_id,
mock_get,
mock_close_connection,
mock_open_connection,
mock_init):
res = self.scapi._get_volume_configuration({})
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get.called)
self.assertEqual(None, res, 'Unexpected result')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_volume_configuration',
return_value=VOLUME_CONFIG)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'put',
return_value=RESPONSE_200)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_update_volume_profiles(self,
mock_get_id,
mock_put,
mock_get_volume_configuration,
mock_close_connection,
mock_open_connection,
mock_init):
scvolume = {'instanceId': '1'}
existingid = self.VOLUME_CONFIG[u'replayProfileList'][0][u'instanceId']
vcid = self.VOLUME_CONFIG[u'instanceId']
# First get_id is for our existing replay profile id and the second
# is for the volume config and the last is for the volume id. And
# then we do this again for the second call below.
mock_get_id.side_effect = [existingid,
vcid,
scvolume['instanceId'],
existingid,
vcid,
scvolume['instanceId']]
newid = '64702.1'
expected_payload = {'ReplayProfileList': [newid, existingid]}
expected_url = 'StorageCenter/ScVolumeConfiguration/' + vcid
res = self.scapi._update_volume_profiles(scvolume, newid, None)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_volume_configuration.called)
mock_put.assert_called_once_with(expected_url,
expected_payload)
self.assertTrue(res)
# Now do a remove. (Restarting with the original config so this will
# end up as an empty list.)
expected_payload['ReplayProfileList'] = []
res = self.scapi._update_volume_profiles(scvolume, None, existingid)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_volume_configuration.called)
mock_put.assert_called_with(expected_url,
expected_payload)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_volume_configuration',
return_value=VOLUME_CONFIG)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'put',
return_value=RESPONSE_400)
# We set this to 1 so we can check our payload
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id')
def test_update_volume_profiles_bad_response(self,
mock_get_id,
mock_put,
mock_get_volume_configuration,
mock_close_connection,
mock_open_connection,
mock_init):
scvolume = {'instanceId': '1'}
existingid = self.VOLUME_CONFIG[u'replayProfileList'][0][u'instanceId']
vcid = self.VOLUME_CONFIG[u'instanceId']
# First get_id is for our existing replay profile id and the second
# is for the volume config and the last is for the volume id. And
# then we do this again for the second call below.
mock_get_id.side_effect = [existingid,
vcid,
scvolume['instanceId'],
existingid,
vcid,
scvolume['instanceId']]
newid = '64702.1'
expected_payload = {'ReplayProfileList': [newid, existingid]}
expected_url = 'StorageCenter/ScVolumeConfiguration/' + vcid
res = self.scapi._update_volume_profiles(scvolume, newid, None)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_volume_configuration.called)
mock_put.assert_called_once_with(expected_url,
expected_payload)
self.assertFalse(res)
# Now do a remove. (Restarting with the original config so this will
# end up as an empty list.)
expected_payload['ReplayProfileList'] = []
res = self.scapi._update_volume_profiles(scvolume, None, existingid)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_volume_configuration.called)
mock_put.assert_called_with(expected_url,
expected_payload)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_volume_configuration',
return_value=None)
def test_update_volume_profiles_no_config(self,
mock_get_volume_configuration,
mock_close_connection,
mock_open_connection,
mock_init):
scvolume = {'instanceId': '1'}
res = self.scapi._update_volume_profiles(scvolume, '64702.2', None)
self.assertTrue(mock_get_volume_configuration.called)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=999)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_update_volume_profiles',
return_value=True)
def test_add_cg_volumes(self,
mock_update_volume_profiles,
mock_find_volume,
mock_close_connection,
mock_open_connection,
mock_init):
profileid = '100'
add_volumes = [{'id': '1'}]
res = self.scapi._add_cg_volumes(profileid, add_volumes)
self.assertTrue(mock_find_volume.called)
mock_update_volume_profiles.assert_called_once_with(999,
addid=profileid,
removeid=None)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=999)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_update_volume_profiles',
return_value=False)
def test_add_cg_volumes_fail(self,
mock_update_volume_profiles,
mock_find_volume,
mock_close_connection,
mock_open_connection,
mock_init):
profileid = '100'
add_volumes = [{'id': '1'}]
res = self.scapi._add_cg_volumes(profileid, add_volumes)
self.assertTrue(mock_find_volume.called)
mock_update_volume_profiles.assert_called_once_with(999,
addid=profileid,
removeid=None)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=999)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_update_volume_profiles',
return_value=True)
def test_remove_cg_volumes(self,
mock_update_volume_profiles,
mock_find_volume,
mock_close_connection,
mock_open_connection,
mock_init):
profileid = '100'
remove_volumes = [{'id': '1'}]
res = self.scapi._remove_cg_volumes(profileid, remove_volumes)
self.assertTrue(mock_find_volume.called)
mock_update_volume_profiles.assert_called_once_with(999,
addid=None,
removeid=profileid)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_volume',
return_value=999)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_update_volume_profiles',
return_value=False)
def test_remove_cg_volumes_false(self,
mock_update_volume_profiles,
mock_find_volume,
mock_close_connection,
mock_open_connection,
mock_init):
profileid = '100'
remove_volumes = [{'id': '1'}]
res = self.scapi._remove_cg_volumes(profileid, remove_volumes)
self.assertTrue(mock_find_volume.called)
mock_update_volume_profiles.assert_called_once_with(999,
addid=None,
removeid=profileid)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_remove_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_update_cg_volumes(self,
mock_get_id,
mock_add_cg_volumes,
mock_remove_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
add_volumes = [{'id': '1'}]
remove_volumes = [{'id': '2'}]
res = self.scapi.update_cg_volumes(profile,
add_volumes,
remove_volumes)
self.assertTrue(mock_get_id.called)
mock_add_cg_volumes.assert_called_once_with('100', add_volumes)
mock_remove_cg_volumes.assert_called_once_with('100',
remove_volumes)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_remove_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_update_cg_volumes_no_remove(self,
mock_get_id,
mock_add_cg_volumes,
mock_remove_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
add_volumes = [{'id': '1'}]
remove_volumes = []
res = self.scapi.update_cg_volumes(profile,
add_volumes,
remove_volumes)
self.assertTrue(mock_get_id.called)
mock_add_cg_volumes.assert_called_once_with('100', add_volumes)
self.assertFalse(mock_remove_cg_volumes.called)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_remove_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_update_cg_volumes_no_add(self,
mock_get_id,
mock_add_cg_volumes,
mock_remove_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
add_volumes = []
remove_volumes = [{'id': '1'}]
res = self.scapi.update_cg_volumes(profile,
add_volumes,
remove_volumes)
self.assertTrue(mock_get_id.called)
mock_remove_cg_volumes.assert_called_once_with('100', remove_volumes)
self.assertFalse(mock_add_cg_volumes.called)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_remove_cg_volumes')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_cg_volumes',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_update_cg_volumes_add_fail(self,
mock_get_id,
mock_add_cg_volumes,
mock_remove_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
add_volumes = [{'id': '1'}]
remove_volumes = [{'id': '2'}]
res = self.scapi.update_cg_volumes(profile,
add_volumes,
remove_volumes)
self.assertTrue(mock_get_id.called)
mock_add_cg_volumes.assert_called_once_with('100', add_volumes)
self.assertTrue(not mock_remove_cg_volumes.called)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_remove_cg_volumes',
return_value=False)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_add_cg_volumes',
return_value=True)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_update_cg_volumes_remove_fail(self,
mock_get_id,
mock_add_cg_volumes,
mock_remove_cg_volumes,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'name': 'guid'}
add_volumes = [{'id': '1'}]
remove_volumes = [{'id': '2'}]
res = self.scapi.update_cg_volumes(profile,
add_volumes,
remove_volumes)
self.assertTrue(mock_get_id.called)
mock_add_cg_volumes.assert_called_once_with('100', add_volumes)
mock_remove_cg_volumes.assert_called_once_with('100',
remove_volumes)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_204)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_snap_cg_replay(self,
mock_get_id,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
replayid = 'guid'
expire = 0
profile = {'instanceId': '100'}
# See the 100 from get_id above?
expected_url = 'StorageCenter/ScReplayProfile/100/CreateReplay'
expected_payload = {'description': replayid, 'expireTime': expire}
res = self.scapi.snap_cg_replay(profile, replayid, expire)
mock_post.assert_called_once_with(expected_url, expected_payload)
self.assertTrue(mock_get_id.called)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_snap_cg_replay_bad_return(self,
mock_get_id,
mock_post,
mock_close_connection,
mock_open_connection,
mock_init):
replayid = 'guid'
expire = 0
profile = {'instanceId': '100'}
# See the 100 from get_id above?
expected_url = 'StorageCenter/ScReplayProfile/100/CreateReplay'
expected_payload = {'description': replayid, 'expireTime': expire}
res = self.scapi.snap_cg_replay(profile, replayid, expire)
mock_post.assert_called_once_with(expected_url, expected_payload)
self.assertTrue(mock_get_id.called)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=RPLAYS)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_find_cg_replay(self,
mock_get_id,
mock_get,
mock_get_json,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'instanceId': '100'}
replayid = 'Cinder Test Replay012345678910'
res = self.scapi.find_cg_replay(profile, replayid)
expected_url = 'StorageCenter/ScReplayProfile/100/ReplayList'
mock_get.assert_called_once_with(expected_url)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_json.called)
# We should fine RPLAYS[1]
self.assertEqual(self.RPLAYS[1], res, 'Wrong replay returned')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_json',
return_value=None)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'get')
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_find_cg_replay_bad_json(self,
mock_get_id,
mock_get,
mock_get_json,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'instanceId': '100'}
replayid = 'Cinder Test Replay012345678910'
res = self.scapi.find_cg_replay(profile, replayid)
expected_url = 'StorageCenter/ScReplayProfile/100/ReplayList'
mock_get.assert_called_once_with(expected_url)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_get_json.called)
self.assertIsNone(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay',
return_value=RPLAY)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_204)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_delete_cg_replay(self,
mock_get_id,
mock_post,
mock_find_replay,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'instanceId': '100'}
replayid = 'guid'
expected_url = 'StorageCenter/ScReplay/100/Expire'
expected_payload = {}
res = self.scapi.delete_cg_replay(profile, replayid)
mock_post.assert_called_once_with(expected_url, expected_payload)
self.assertTrue(mock_find_replay.called)
self.assertTrue(mock_get_id.called)
self.assertTrue(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay',
return_value=RPLAY)
@mock.patch.object(dell_storagecenter_api.HttpClient,
'post',
return_value=RESPONSE_400)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'_get_id',
return_value='100')
def test_delete_cg_replay_error(self,
mock_get_id,
mock_post,
mock_find_replay,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'instanceId': '100'}
replayid = 'guid'
expected_url = 'StorageCenter/ScReplay/100/Expire'
expected_payload = {}
res = self.scapi.delete_cg_replay(profile, replayid)
mock_post.assert_called_once_with(expected_url, expected_payload)
self.assertTrue(mock_get_id.called)
self.assertTrue(mock_find_replay.called)
self.assertFalse(res)
@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
'find_replay',
return_value=None)
def test_delete_cg_replay_cant_find(self,
mock_find_replay,
mock_close_connection,
mock_open_connection,
mock_init):
profile = {'instanceId': '100'}
replayid = 'guid'
res = self.scapi.delete_cg_replay(profile, replayid)
self.assertTrue(mock_find_replay.called)
self.assertTrue(res)
class DellSCSanAPIConnectionTestCase(test.TestCase):

View File

@ -169,7 +169,7 @@ class StorageCenterApi(object):
Handles calls to Dell Enterprise Manager (EM) via the REST API interface.
'''
APIVERSION = '1.0.2'
APIVERSION = '1.2.0'
def __init__(self, host, port, user, password, verify):
'''This creates a connection to Dell Enterprise Manager.
@ -297,6 +297,7 @@ class StorageCenterApi(object):
payload['ApplicationVersion'] = self.APIVERSION
r = self.client.post('ApiConnection/Login',
payload)
# TODO(Swanson): If we get a 400 back we should also print the text.
if r.status_code != 200:
LOG.error(_LE('Login error: %(code)d %(reason)s'),
{'code': r.status_code,
@ -1084,6 +1085,7 @@ class StorageCenterApi(object):
:returns: Active controller ID.
'''
actvctrl = None
# TODO(Swanson): We have a function that gets this. Call that.
r = self.client.get('StorageCenter/ScVolume/%s/VolumeConfiguration'
% self._get_id(scvolume))
if r.status_code == 200:
@ -1553,3 +1555,273 @@ class StorageCenterApi(object):
'reason': r.reason})
else:
LOG.debug('_delete_server: deleteAllowed is False.')
def find_replay_profile(self, name):
'''Finds the Dell SC replay profile object name.
:param name: Name of the replay profile object. This is the
consistency group id.
:return: Dell SC replay profile or None.
:raises: VolumeBackendAPIException
'''
pf = PayloadFilter()
pf.append('ScSerialNumber', self.ssn)
pf.append('Name', name)
r = self.client.post('StorageCenter/ScReplayProfile/GetList',
pf.payload)
if r.status_code == 200:
profilelist = self._get_json(r)
if profilelist:
if len(profilelist) > 1:
LOG.error(_LE('Multiple replay profiles under name %s'),
name)
raise exception.VolumeBackendAPIException(
_('Multiple profiles found.'))
return profilelist[0]
else:
LOG.error(_LE('find_replay_profile error %s'), r)
return None
def create_replay_profile(self, name):
'''Creates a replay profile on the Dell SC.
:param name: The ID of the consistency group. This will be matched to
the name on the Dell SC.
:return: SC profile or None.
'''
profile = self.find_replay_profile(name)
if not profile:
payload = {}
payload['StorageCenter'] = self.ssn
payload['Name'] = name
payload['Type'] = 'Consistent'
payload['Notes'] = self.notes
r = self.client.post('StorageCenter/ScReplayProfile',
payload)
if r.status_code == 201:
profile = self._first_result(r)
else:
LOG.error(_LE('create_replay_profile failed %s'), r)
return profile
def delete_replay_profile(self, profile):
'''Delete the replay profile from the Dell SC.
:param profile: SC replay profile.
:return: Nothing.
:raises: VolumeBackendAPIException
'''
r = self.client.delete('StorageCenter/ScReplayProfile/%s' %
self._get_id(profile))
# 200 is a good return. Log and leave.
if r.status_code == 200:
LOG.info(_LI('Profile %s has been deleted.'),
profile.get('name'))
else:
# We failed due to a failure to delete an existing profile.
# This is reason to raise an exception.
LOG.error(_LE('Unable to delete profile %(cg)s : %(reason)s'),
{'cg': profile.get('name'),
'reason': r})
raise exception.VolumeBackendAPIException(
_('Error deleting replay profile.'))
def _get_volume_configuration(self, scvolume):
'''Get the ScVolumeConfiguration object.
:param scvolume: The Dell SC volume object.
:return: The SCVolumeConfiguration object or None.
'''
r = self.client.get('StorageCenter/ScVolume/%s/VolumeConfiguration' %
self._get_id(scvolume))
if r.status_code == 200:
LOG.debug('get_volume_configuration %s', r)
return self._first_result(r)
return None
def _update_volume_profiles(self, scvolume, addid=None, removeid=None):
'''Either Adds or removes the listed profile from the SC volume.
:param scvolume: Dell SC volume object.
:param addid: Profile ID to be added to the SC volume configuration.
:param removeid: ID to be removed to the SC volume configuration.
:return: True/False on success/failure.
'''
if scvolume:
scvolumecfg = self._get_volume_configuration(scvolume)
if scvolumecfg:
profilelist = scvolumecfg.get('replayProfileList', [])
newprofilelist = []
# Do we have one to add? Start the list with it.
if addid:
newprofilelist = [addid]
# Re-add our existing profiles.
for profile in profilelist:
profileid = self._get_id(profile)
# Make sure it isn't one we want removed and that we
# haven't already added it. (IE it isn't the addid.)
if (profileid != removeid and
newprofilelist.count(profileid) == 0):
newprofilelist.append(profileid)
# Update our volume configuration.
payload = {}
payload['ReplayProfileList'] = newprofilelist
r = self.client.put('StorageCenter/ScVolumeConfiguration/%s' %
self._get_id(scvolumecfg),
payload)
# check result
LOG.debug('_update_volume_profiles %s : %s : %s',
self._get_id(scvolume),
profilelist,
r)
if r.status_code == 200:
return True
return False
def _add_cg_volumes(self, profileid, add_volumes):
'''Trundles through add_volumes and adds the replay profile to them.
:param profileid: The ID of the replay profile.
:param add_volumes: List of Dell SC volume objects that are getting
added to the consistency group.
:return: True/False on success/failure.
'''
for vol in add_volumes:
if (self._update_volume_profiles(self.find_volume(vol['id']),
addid=profileid,
removeid=None)):
LOG.info(_LI('Added %s to cg.'), vol['id'])
else:
LOG.error(_LE('Failed to add %s to cg.'), vol['id'])
return False
return True
def _remove_cg_volumes(self, profileid, remove_volumes):
'''Removes the replay profile from the remove_volumes list of vols.
:param profileid: The ID of the replay profile.
:param remove_volumes: List of Dell SC volume objects that are getting
removed from the consistency group.
:return: True/False on success/failure.
'''
for vol in remove_volumes:
if (self._update_volume_profiles(self.find_volume(vol['id']),
addid=None,
removeid=profileid)):
LOG.info(_LI('Removed %s from cg.'), vol['id']),
else:
LOG.error(_LE('Failed to remove %s from cg.'), vol['id'])
return False
return True
def update_cg_volumes(self, profile, add_volumes=None,
remove_volumes=None):
'''Adds or removes the profile from the specified volumes
:param profile: Dell SC replay profile object.
:param add_volumes: List of volumes we are adding to the consistency
group. (Which is to say we are adding the profile
to this list of volumes.)
:param remove_volumes: List of volumes we are removing from the
consistency group. (Which is to say we are
removing the profile from this list of volumes.)
:return: True/False on success/failure.
'''
ret = True
profileid = self._get_id(profile)
if add_volumes:
LOG.info(_LI('Adding volumes to cg %s.'), profile['name'])
ret = self._add_cg_volumes(profileid, add_volumes)
if ret and remove_volumes:
LOG.info(_LI('Removing volumes from cg %s.'), profile['name'])
ret = self._remove_cg_volumes(profileid, remove_volumes)
return ret
def snap_cg_replay(self, profile, replayid, expire):
'''Snaps a replay of a consistency group.
:param profile: The name of the consistency group profile.
:param replayid: The name of the replay.
:param expire: Time in mintues before a replay expires. 0 means no
expiration.
:returns: Dell SC replay object.
'''
if profile:
payload = {}
payload['description'] = replayid
payload['expireTime'] = expire
r = self.client.post('StorageCenter/ScReplayProfile/%s/'
'CreateReplay'
% self._get_id(profile),
payload)
# 204 appears to be the correct return.
if r.status_code == 204:
LOG.debug('CreateReplay result %s', r)
return True
LOG.error(_LE('snap_cg error: %(code)d %(reason)s'),
{'code': r.status_code,
'reason': r.reason})
return False
def find_cg_replay(self, profile, replayid):
'''Searches for the replay by replayid.
replayid is stored in the replay's description attribute.
:param scvolume: Dell volume object.
:param replayid: Name to search for. This is a portion of the
snapshot ID as we do not have space for the entire
GUID in the replay description.
:returns: Dell replay object or None.
'''
r = self.client.get('StorageCenter/ScReplayProfile/%s/ReplayList'
% self._get_id(profile))
replays = self._get_json(r)
# This will be a list. If it isn't bail
if isinstance(replays, list):
for replay in replays:
# The only place to save our information with the public
# api is the description field which isn't quite long
# enough. So we check that our description is pretty much
# the max length and we compare that to the start of
# the snapshot id.
description = replay.get('description')
if (len(description) >= 30 and
replayid.startswith(description) is True and
replay.get('markedForExpiration') is not True):
# We found our replay so return it.
return replay
# If we are here then we didn't find the replay so warn and leave.
LOG.warning(_LW('Unable to find snapshot %s'),
replayid)
return None
def delete_cg_replay(self, profile, replayid):
'''Finds a Dell replay by replayid string and expires it.
Once marked for expiration we do not return the replay as a snapshot
even though it might still exist. (Backend requirements.)
:param cg_name: Consistency Group name. This is the ReplayProfileName.
:param replayid: Name to search for. This is a portion of the snapshot
ID as we do not have space for the entire GUID in the
replay description.
:returns: Boolean for success or failure.
'''
LOG.debug('Expiring consistency group replay %s', replayid)
replay = self.find_replay(profile,
replayid)
if replay is not None:
r = self.client.post('StorageCenter/ScReplay/%s/Expire'
% self._get_id(replay),
{})
if r.status_code != 204:
LOG.error(_LE('ScReplay Expire error: %(code)d %(reason)s'),
{'code': r.status_code,
'reason': r.reason})
return False
# We either couldn't find it or expired it.
return True

View File

@ -17,9 +17,10 @@ from oslo_log import log as logging
from oslo_utils import excutils
from cinder import exception
from cinder.i18n import _, _LE, _LW
from cinder.i18n import _, _LE, _LI, _LW
from cinder.volume import driver
from cinder.volume.drivers.dell import dell_storagecenter_api
from cinder.volume.drivers.san import san
from cinder.volume.drivers.san.san import san_opts
from cinder.volume import volume_types
@ -47,11 +48,12 @@ CONF = cfg.CONF
CONF.register_opts(common_opts)
class DellCommonDriver(san.SanDriver):
class DellCommonDriver(driver.VolumeDriver):
def __init__(self, *args, **kwargs):
super(DellCommonDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(common_opts)
self.configuration.append_config_values(san_opts)
self.backend_name =\
self.configuration.safe_get('volume_backend_name') or 'Dell'
@ -328,6 +330,7 @@ class DellCommonDriver(san.SanDriver):
data['reserved_percentage'] = 0
data['free_capacity_gb'] = 'unavailable'
data['total_capacity_gb'] = 'unavailable'
data['consistencygroup_support'] = True
# In theory if storageusage is None then we should have
# blown up getting it. If not just report unavailable.
if storageusage is not None:
@ -372,3 +375,158 @@ class DellCommonDriver(san.SanDriver):
LOG.error(_LE('Unable to rename the logical volume for volume: %s'),
original_volume_name)
return {'_name_id': new_volume['_name_id'] or new_volume['id']}
def create_consistencygroup(self, context, group):
'''This creates a replay profile on the storage backend.
:param context: the context of the caller.
:param group: the dictionary of the consistency group to be created.
:return: Nothing on success.
:raises: VolumeBackendAPIException
'''
gid = group['id']
with self._client.open_connection() as api:
cgroup = api.create_replay_profile(gid)
if cgroup:
LOG.info(_LI('Created Consistency Group %s'), gid)
return
raise exception.VolumeBackendAPIException(
_('Unable to create consistency group %s') % gid)
def delete_consistencygroup(self, context, group):
'''Delete the Dell SC profile associated with this consistency group.
:param context: the context of the caller.
:param group: the dictionary of the consistency group to be created.
:return: Updated model_update, volumes.
'''
gid = group['id']
with self._client.open_connection() as api:
profile = api.find_replay_profile(gid)
if profile:
api.delete_replay_profile(profile)
# If we are here because we found no profile that should be fine
# as we are trying to delete it anyway.
# Now whack the volumes. So get our list.
volumes = self.db.volume_get_all_by_group(context, gid)
# Trundle through the list deleting the volumes.
for volume in volumes:
self.delete_volume(volume)
volume['status'] = 'deleted'
model_update = {'status': group['status']}
return model_update, volumes
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
'''Updates a consistency group.
:param context: the context of the caller.
:param group: the dictionary of the consistency group to be updated.
:param add_volumes: a list of volume dictionaries to be added.
:param remove_volumes: a list of volume dictionaries to be removed.
:return model_update, add_volumes_update, remove_volumes_update
model_update is a dictionary that the driver wants the manager
to update upon a successful return. If None is returned, the manager
will set the status to 'available'.
add_volumes_update and remove_volumes_update are lists of dictionaries
that the driver wants the manager to update upon a successful return.
Note that each entry requires a {'id': xxx} so that the correct
volume entry can be updated. If None is returned, the volume will
remain its original status. Also note that you cannot directly
assign add_volumes to add_volumes_update as add_volumes is a list of
cinder.db.sqlalchemy.models.Volume objects and cannot be used for
db update directly. Same with remove_volumes.
If the driver throws an exception, the status of the group as well as
those of the volumes to be added/removed will be set to 'error'.
'''
gid = group['id']
with self._client.open_connection() as api:
profile = api.find_replay_profile(gid)
if not profile:
LOG.error(_LE('Cannot find Consistency Group %s'), gid)
elif api.update_cg_volumes(profile,
add_volumes,
remove_volumes):
LOG.info(_LI('Updated Consistency Group %s'), gid)
# we need nothing updated above us so just return None.
return None, None, None
# Things did not go well so throw.
raise exception.VolumeBackendAPIException(
_('Unable to update consistency group %s') % gid)
def create_cgsnapshot(self, context, cgsnapshot):
'''Takes a snapshot of the consistency group.
:param context: the context of the caller.
:param cgsnapshot: Information about the snapshot to take.
:return: Updated model_update, snapshots.
:raises: VolumeBackendAPIException.
'''
cgid = cgsnapshot['consistencygroup_id']
snapshotid = cgsnapshot['id']
with self._client.open_connection() as api:
profile = api.find_replay_profile(cgid)
if profile:
LOG.debug('profile %s replayid %s', profile, snapshotid)
if api.snap_cg_replay(profile, snapshotid, 0):
snapshots = self.db.snapshot_get_all_for_cgsnapshot(
context,
snapshotid)
LOG.debug(snapshots)
for snapshot in snapshots:
LOG.debug(snapshot)
snapshot['status'] = 'available'
model_update = {'status': 'available'}
return model_update, snapshots
# That didn't go well. Tell them why. Then bomb out.
LOG.error(_LE('Failed to snap Consistency Group %s'), cgid)
else:
LOG.error(_LE('Cannot find Consistency Group %s'), cgid)
raise exception.VolumeBackendAPIException(
_('Unable to snap Consistency Group %s') % cgid)
def delete_cgsnapshot(self, context, cgsnapshot):
'''Deletes a cgsnapshot.
If profile isn't found return success. If failed to delete the
replay (the snapshot) then raise an exception.
:param context: the context of the caller.
:param cgsnapshot: Information about the snapshot to delete.
:return: Updated model_update, snapshots.
:raises: VolumeBackendAPIException.
'''
cgid = cgsnapshot['consistencygroup_id']
snapshotid = cgsnapshot['id']
with self._client.open_connection() as api:
profile = api.find_replay_profile(cgid)
if profile:
LOG.info(_LI('Deleting snapshot %(ss)s from %(pro)s'),
{'ss': snapshotid,
'pro': profile})
if not api.delete_cg_replay(profile, snapshotid):
raise exception.VolumeBackendAPIException(
_('Unable to delete Consistency Group snapshot %s') %
snapshotid)
snapshots = self.db.snapshot_get_all_for_cgsnapshot(context,
snapshotid)
for snapshot in snapshots:
snapshot['status'] = 'deleted'
model_update = {'status': 'deleted'}
return model_update, snapshots

View File

@ -37,9 +37,10 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
Version history:
1.0.0 - Initial driver
1.1.0 - Added extra spec support for Storage Profile selection
1.2.0 - Added consistency group support.
'''
VERSION = '1.1.0'
VERSION = '1.2.0'
def __init__(self, *args, **kwargs):
super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)

View File

@ -19,14 +19,13 @@ from oslo_utils import excutils
from cinder import exception
from cinder.i18n import _, _LE, _LI
from cinder.volume import driver
from cinder.volume.drivers.dell import dell_storagecenter_common
from cinder.volume.drivers import san
LOG = logging.getLogger(__name__)
class DellStorageCenterISCSIDriver(san.SanISCSIDriver,
dell_storagecenter_common.DellCommonDriver):
class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
driver.ISCSIDriver):
'''Implements commands for Dell StorageCenter ISCSI management.
@ -36,9 +35,10 @@ class DellStorageCenterISCSIDriver(san.SanISCSIDriver,
Version history:
1.0.0 - Initial driver
1.1.0 - Added extra spec support for Storage Profile selection
1.2.0 - Added consistency group support.
'''
VERSION = '1.1.0'
VERSION = '1.2.0'
def __init__(self, *args, **kwargs):
super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)