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',
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'])
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,
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()
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):
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.
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,
: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:
'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
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
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'
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:
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
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)
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.
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)