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:
parent
869aad48aa
commit
55321e371f
|
@ -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'])
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue