From: Sean McGinnis Date: Wed, 24 Jun 2015 21:55:33 +0000 (-0500) Subject: Dell SC: Add support for driver retype X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=93b26e29991e869cd8bd9cbce3e2ce08693925f2;p=openstack-build%2Fcinder-build.git 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 --- diff --git a/cinder/tests/unit/test_dellsc.py b/cinder/tests/unit/test_dellsc.py index 38a42269e..2d4c55244 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 13836093d..c700954b3 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 db3071c79..1e985b52b 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 cd68ac078..5627dd57a 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 6944d5bd7..4a4f65104 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 f6b290ca9..6334342ea 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)