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)