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
This commit is contained in:
Sean McGinnis 2015-06-24 16:55:33 -05:00
parent 1f71d038f1
commit 93b26e2999
6 changed files with 279 additions and 4 deletions

View File

@ -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)

View 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])

View 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.

View 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

View 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)

View 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)