Merge "Implement retype in HP LeftHand driver"
This commit is contained in:
@@ -18,6 +18,7 @@ import mock
|
||||
|
||||
from hplefthandclient import exceptions as hpexceptions
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import test
|
||||
@@ -974,8 +975,10 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
||||
volume_with_vt['volume_type_id'] = self.volume_type_id
|
||||
|
||||
# get the extra specs of interest from this volume's volume type
|
||||
extra_specs = self.driver.proxy._get_extra_specs(
|
||||
volume_with_vt,
|
||||
volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
|
||||
volume_with_vt)
|
||||
extra_specs = self.driver.proxy._get_lh_extra_specs(
|
||||
volume_extra_specs,
|
||||
hp_lefthand_rest_proxy.extra_specs_key_map.keys())
|
||||
|
||||
# map the extra specs key/value pairs to key/value pairs
|
||||
@@ -1000,8 +1003,10 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
||||
'hplh:ao': 'true'}}
|
||||
|
||||
# get the extra specs of interest from this volume's volume type
|
||||
extra_specs = self.driver.proxy._get_extra_specs(
|
||||
volume_with_vt,
|
||||
volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
|
||||
volume_with_vt)
|
||||
extra_specs = self.driver.proxy._get_lh_extra_specs(
|
||||
volume_extra_specs,
|
||||
hp_lefthand_rest_proxy.extra_specs_key_map.keys())
|
||||
|
||||
# map the extra specs key/value pairs to key/value pairs
|
||||
@@ -1012,3 +1017,134 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
||||
# {'isAdaptiveOptimizationEnabled': True}
|
||||
# without hplh:data_pl since r-07 is an invalid value
|
||||
self.assertDictMatch({'isAdaptiveOptimizationEnabled': True}, optional)
|
||||
|
||||
def test_retype_with_no_LH_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
# and return the mock HTTP LeftHand client
|
||||
mock_client = self.setup_driver()
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
host = {'host': self.serverName}
|
||||
key_specs_old = {'foo': False, 'bar': 2, 'error': True}
|
||||
key_specs_new = {'foo': True, 'bar': 5, 'error': False}
|
||||
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
||||
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
||||
|
||||
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
||||
new_type_ref['id'])
|
||||
|
||||
volume = dict.copy(self.volume)
|
||||
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
||||
volume['volume_type'] = old_type
|
||||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume')]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_with_only_LH_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
# and return the mock HTTP LeftHand client
|
||||
mock_client = self.setup_driver()
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
host = {'host': self.serverName}
|
||||
key_specs_old = {'hplh:provisioning': 'thin'}
|
||||
key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
|
||||
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
||||
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
||||
|
||||
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
||||
new_type_ref['id'])
|
||||
|
||||
volume = dict.copy(self.volume)
|
||||
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
||||
volume['volume_type'] = old_type
|
||||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1, {
|
||||
'isThinProvisioned': False,
|
||||
'isAdaptiveOptimizationEnabled': True})]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_with_both_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
# and return the mock HTTP LeftHand client
|
||||
mock_client = self.setup_driver()
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
host = {'host': self.serverName}
|
||||
key_specs_old = {'hplh:provisioning': 'full', 'foo': 'bar'}
|
||||
key_specs_new = {'hplh:provisioning': 'thin', 'foo': 'foobar'}
|
||||
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
||||
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
||||
|
||||
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
||||
new_type_ref['id'])
|
||||
|
||||
volume = dict.copy(self.volume)
|
||||
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
||||
volume['volume_type'] = old_type
|
||||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(1, {'isThinProvisioned': True})]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
def test_retype_same_extra_specs(self):
|
||||
# setup drive with default configuration
|
||||
# and return the mock HTTP LeftHand client
|
||||
mock_client = self.setup_driver()
|
||||
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
host = {'host': self.serverName}
|
||||
key_specs_old = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
|
||||
key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'false'}
|
||||
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
||||
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
||||
|
||||
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
||||
new_type_ref['id'])
|
||||
|
||||
volume = dict.copy(self.volume)
|
||||
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
||||
volume['volume_type'] = old_type
|
||||
volume['host'] = host
|
||||
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
||||
|
||||
self.driver.retype(ctxt, volume, new_type, diff, host)
|
||||
|
||||
expected = self.driver_startup_call_stack + [
|
||||
mock.call.getVolumeByName('fakevolume'),
|
||||
mock.call.modifyVolume(
|
||||
1,
|
||||
{'isAdaptiveOptimizationEnabled': False})]
|
||||
|
||||
# validate call chain
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
@@ -448,3 +448,18 @@ class HPLeftHandCLIQProxy(SanISCSIDriver):
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def retype(self, context, 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.
|
||||
"""
|
||||
return False
|
||||
|
||||
@@ -46,9 +46,10 @@ class HPLeftHandISCSIDriver(VolumeDriver):
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.0.1 - Added support for retype
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0"
|
||||
VERSION = "1.0.1"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HPLeftHandISCSIDriver, self).__init__(*args, **kwargs)
|
||||
@@ -135,3 +136,8 @@ class HPLeftHandISCSIDriver(VolumeDriver):
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def remove_export(self, context, volume):
|
||||
return self.proxy.remove_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def retype(self, context, volume, new_type, diff, host):
|
||||
"""Convert the volume to be of the new type."""
|
||||
return self.proxy.retype(context, volume, new_type, diff, host)
|
||||
|
||||
@@ -83,9 +83,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial REST iSCSI proxy
|
||||
1.0.1 - Added support for retype
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0"
|
||||
VERSION = "1.0.1"
|
||||
|
||||
device_stats = {}
|
||||
|
||||
@@ -131,8 +132,9 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
||||
"""Creates a volume."""
|
||||
try:
|
||||
# get the extra specs of interest from this volume's volume type
|
||||
extra_specs = self._get_extra_specs(
|
||||
volume,
|
||||
volume_extra_specs = self._get_volume_extra_specs(volume)
|
||||
extra_specs = self._get_lh_extra_specs(
|
||||
volume_extra_specs,
|
||||
extra_specs_key_map.keys())
|
||||
|
||||
# map the extra specs key/value pairs to key/value pairs
|
||||
@@ -285,19 +287,24 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def _get_extra_specs(self, volume, valid_keys):
|
||||
"""Get extra specs of interest (valid_keys) from volume type."""
|
||||
def _get_volume_extra_specs(self, volume):
|
||||
"""Get extra specs from a volume."""
|
||||
extra_specs = {}
|
||||
type_id = volume.get('volume_type_id', None)
|
||||
if type_id is not None:
|
||||
ctxt = context.get_admin_context()
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
specs = volume_type.get('extra_specs')
|
||||
for key, value in specs.iteritems():
|
||||
if key in valid_keys:
|
||||
extra_specs[key] = value
|
||||
extra_specs = volume_type.get('extra_specs')
|
||||
return extra_specs
|
||||
|
||||
def _get_lh_extra_specs(self, extra_specs, valid_keys):
|
||||
"""Get LeftHand extra_specs (valid_keys only)."""
|
||||
extra_specs_of_interest = {}
|
||||
for key, value in extra_specs.iteritems():
|
||||
if key in valid_keys:
|
||||
extra_specs_of_interest[key] = value
|
||||
return extra_specs_of_interest
|
||||
|
||||
def _map_extra_specs(self, extra_specs):
|
||||
"""Map the extra spec key/values to LeftHand key/values."""
|
||||
client_options = {}
|
||||
@@ -361,3 +368,53 @@ class HPLeftHandRESTProxy(ISCSIDriver):
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
||||
|
||||
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 retype
|
||||
: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, where
|
||||
host['host'] is its name, and host['capabilities'] is a
|
||||
dictionary of its reported capabilities.
|
||||
"""
|
||||
LOG.debug(_('enter: retype: id=%(id)s, new_type=%(new_type)s,'
|
||||
'diff=%(diff)s, host=%(host)s') % {'id': volume['id'],
|
||||
'new_type': new_type,
|
||||
'diff': diff,
|
||||
'host': host})
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
raise exception.VolumeNotFound(volume_id=volume['id'])
|
||||
|
||||
try:
|
||||
# pick out the LH extra specs
|
||||
new_extra_specs = dict(new_type).get('extra_specs')
|
||||
lh_extra_specs = self._get_lh_extra_specs(
|
||||
new_extra_specs,
|
||||
extra_specs_key_map.keys())
|
||||
|
||||
LOG.debug(_('LH specs=%(specs)s') % {'specs': lh_extra_specs})
|
||||
|
||||
# only set the ones that have changed
|
||||
changed_extra_specs = {}
|
||||
for key, value in lh_extra_specs.iteritems():
|
||||
(old, new) = diff['extra_specs'][key]
|
||||
if old != new:
|
||||
changed_extra_specs[key] = value
|
||||
|
||||
# map extra specs to LeftHand options
|
||||
options = self._map_extra_specs(changed_extra_specs)
|
||||
if len(options) > 0:
|
||||
self.client.modifyVolume(volume_info['id'], options)
|
||||
return True
|
||||
|
||||
except Exception as ex:
|
||||
LOG.warning("%s" % str(ex))
|
||||
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user