From 93b26e29991e869cd8bd9cbce3e2ce08693925f2 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 24 Jun 2015 16:55:33 -0500 Subject: [PATCH] Dell SC: Add support for driver retype Previously there was no need to support retype by the driver since any kind of retyping would require migration. With the addition of the ability to set specific Storage Profiles to use by commit Icf76fceca5a0ae20bb08b276b0c41ef6cdb31087 we now have something the driver can handle to optimize the retyping operation. This adds handling for changes in the selected Storage Profile if the backend stays the same. Any other retype changes will return failure, resulting in it falling back to full volume migration. Change-Id: I18c55e0392e18d1686546ba17cb99b53b7b50566 --- cinder/tests/unit/test_dellsc.py | 58 +++++++++- cinder/tests/unit/test_dellscapi.py | 107 ++++++++++++++++++ .../drivers/dell/dell_storagecenter_api.py | 72 ++++++++++++ .../drivers/dell/dell_storagecenter_common.py | 40 +++++++ .../drivers/dell/dell_storagecenter_fc.py | 3 +- .../drivers/dell/dell_storagecenter_iscsi.py | 3 +- 6 files changed, 279 insertions(+), 4 deletions(-) diff --git a/cinder/tests/unit/test_dellsc.py b/cinder/tests/unit/test_dellsc.py index 38a42269efe..2d4c55244ba 100644 --- a/cinder/tests/unit/test_dellsc.py +++ b/cinder/tests/unit/test_dellsc.py @@ -12,14 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - import mock +import uuid from cinder import context from cinder import exception from cinder import test from cinder.volume.drivers.dell import dell_storagecenter_api +from cinder.volume.drivers.dell import dell_storagecenter_common from cinder.volume.drivers.dell import dell_storagecenter_iscsi from cinder.volume import volume_types @@ -1636,6 +1636,37 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): volume, existing_ref) + def test_retype_not_extra_specs(self, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.driver.retype( + None, None, None, {'extra_specs': None}, None) + self.assertFalse(res) + + def test_retype_not_storage_profile(self, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.driver.retype( + None, None, None, {'extra_specs': {'something': 'else'}}, None) + self.assertFalse(res) + + def test_retype_malformed(self, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_common, "LOG") + res = self.driver.retype( + None, None, None, + {'extra_specs': { + 'storagetype:storageprofile': ['something', + 'not', + 'right']}}, + None) + self.assertFalse(res) + self.assertEqual(1, LOG.warning.call_count) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, 'find_volume', return_value=VOLUME) @@ -1667,3 +1698,26 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): self.driver.unmanage(volume) mock_find_volume.assert_called_once_with(volume['id']) self.assertFalse(mock_unmanage.called) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'update_storage_profile') + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_volume', + return_value=VOLUME) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + 'find_sc', + return_value=12345) + def test_retype(self, + mock_find_sc, + mock_find_volume, + mock_update_storage_profile, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.driver.retype( + None, {'id': 'volid'}, None, + {'extra_specs': {'storagetype:storageprofile': ['A', 'B']}}, + None) + mock_update_storage_profile.ssert_called_once_with( + self.VOLUME, 'B') + self.assertTrue(res) diff --git a/cinder/tests/unit/test_dellscapi.py b/cinder/tests/unit/test_dellscapi.py index 13836093da2..c700954b33b 100644 --- a/cinder/tests/unit/test_dellscapi.py +++ b/cinder/tests/unit/test_dellscapi.py @@ -4009,6 +4009,113 @@ 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={'test': 'test'}) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_200) + def test_get_user_preferences(self, + mock_get, + mock_get_json, + mock_close_connection, + mock_open_connection, + mock_init): + # Not really testing anything other than the ability to mock, but + # including for completeness. + res = self.scapi._get_user_preferences() + self.assertEqual({'test': 'test'}, res) + + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'get', + return_value=RESPONSE_400) + def test_get_user_preferences_failure(self, + mock_get, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_api, "LOG") + res = self.scapi._get_user_preferences() + self.assertEqual({}, res) + self.assertTrue(LOG.error.call_count > 0) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_user_preferences', + return_value=None) + def test_update_storage_profile_noprefs(self, + mock_prefs, + mock_close_connection, + mock_open_connection, + mock_init): + res = self.scapi.update_storage_profile(None, None) + self.assertFalse(res) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_user_preferences', + return_value={'allowStorageProfileSelection': False}) + def test_update_storage_profile_not_allowed(self, + mock_prefs, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_api, "LOG") + res = self.scapi.update_storage_profile(None, None) + self.assertFalse(res) + self.assertEqual(1, LOG.error.call_count) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_find_storage_profile', + return_value=None) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_user_preferences', + return_value={'allowStorageProfileSelection': True}) + def test_update_storage_profile_prefs_not_found(self, + mock_profile, + mock_prefs, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_api, "LOG") + res = self.scapi.update_storage_profile(None, 'Fake') + self.assertFalse(res) + self.assertEqual(1, LOG.error.call_count) + + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, + '_get_user_preferences', + return_value={'allowStorageProfileSelection': True, + 'storageProfile': None}) + def test_update_storage_profile_default_not_found(self, + mock_prefs, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_api, "LOG") + res = self.scapi.update_storage_profile(None, None) + self.assertFalse(res) + self.assertEqual(1, LOG.error.call_count) + + @mock.patch.object( + dell_storagecenter_api.StorageCenterApi, + '_get_user_preferences', + return_value={'allowStorageProfileSelection': True, + 'storageProfile': {'name': 'Fake', + 'instanceId': 'fakeId'}}) + @mock.patch.object(dell_storagecenter_api.HttpClient, + 'post', + return_value=RESPONSE_200) + def test_update_storage_profile(self, + mock_post, + mock_prefs, + mock_close_connection, + mock_open_connection, + mock_init): + LOG = self.mock_object(dell_storagecenter_api, "LOG") + fake_scvolume = {'name': 'name', 'instanceId': 'id'} + res = self.scapi.update_storage_profile(fake_scvolume, None) + self.assertTrue(res) + self.assertTrue('fakeId' in repr(mock_post.call_args_list[0])) + self.assertEqual(1, LOG.info.call_count) + @mock.patch.object(dell_storagecenter_api.StorageCenterApi, '_get_json', return_value=[RPLAY_PROFILE]) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index db3071c7953..1e985b52b46 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -1538,6 +1538,78 @@ class StorageCenterApi(object): return False return True + def update_storage_profile(self, scvolume, storage_profile): + """Update a volume's Storage Profile. + + Changes the volume setting to use a different Storage Profile. If + storage_profile is None, will reset to the default profile for the + cinder user account. + + :param scvolume: The Storage Center volume to be updated. + :param storage_profile: The requested Storage Profile name. + :returns: True if successful, False otherwise. + """ + prefs = self._get_user_preferences() + if not prefs: + return False + + if not prefs.get('allowStorageProfileSelection'): + LOG.error(_LE('User does not have permission to change ' + 'Storage Profile selection.')) + return False + + profile = self._find_storage_profile(storage_profile) + if storage_profile: + if not profile: + LOG.error(_LE('Storage Profile %s was not found.'), + storage_profile) + return False + else: + # Going from specific profile to the user default + profile = prefs.get('storageProfile') + if not profile: + LOG.error(_LE('Default Storage Profile was not found.')) + return False + + LOG.info(_LI('Switching volume %(vol)s to profile %(prof)s.'), + {'vol': scvolume['name'], + 'prof': profile.get('name')}) + payload = {} + payload['StorageProfile'] = self._get_id(profile) + r = self.client.post('StorageCenter/ScVolumeConfiguration' + '/%s/Modify' + % self._get_id(scvolume), + payload) + if r.status_code != 200: + LOG.error(_LE('Error changing Storage Profile for volume ' + '%(original)s to %(name)s: %(code)d %(reason)s ' + '%(text)s'), + {'original': scvolume['name'], + 'name': storage_profile, + 'code': r.status_code, + 'reason': r.reason, + 'text': r.text}) + return False + return True + + def _get_user_preferences(self): + """Gets the preferences and defaults for this user. + + There are a set of preferences and defaults for each user on the + Storage Center. This retrieves all settings for the current account + used by Cinder. + """ + r = self.client.get('StorageCenter/StorageCenter/%s/UserPreferences' % + self.ssn) + if r.status_code != 200: + LOG.error(_LE('Error getting user preferences: ' + '%(code)d %(reason)s %(text)s'), + {'code': r.status_code, + 'reason': r.reason, + 'text': r.text}) + return {} + return self._get_json(r) + def _delete_server(self, scserver): '''Deletes scserver from the backend. diff --git a/cinder/volume/drivers/dell/dell_storagecenter_common.py b/cinder/volume/drivers/dell/dell_storagecenter_common.py index cd68ac078f8..5627dd57aa6 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_common.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_common.py @@ -636,3 +636,43 @@ class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD, scvolume = api.find_volume(volume['id']) if scvolume: api.unmanage(scvolume) + + def retype(self, ctxt, volume, new_type, diff, host): + """Convert the volume to be of the new type. + + Returns a boolean indicating whether the retype occurred. + + :param ctxt: Context + :param volume: A dictionary describing the volume to migrate + :param new_type: A dictionary describing the volume type to convert to + :param diff: A dictionary with the difference between the two types + :param host: A dictionary describing the host to migrate to, where + host['host'] is its name, and host['capabilities'] is a + dictionary of its reported capabilities (Not Used). + """ + # We currently only support retyping for the Storage Profile extra spec + if diff['extra_specs']: + storage_profiles = diff['extra_specs'].get( + 'storagetype:storageprofile') + if storage_profiles: + if len(storage_profiles) != 2: + LOG.warning(_LW('Unable to retype Storage Profile, ' + 'expected to receive current and ' + 'requested storagetype:storageprofile ' + 'values. Value received: %s'), + storage_profiles) + return False + + requested = storage_profiles[1] + volume_name = volume.get('id') + LOG.debug('Retyping volume %(vol)s to use storage ' + 'profile %(profile)s', + {'vol': volume_name, + 'profile': requested}) + with self._client.open_connection() as api: + if api.find_sc(): + scvolume = api.find_volume(volume_name) + return api.update_storage_profile( + scvolume, requested) + + return False diff --git a/cinder/volume/drivers/dell/dell_storagecenter_fc.py b/cinder/volume/drivers/dell/dell_storagecenter_fc.py index 6944d5bd7d0..4a4f65104c0 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_fc.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_fc.py @@ -41,9 +41,10 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver, 2.0.0 - Switched to inheriting functional objects rather than volume driver. 2.1.0 - Added support for ManageableVD. + 2.2.0 - Driver retype support for switching volume's Storage Profile ''' - VERSION = '2.1.0' + VERSION = '2.2.0' def __init__(self, *args, **kwargs): super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py index f6b290ca906..6334342ea90 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py @@ -39,9 +39,10 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver, 2.0.0 - Switched to inheriting functional objects rather than volume driver. 2.1.0 - Added support for ManageableVD. + 2.2.0 - Driver retype support for switching volume's Storage Profile ''' - VERSION = '2.1.0' + VERSION = '2.2.0' def __init__(self, *args, **kwargs): super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)