]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Dell SC: Add support for driver retype
authorSean McGinnis <sean_mcginnis@dell.com>
Wed, 24 Jun 2015 21:55:33 +0000 (16:55 -0500)
committerSean McGinnis <sean_mcginnis@dell.com>
Thu, 23 Jul 2015 17:47:53 +0000 (12:47 -0500)
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
cinder/tests/unit/test_dellscapi.py
cinder/volume/drivers/dell/dell_storagecenter_api.py
cinder/volume/drivers/dell/dell_storagecenter_common.py
cinder/volume/drivers/dell/dell_storagecenter_fc.py
cinder/volume/drivers/dell/dell_storagecenter_iscsi.py

index 38a42269efeb48b714347518d2fb4c9a148ab31c..2d4c55244ba0aa1bc632c524116970a21b1de8f4 100644 (file)
 #    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)
index 13836093da2533b323a9f0806aeb4565d657c219..c700954b33b78eba904590b7620fb430916a08af 100644 (file)
@@ -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])
index db3071c7953055abd0974be872699971061f0549..1e985b52b4673a1cd4826fb12b39684dba0fc1c9 100644 (file)
@@ -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.
 
index cd68ac078f83d110fae8ccd7065bcc9ace7baa5d..5627dd57aa6e655fc40d7943a722261eec9e85dc 100644 (file)
@@ -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
index 6944d5bd7d06f13dceae960f90e1e3ed09ab1a1a..4a4f65104c069daee4cac9eeab37b1fc3b161a1f 100644 (file)
@@ -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)
index f6b290ca90648ab89c77a3e6f8ab1d863a9d57e3..6334342ea90eebcd9582a9b14a6354c1c7f3ef89 100644 (file)
@@ -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)