Merge "Update retype API to use versionedobjects"
This commit is contained in:
commit
8d0e9f381a
@ -56,7 +56,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
class SchedulerManager(manager.Manager):
|
class SchedulerManager(manager.Manager):
|
||||||
"""Chooses a host to create volumes."""
|
"""Chooses a host to create volumes."""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.9'
|
RPC_API_VERSION = '1.10'
|
||||||
|
|
||||||
target = messaging.Target(version=RPC_API_VERSION)
|
target = messaging.Target(version=RPC_API_VERSION)
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ class SchedulerManager(manager.Manager):
|
|||||||
force_host_copy)
|
force_host_copy)
|
||||||
|
|
||||||
def retype(self, context, topic, volume_id,
|
def retype(self, context, topic, volume_id,
|
||||||
request_spec, filter_properties=None):
|
request_spec, filter_properties=None, volume=None):
|
||||||
"""Schedule the modification of a volume's type.
|
"""Schedule the modification of a volume's type.
|
||||||
|
|
||||||
:param context: the request context
|
:param context: the request context
|
||||||
@ -190,10 +190,17 @@ class SchedulerManager(manager.Manager):
|
|||||||
:param volume_id: the ID of the volume to retype
|
:param volume_id: the ID of the volume to retype
|
||||||
:param request_spec: parameters for this retype request
|
:param request_spec: parameters for this retype request
|
||||||
:param filter_properties: parameters to filter by
|
:param filter_properties: parameters to filter by
|
||||||
|
:param volume: the volume object to retype
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._wait_for_scheduler()
|
self._wait_for_scheduler()
|
||||||
|
|
||||||
|
# FIXME(thangp): Remove this in v2.0 of RPC API.
|
||||||
|
if volume is None:
|
||||||
|
# For older clients, mimic the old behavior and look up the
|
||||||
|
# volume by its volume_id.
|
||||||
|
volume = objects.Volume.get_by_id(context, volume_id)
|
||||||
|
|
||||||
def _retype_volume_set_error(self, context, ex, request_spec,
|
def _retype_volume_set_error(self, context, ex, request_spec,
|
||||||
volume_ref, msg, reservations):
|
volume_ref, msg, reservations):
|
||||||
if reservations:
|
if reservations:
|
||||||
@ -204,14 +211,13 @@ class SchedulerManager(manager.Manager):
|
|||||||
self._set_volume_state_and_notify('retype', volume_state,
|
self._set_volume_state_and_notify('retype', volume_state,
|
||||||
context, ex, request_spec, msg)
|
context, ex, request_spec, msg)
|
||||||
|
|
||||||
volume_ref = db.volume_get(context, volume_id)
|
|
||||||
reservations = request_spec.get('quota_reservations')
|
reservations = request_spec.get('quota_reservations')
|
||||||
new_type = request_spec.get('volume_type')
|
new_type = request_spec.get('volume_type')
|
||||||
if new_type is None:
|
if new_type is None:
|
||||||
msg = _('New volume type not specified in request_spec.')
|
msg = _('New volume type not specified in request_spec.')
|
||||||
ex = exception.ParameterNotFound(param='volume_type')
|
ex = exception.ParameterNotFound(param='volume_type')
|
||||||
_retype_volume_set_error(self, context, ex, request_spec,
|
_retype_volume_set_error(self, context, ex, request_spec,
|
||||||
volume_ref, msg, reservations)
|
volume, msg, reservations)
|
||||||
|
|
||||||
# Default migration policy is 'never'
|
# Default migration policy is 'never'
|
||||||
migration_policy = request_spec.get('migration_policy')
|
migration_policy = request_spec.get('migration_policy')
|
||||||
@ -225,15 +231,15 @@ class SchedulerManager(manager.Manager):
|
|||||||
except exception.NoValidHost as ex:
|
except exception.NoValidHost as ex:
|
||||||
msg = (_("Could not find a host for volume %(volume_id)s with "
|
msg = (_("Could not find a host for volume %(volume_id)s with "
|
||||||
"type %(type_id)s.") %
|
"type %(type_id)s.") %
|
||||||
{'type_id': new_type['id'], 'volume_id': volume_id})
|
{'type_id': new_type['id'], 'volume_id': volume.id})
|
||||||
_retype_volume_set_error(self, context, ex, request_spec,
|
_retype_volume_set_error(self, context, ex, request_spec,
|
||||||
volume_ref, msg, reservations)
|
volume, msg, reservations)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
_retype_volume_set_error(self, context, ex, request_spec,
|
_retype_volume_set_error(self, context, ex, request_spec,
|
||||||
volume_ref, None, reservations)
|
volume, None, reservations)
|
||||||
else:
|
else:
|
||||||
volume_rpcapi.VolumeAPI().retype(context, volume_ref,
|
volume_rpcapi.VolumeAPI().retype(context, volume,
|
||||||
new_type['id'], tgt_host,
|
new_type['id'], tgt_host,
|
||||||
migration_policy, reservations)
|
migration_policy, reservations)
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ class SchedulerAPI(object):
|
|||||||
1.7 - Add get_active_pools method
|
1.7 - Add get_active_pools method
|
||||||
1.8 - Add sending object over RPC in create_consistencygroup method
|
1.8 - Add sending object over RPC in create_consistencygroup method
|
||||||
1.9 - Adds support for sending objects over RPC in create_volume()
|
1.9 - Adds support for sending objects over RPC in create_volume()
|
||||||
|
1.10 - Adds support for sending objects over RPC in retype()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.0'
|
RPC_API_VERSION = '1.0'
|
||||||
@ -107,15 +108,20 @@ class SchedulerAPI(object):
|
|||||||
filter_properties=filter_properties)
|
filter_properties=filter_properties)
|
||||||
|
|
||||||
def retype(self, ctxt, topic, volume_id,
|
def retype(self, ctxt, topic, volume_id,
|
||||||
request_spec=None, filter_properties=None):
|
request_spec=None, filter_properties=None, volume=None):
|
||||||
|
|
||||||
cctxt = self.client.prepare(version='1.4')
|
|
||||||
request_spec_p = jsonutils.to_primitive(request_spec)
|
request_spec_p = jsonutils.to_primitive(request_spec)
|
||||||
return cctxt.cast(ctxt, 'retype',
|
msg_args = {'topic': topic, 'volume_id': volume_id,
|
||||||
topic=topic,
|
'request_spec': request_spec_p,
|
||||||
volume_id=volume_id,
|
'filter_properties': filter_properties}
|
||||||
request_spec=request_spec_p,
|
if self.client.can_send_version('1.10'):
|
||||||
filter_properties=filter_properties)
|
version = '1.10'
|
||||||
|
msg_args['volume'] = volume
|
||||||
|
else:
|
||||||
|
version = '1.4'
|
||||||
|
|
||||||
|
cctxt = self.client.prepare(version=version)
|
||||||
|
return cctxt.cast(ctxt, 'retype', **msg_args)
|
||||||
|
|
||||||
def manage_existing(self, ctxt, topic, volume_id,
|
def manage_existing(self, ctxt, topic, volume_id,
|
||||||
request_spec=None, filter_properties=None):
|
request_spec=None, filter_properties=None):
|
||||||
|
@ -128,14 +128,31 @@ class SchedulerRpcAPITestCase(test.TestCase):
|
|||||||
filter_properties='filter_properties',
|
filter_properties='filter_properties',
|
||||||
version='1.3')
|
version='1.3')
|
||||||
|
|
||||||
def test_retype(self):
|
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||||
|
return_value=True)
|
||||||
|
def test_retype(self, can_send_version):
|
||||||
self._test_scheduler_api('retype',
|
self._test_scheduler_api('retype',
|
||||||
rpc_method='cast',
|
rpc_method='cast',
|
||||||
topic='topic',
|
topic='topic',
|
||||||
volume_id='volume_id',
|
volume_id='volume_id',
|
||||||
request_spec='fake_request_spec',
|
request_spec='fake_request_spec',
|
||||||
filter_properties='filter_properties',
|
filter_properties='filter_properties',
|
||||||
|
volume='volume',
|
||||||
|
version='1.10')
|
||||||
|
can_send_version.assert_called_with('1.10')
|
||||||
|
|
||||||
|
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||||
|
return_value=False)
|
||||||
|
def test_retype_old(self, can_send_version):
|
||||||
|
self._test_scheduler_api('retype',
|
||||||
|
rpc_method='cast',
|
||||||
|
topic='topic',
|
||||||
|
volume_id='volume_id',
|
||||||
|
request_spec='fake_request_spec',
|
||||||
|
filter_properties='filter_properties',
|
||||||
|
volume='volume',
|
||||||
version='1.4')
|
version='1.4')
|
||||||
|
can_send_version.assert_called_with('1.10')
|
||||||
|
|
||||||
def test_manage_existing(self):
|
def test_manage_existing(self):
|
||||||
self._test_scheduler_api('manage_existing',
|
self._test_scheduler_api('manage_existing',
|
||||||
|
@ -225,37 +225,37 @@ class SchedulerManagerTestCase(test.TestCase):
|
|||||||
request_spec, {})
|
request_spec, {})
|
||||||
|
|
||||||
@mock.patch('cinder.db.volume_update')
|
@mock.patch('cinder.db.volume_update')
|
||||||
@mock.patch('cinder.db.volume_get')
|
@mock.patch('cinder.db.volume_attachment_get_used_by_volume_id')
|
||||||
def test_retype_volume_exception_returns_volume_state(self, _mock_vol_get,
|
def test_retype_volume_exception_returns_volume_state(
|
||||||
_mock_vol_update):
|
self, _mock_vol_attachment_get, _mock_vol_update):
|
||||||
# Test NoValidHost exception behavior for retype.
|
# Test NoValidHost exception behavior for retype.
|
||||||
# Puts the volume in original state and eats the exception.
|
# Puts the volume in original state and eats the exception.
|
||||||
volume = tests_utils.create_volume(self.context,
|
volume = tests_utils.create_volume(self.context,
|
||||||
status='retyping',
|
status='retyping',
|
||||||
previous_status='in-use')
|
previous_status='in-use')
|
||||||
instance_uuid = '12345678-1234-5678-1234-567812345678'
|
instance_uuid = '12345678-1234-5678-1234-567812345678'
|
||||||
volume = tests_utils.attach_volume(self.context, volume['id'],
|
volume_attach = tests_utils.attach_volume(self.context, volume.id,
|
||||||
instance_uuid, None, '/dev/fake')
|
instance_uuid, None,
|
||||||
fake_volume_id = volume.id
|
'/dev/fake')
|
||||||
|
_mock_vol_attachment_get.return_value = [volume_attach]
|
||||||
topic = 'fake_topic'
|
topic = 'fake_topic'
|
||||||
request_spec = {'volume_id': fake_volume_id, 'volume_type': {'id': 3},
|
request_spec = {'volume_id': volume.id, 'volume_type': {'id': 3},
|
||||||
'migration_policy': 'on-demand'}
|
'migration_policy': 'on-demand'}
|
||||||
_mock_vol_get.return_value = volume
|
|
||||||
_mock_vol_update.return_value = {'status': 'in-use'}
|
_mock_vol_update.return_value = {'status': 'in-use'}
|
||||||
_mock_find_retype_host = mock.Mock(
|
_mock_find_retype_host = mock.Mock(
|
||||||
side_effect=exception.NoValidHost(reason=""))
|
side_effect=exception.NoValidHost(reason=""))
|
||||||
orig_retype = self.manager.driver.find_retype_host
|
orig_retype = self.manager.driver.find_retype_host
|
||||||
self.manager.driver.find_retype_host = _mock_find_retype_host
|
self.manager.driver.find_retype_host = _mock_find_retype_host
|
||||||
|
|
||||||
self.manager.retype(self.context, topic, fake_volume_id,
|
self.manager.retype(self.context, topic, volume.id,
|
||||||
request_spec=request_spec,
|
request_spec=request_spec,
|
||||||
filter_properties={})
|
filter_properties={},
|
||||||
|
volume=volume)
|
||||||
|
|
||||||
_mock_vol_get.assert_called_once_with(self.context, fake_volume_id)
|
|
||||||
_mock_find_retype_host.assert_called_once_with(self.context,
|
_mock_find_retype_host.assert_called_once_with(self.context,
|
||||||
request_spec, {},
|
request_spec, {},
|
||||||
'on-demand')
|
'on-demand')
|
||||||
_mock_vol_update.assert_called_once_with(self.context, fake_volume_id,
|
_mock_vol_update.assert_called_once_with(self.context, volume.id,
|
||||||
{'status': 'in-use'})
|
{'status': 'in-use'})
|
||||||
self.manager.driver.find_retype_host = orig_retype
|
self.manager.driver.find_retype_host = orig_retype
|
||||||
|
|
||||||
|
@ -683,6 +683,17 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||||||
False,
|
False,
|
||||||
FAKE_METADATA_TYPE.fake_type)
|
FAKE_METADATA_TYPE.fake_type)
|
||||||
|
|
||||||
|
@mock.patch('cinder.db.volume_update')
|
||||||
|
def test_update_with_ovo(self, volume_update):
|
||||||
|
"""Test update volume using oslo_versionedobject."""
|
||||||
|
volume = tests_utils.create_volume(self.context, **self.volume_params)
|
||||||
|
volume_api = cinder.volume.api.API()
|
||||||
|
updates = {'display_name': 'foobbar'}
|
||||||
|
volume_api.update(self.context, volume, updates)
|
||||||
|
volume_update.assert_called_once_with(self.context, volume.id,
|
||||||
|
updates)
|
||||||
|
self.assertEqual('foobbar', volume.display_name)
|
||||||
|
|
||||||
def test_delete_volume_metadata_with_metatype(self):
|
def test_delete_volume_metadata_with_metatype(self):
|
||||||
"""Test delete volume metadata with different metadata type."""
|
"""Test delete volume metadata with different metadata type."""
|
||||||
test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
|
test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
|
||||||
@ -3908,7 +3919,6 @@ class VolumeTestCase(BaseVolumeTestCase):
|
|||||||
status='creating', host=CONF.host)
|
status='creating', host=CONF.host)
|
||||||
self.volume.create_volume(self.context, volume['id'])
|
self.volume.create_volume(self.context, volume['id'])
|
||||||
volume['status'] = 'in-use'
|
volume['status'] = 'in-use'
|
||||||
volume['host'] = 'fakehost'
|
|
||||||
|
|
||||||
volume_api = cinder.volume.api.API()
|
volume_api = cinder.volume.api.API()
|
||||||
|
|
||||||
@ -4753,15 +4763,16 @@ class VolumeMigrationTestCase(VolumeTestCase):
|
|||||||
host=CONF.host, status='retyping',
|
host=CONF.host, status='retyping',
|
||||||
volume_type_id=old_vol_type['id'],
|
volume_type_id=old_vol_type['id'],
|
||||||
replication_status=rep_status)
|
replication_status=rep_status)
|
||||||
volume['previous_status'] = 'available'
|
volume.previous_status = 'available'
|
||||||
|
volume.save()
|
||||||
if snap:
|
if snap:
|
||||||
self._create_snapshot(volume['id'], size=volume['size'])
|
self._create_snapshot(volume.id, size=volume.size)
|
||||||
if driver or diff_equal:
|
if driver or diff_equal:
|
||||||
host_obj = {'host': CONF.host, 'capabilities': {}}
|
host_obj = {'host': CONF.host, 'capabilities': {}}
|
||||||
else:
|
else:
|
||||||
host_obj = {'host': 'newhost', 'capabilities': {}}
|
host_obj = {'host': 'newhost', 'capabilities': {}}
|
||||||
|
|
||||||
reserve_opts = {'volumes': 1, 'gigabytes': volume['size']}
|
reserve_opts = {'volumes': 1, 'gigabytes': volume.size}
|
||||||
QUOTAS.add_volume_type_opts(self.context,
|
QUOTAS.add_volume_type_opts(self.context,
|
||||||
reserve_opts,
|
reserve_opts,
|
||||||
vol_type['id'])
|
vol_type['id'])
|
||||||
@ -4782,20 +4793,21 @@ class VolumeMigrationTestCase(VolumeTestCase):
|
|||||||
_mig.return_value = True
|
_mig.return_value = True
|
||||||
|
|
||||||
if not exc:
|
if not exc:
|
||||||
self.volume.retype(self.context, volume['id'],
|
self.volume.retype(self.context, volume.id,
|
||||||
vol_type['id'], host_obj,
|
vol_type['id'], host_obj,
|
||||||
migration_policy=policy,
|
migration_policy=policy,
|
||||||
reservations=reservations)
|
reservations=reservations,
|
||||||
|
volume=volume)
|
||||||
else:
|
else:
|
||||||
self.assertRaises(exc, self.volume.retype,
|
self.assertRaises(exc, self.volume.retype,
|
||||||
self.context, volume['id'],
|
self.context, volume.id,
|
||||||
vol_type['id'], host_obj,
|
vol_type['id'], host_obj,
|
||||||
migration_policy=policy,
|
migration_policy=policy,
|
||||||
reservations=reservations)
|
reservations=reservations,
|
||||||
get_volume.assert_called_once_with(self.context, volume['id'])
|
volume=volume)
|
||||||
|
|
||||||
# get volume/quota properties
|
# get volume/quota properties
|
||||||
volume = db.volume_get(elevated, volume['id'])
|
volume = objects.Volume.get_by_id(elevated, volume.id)
|
||||||
try:
|
try:
|
||||||
usage = db.quota_usage_get(elevated, project_id, 'volumes_new')
|
usage = db.quota_usage_get(elevated, project_id, 'volumes_new')
|
||||||
volumes_in_use = usage.in_use
|
volumes_in_use = usage.in_use
|
||||||
@ -4804,19 +4816,19 @@ class VolumeMigrationTestCase(VolumeTestCase):
|
|||||||
|
|
||||||
# check properties
|
# check properties
|
||||||
if driver or diff_equal:
|
if driver or diff_equal:
|
||||||
self.assertEqual(vol_type['id'], volume['volume_type_id'])
|
self.assertEqual(vol_type['id'], volume.volume_type_id)
|
||||||
self.assertEqual('available', volume['status'])
|
self.assertEqual('available', volume.status)
|
||||||
self.assertEqual(CONF.host, volume['host'])
|
self.assertEqual(CONF.host, volume.host)
|
||||||
self.assertEqual(1, volumes_in_use)
|
self.assertEqual(1, volumes_in_use)
|
||||||
elif not exc:
|
elif not exc:
|
||||||
self.assertEqual(old_vol_type['id'], volume['volume_type_id'])
|
self.assertEqual(old_vol_type['id'], volume.volume_type_id)
|
||||||
self.assertEqual('retyping', volume['status'])
|
self.assertEqual('retyping', volume.status)
|
||||||
self.assertEqual(CONF.host, volume['host'])
|
self.assertEqual(CONF.host, volume.host)
|
||||||
self.assertEqual(1, volumes_in_use)
|
self.assertEqual(1, volumes_in_use)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(old_vol_type['id'], volume['volume_type_id'])
|
self.assertEqual(old_vol_type['id'], volume.volume_type_id)
|
||||||
self.assertEqual('available', volume['status'])
|
self.assertEqual('available', volume.status)
|
||||||
self.assertEqual(CONF.host, volume['host'])
|
self.assertEqual(CONF.host, volume.host)
|
||||||
self.assertEqual(0, volumes_in_use)
|
self.assertEqual(0, volumes_in_use)
|
||||||
|
|
||||||
def test_retype_volume_driver_success(self):
|
def test_retype_volume_driver_success(self):
|
||||||
|
@ -399,7 +399,9 @@ class VolumeRpcAPITestCase(test.TestCase):
|
|||||||
error=False,
|
error=False,
|
||||||
version='1.10')
|
version='1.10')
|
||||||
|
|
||||||
def test_retype(self):
|
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||||
|
return_value=True)
|
||||||
|
def test_retype(self, can_send_version):
|
||||||
class FakeHost(object):
|
class FakeHost(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.host = 'host'
|
self.host = 'host'
|
||||||
@ -407,12 +409,31 @@ class VolumeRpcAPITestCase(test.TestCase):
|
|||||||
dest_host = FakeHost()
|
dest_host = FakeHost()
|
||||||
self._test_volume_api('retype',
|
self._test_volume_api('retype',
|
||||||
rpc_method='cast',
|
rpc_method='cast',
|
||||||
volume=self.fake_volume,
|
volume=self.fake_volume_obj,
|
||||||
|
new_type_id='fake',
|
||||||
|
dest_host=dest_host,
|
||||||
|
migration_policy='never',
|
||||||
|
reservations=None,
|
||||||
|
version='1.34')
|
||||||
|
can_send_version.assert_called_once_with('1.34')
|
||||||
|
|
||||||
|
@mock.patch('oslo_messaging.RPCClient.can_send_version',
|
||||||
|
return_value=False)
|
||||||
|
def test_retype_old(self, can_send_version):
|
||||||
|
class FakeHost(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.host = 'host'
|
||||||
|
self.capabilities = {}
|
||||||
|
dest_host = FakeHost()
|
||||||
|
self._test_volume_api('retype',
|
||||||
|
rpc_method='cast',
|
||||||
|
volume=self.fake_volume_obj,
|
||||||
new_type_id='fake',
|
new_type_id='fake',
|
||||||
dest_host=dest_host,
|
dest_host=dest_host,
|
||||||
migration_policy='never',
|
migration_policy='never',
|
||||||
reservations=None,
|
reservations=None,
|
||||||
version='1.12')
|
version='1.12')
|
||||||
|
can_send_version.assert_called_once_with('1.34')
|
||||||
|
|
||||||
def test_manage_existing(self):
|
def test_manage_existing(self):
|
||||||
self._test_volume_api('manage_existing',
|
self._test_volume_api('manage_existing',
|
||||||
|
@ -456,8 +456,16 @@ class API(base.Base):
|
|||||||
msg = _("The volume cannot be updated during maintenance.")
|
msg = _("The volume cannot be updated during maintenance.")
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
|
|
||||||
vref = self.db.volume_update(context, volume['id'], fields)
|
# NOTE(thangp): Update is called by various APIs, some of which are
|
||||||
LOG.info(_LI("Volume updated successfully."), resource=vref)
|
# not yet using oslo_versionedobjects. We need to handle the case
|
||||||
|
# where volume is either a dict or a oslo_versionedobject.
|
||||||
|
if isinstance(volume, objects_base.CinderObject):
|
||||||
|
volume.update(fields)
|
||||||
|
volume.save()
|
||||||
|
LOG.info(_LI("Volume updated successfully."), resource=volume)
|
||||||
|
else:
|
||||||
|
vref = self.db.volume_update(context, volume['id'], fields)
|
||||||
|
LOG.info(_LI("Volume updated successfully."), resource=vref)
|
||||||
|
|
||||||
def get(self, context, volume_id, viewable_admin_meta=False):
|
def get(self, context, volume_id, viewable_admin_meta=False):
|
||||||
volume = objects.Volume.get_by_id(context, volume_id)
|
volume = objects.Volume.get_by_id(context, volume_id)
|
||||||
@ -1436,18 +1444,18 @@ class API(base.Base):
|
|||||||
@wrap_check_policy
|
@wrap_check_policy
|
||||||
def retype(self, context, volume, new_type, migration_policy=None):
|
def retype(self, context, volume, new_type, migration_policy=None):
|
||||||
"""Attempt to modify the type associated with an existing volume."""
|
"""Attempt to modify the type associated with an existing volume."""
|
||||||
if volume['status'] not in ['available', 'in-use']:
|
if volume.status not in ['available', 'in-use']:
|
||||||
msg = _('Unable to update type due to incorrect status: '
|
msg = _('Unable to update type due to incorrect status: '
|
||||||
'%(vol_status)s on volume: %(vol_id)s. Volume status '
|
'%(vol_status)s on volume: %(vol_id)s. Volume status '
|
||||||
'must be available or '
|
'must be available or '
|
||||||
'in-use.') % {'vol_status': volume['status'],
|
'in-use.') % {'vol_status': volume.status,
|
||||||
'vol_id': volume['id']}
|
'vol_id': volume.id}
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
|
|
||||||
if self._is_volume_migrating(volume):
|
if self._is_volume_migrating(volume):
|
||||||
msg = (_("Volume %s is already part of an active migration.")
|
msg = (_("Volume %s is already part of an active migration.")
|
||||||
% volume['id'])
|
% volume.id)
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
|
|
||||||
@ -1457,8 +1465,7 @@ class API(base.Base):
|
|||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
cg_id = volume.get('consistencygroup_id', None)
|
if volume.consistencygroup_id:
|
||||||
if cg_id:
|
|
||||||
msg = _("Volume must not be part of a consistency group.")
|
msg = _("Volume must not be part of a consistency group.")
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
@ -1480,16 +1487,16 @@ class API(base.Base):
|
|||||||
vol_type_qos_id = vol_type['qos_specs_id']
|
vol_type_qos_id = vol_type['qos_specs_id']
|
||||||
|
|
||||||
old_vol_type = None
|
old_vol_type = None
|
||||||
old_vol_type_id = volume['volume_type_id']
|
old_vol_type_id = volume.volume_type_id
|
||||||
old_vol_type_qos_id = None
|
old_vol_type_qos_id = None
|
||||||
|
|
||||||
# Error if the original and new type are the same
|
# Error if the original and new type are the same
|
||||||
if volume['volume_type_id'] == vol_type_id:
|
if volume.volume_type_id == vol_type_id:
|
||||||
msg = _('New volume_type same as original: %s.') % new_type
|
msg = _('New volume_type same as original: %s.') % new_type
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
if volume['volume_type_id']:
|
if volume.volume_type_id:
|
||||||
old_vol_type = volume_types.get_volume_type(
|
old_vol_type = volume_types.get_volume_type(
|
||||||
context, old_vol_type_id)
|
context, old_vol_type_id)
|
||||||
old_vol_type_qos_id = old_vol_type['qos_specs_id']
|
old_vol_type_qos_id = old_vol_type['qos_specs_id']
|
||||||
@ -1506,14 +1513,14 @@ class API(base.Base):
|
|||||||
# We don't support changing QoS at the front-end yet for in-use volumes
|
# We don't support changing QoS at the front-end yet for in-use volumes
|
||||||
# TODO(avishay): Call Nova to change QoS setting (libvirt has support
|
# TODO(avishay): Call Nova to change QoS setting (libvirt has support
|
||||||
# - virDomainSetBlockIoTune() - Nova does not have support yet).
|
# - virDomainSetBlockIoTune() - Nova does not have support yet).
|
||||||
if (volume['status'] != 'available' and
|
if (volume.status != 'available' and
|
||||||
old_vol_type_qos_id != vol_type_qos_id):
|
old_vol_type_qos_id != vol_type_qos_id):
|
||||||
for qos_id in [old_vol_type_qos_id, vol_type_qos_id]:
|
for qos_id in [old_vol_type_qos_id, vol_type_qos_id]:
|
||||||
if qos_id:
|
if qos_id:
|
||||||
specs = qos_specs.get_qos_specs(context.elevated(), qos_id)
|
specs = qos_specs.get_qos_specs(context.elevated(), qos_id)
|
||||||
if specs['consumer'] != 'back-end':
|
if specs['consumer'] != 'back-end':
|
||||||
msg = _('Retype cannot change front-end qos specs for '
|
msg = _('Retype cannot change front-end qos specs for '
|
||||||
'in-use volume: %s.') % volume['id']
|
'in-use volume: %s.') % volume.id
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
# We're checking here in so that we can report any quota issues as
|
# We're checking here in so that we can report any quota issues as
|
||||||
@ -1523,17 +1530,17 @@ class API(base.Base):
|
|||||||
vol_type_id)
|
vol_type_id)
|
||||||
|
|
||||||
self.update(context, volume, {'status': 'retyping',
|
self.update(context, volume, {'status': 'retyping',
|
||||||
'previous_status': volume['status']})
|
'previous_status': volume.status})
|
||||||
|
|
||||||
request_spec = {'volume_properties': volume,
|
request_spec = {'volume_properties': volume,
|
||||||
'volume_id': volume['id'],
|
'volume_id': volume.id,
|
||||||
'volume_type': vol_type,
|
'volume_type': vol_type,
|
||||||
'migration_policy': migration_policy,
|
'migration_policy': migration_policy,
|
||||||
'quota_reservations': reservations}
|
'quota_reservations': reservations}
|
||||||
|
|
||||||
self.scheduler_rpcapi.retype(context, CONF.volume_topic, volume['id'],
|
self.scheduler_rpcapi.retype(context, CONF.volume_topic, volume.id,
|
||||||
request_spec=request_spec,
|
request_spec=request_spec,
|
||||||
filter_properties={})
|
filter_properties={}, volume=volume)
|
||||||
LOG.info(_LI("Retype volume request issued successfully."),
|
LOG.info(_LI("Retype volume request issued successfully."),
|
||||||
resource=volume)
|
resource=volume)
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ def locked_snapshot_operation(f):
|
|||||||
class VolumeManager(manager.SchedulerDependentManager):
|
class VolumeManager(manager.SchedulerDependentManager):
|
||||||
"""Manages attachable block storage devices."""
|
"""Manages attachable block storage devices."""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.33'
|
RPC_API_VERSION = '1.34'
|
||||||
|
|
||||||
target = messaging.Target(version=RPC_API_VERSION)
|
target = messaging.Target(version=RPC_API_VERSION)
|
||||||
|
|
||||||
@ -2039,22 +2039,28 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
resource=volume)
|
resource=volume)
|
||||||
|
|
||||||
def retype(self, ctxt, volume_id, new_type_id, host,
|
def retype(self, ctxt, volume_id, new_type_id, host,
|
||||||
migration_policy='never', reservations=None):
|
migration_policy='never', reservations=None, volume=None):
|
||||||
|
|
||||||
def _retype_error(context, volume_id, old_reservations,
|
def _retype_error(context, volume, old_reservations,
|
||||||
new_reservations, status_update):
|
new_reservations, status_update):
|
||||||
try:
|
try:
|
||||||
self.db.volume_update(context, volume_id, status_update)
|
volume.update(status_update)
|
||||||
|
volume.save()
|
||||||
finally:
|
finally:
|
||||||
QUOTAS.rollback(context, old_reservations)
|
QUOTAS.rollback(context, old_reservations)
|
||||||
QUOTAS.rollback(context, new_reservations)
|
QUOTAS.rollback(context, new_reservations)
|
||||||
|
|
||||||
context = ctxt.elevated()
|
context = ctxt.elevated()
|
||||||
|
|
||||||
volume_ref = self.db.volume_get(ctxt, volume_id)
|
# FIXME(thangp): Remove this in v2.0 of RPC API.
|
||||||
status_update = {'status': volume_ref['previous_status']}
|
if volume is None:
|
||||||
if context.project_id != volume_ref['project_id']:
|
# For older clients, mimic the old behavior and look up the volume
|
||||||
project_id = volume_ref['project_id']
|
# by its volume_id.
|
||||||
|
volume = objects.Volume.get_by_id(context, volume_id)
|
||||||
|
|
||||||
|
status_update = {'status': volume.previous_status}
|
||||||
|
if context.project_id != volume.project_id:
|
||||||
|
project_id = volume.project_id
|
||||||
else:
|
else:
|
||||||
project_id = context.project_id
|
project_id = context.project_id
|
||||||
|
|
||||||
@ -2069,19 +2075,21 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
# set the volume status to error. Should that be done
|
# set the volume status to error. Should that be done
|
||||||
# here? Setting the volume back to it's original status
|
# here? Setting the volume back to it's original status
|
||||||
# for now.
|
# for now.
|
||||||
self.db.volume_update(context, volume_id, status_update)
|
volume.update(status_update)
|
||||||
|
volume.save()
|
||||||
|
|
||||||
# Get old reservations
|
# Get old reservations
|
||||||
try:
|
try:
|
||||||
reserve_opts = {'volumes': -1, 'gigabytes': -volume_ref['size']}
|
reserve_opts = {'volumes': -1, 'gigabytes': -volume.size}
|
||||||
QUOTAS.add_volume_type_opts(context,
|
QUOTAS.add_volume_type_opts(context,
|
||||||
reserve_opts,
|
reserve_opts,
|
||||||
volume_ref.get('volume_type_id'))
|
volume.volume_type_id)
|
||||||
old_reservations = QUOTAS.reserve(context,
|
old_reservations = QUOTAS.reserve(context,
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
**reserve_opts)
|
**reserve_opts)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.db.volume_update(context, volume_id, status_update)
|
volume.update(status_update)
|
||||||
|
volume.save()
|
||||||
LOG.exception(_LE("Failed to update usages "
|
LOG.exception(_LE("Failed to update usages "
|
||||||
"while retyping volume."))
|
"while retyping volume."))
|
||||||
raise exception.CinderException(_("Failed to get old volume type"
|
raise exception.CinderException(_("Failed to get old volume type"
|
||||||
@ -2093,7 +2101,7 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
# If volume types have the same contents, no need to do anything
|
# If volume types have the same contents, no need to do anything
|
||||||
retyped = False
|
retyped = False
|
||||||
diff, all_equal = volume_types.volume_types_diff(
|
diff, all_equal = volume_types.volume_types_diff(
|
||||||
context, volume_ref.get('volume_type_id'), new_type_id)
|
context, volume.volume_type_id, new_type_id)
|
||||||
if all_equal:
|
if all_equal:
|
||||||
retyped = True
|
retyped = True
|
||||||
|
|
||||||
@ -2113,7 +2121,7 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
try:
|
try:
|
||||||
new_type = volume_types.get_volume_type(context, new_type_id)
|
new_type = volume_types.get_volume_type(context, new_type_id)
|
||||||
ret = self.driver.retype(context,
|
ret = self.driver.retype(context,
|
||||||
volume_ref,
|
volume,
|
||||||
new_type,
|
new_type,
|
||||||
diff,
|
diff,
|
||||||
host)
|
host)
|
||||||
@ -2125,49 +2133,49 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
retyped = ret
|
retyped = ret
|
||||||
|
|
||||||
if retyped:
|
if retyped:
|
||||||
LOG.info(_LI("Volume %s: retyped successfully"), volume_id)
|
LOG.info(_LI("Volume %s: retyped successfully"), volume.id)
|
||||||
except Exception:
|
except Exception:
|
||||||
retyped = False
|
retyped = False
|
||||||
LOG.exception(_LE("Volume %s: driver error when trying to "
|
LOG.exception(_LE("Volume %s: driver error when trying to "
|
||||||
"retype, falling back to generic "
|
"retype, falling back to generic "
|
||||||
"mechanism."), volume_ref['id'])
|
"mechanism."), volume.id)
|
||||||
|
|
||||||
# We could not change the type, so we need to migrate the volume, where
|
# We could not change the type, so we need to migrate the volume, where
|
||||||
# the destination volume will be of the new type
|
# the destination volume will be of the new type
|
||||||
if not retyped:
|
if not retyped:
|
||||||
if migration_policy == 'never':
|
if migration_policy == 'never':
|
||||||
_retype_error(context, volume_id, old_reservations,
|
_retype_error(context, volume, old_reservations,
|
||||||
new_reservations, status_update)
|
new_reservations, status_update)
|
||||||
msg = _("Retype requires migration but is not allowed.")
|
msg = _("Retype requires migration but is not allowed.")
|
||||||
raise exception.VolumeMigrationFailed(reason=msg)
|
raise exception.VolumeMigrationFailed(reason=msg)
|
||||||
|
|
||||||
snaps = objects.SnapshotList.get_all_for_volume(context,
|
snaps = objects.SnapshotList.get_all_for_volume(context,
|
||||||
volume_ref['id'])
|
volume.id)
|
||||||
if snaps:
|
if snaps:
|
||||||
_retype_error(context, volume_id, old_reservations,
|
_retype_error(context, volume, old_reservations,
|
||||||
new_reservations, status_update)
|
new_reservations, status_update)
|
||||||
msg = _("Volume must not have snapshots.")
|
msg = _("Volume must not have snapshots.")
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
|
|
||||||
# Don't allow volume with replicas to be migrated
|
# Don't allow volume with replicas to be migrated
|
||||||
rep_status = volume_ref['replication_status']
|
rep_status = volume.replication_status
|
||||||
if rep_status is not None and rep_status != 'disabled':
|
if rep_status is not None and rep_status != 'disabled':
|
||||||
_retype_error(context, volume_id, old_reservations,
|
_retype_error(context, volume, old_reservations,
|
||||||
new_reservations, status_update)
|
new_reservations, status_update)
|
||||||
msg = _("Volume must not be replicated.")
|
msg = _("Volume must not be replicated.")
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(reason=msg)
|
raise exception.InvalidVolume(reason=msg)
|
||||||
|
|
||||||
self.db.volume_update(context, volume_ref['id'],
|
volume.migration_status = 'starting'
|
||||||
{'migration_status': 'starting'})
|
volume.save()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.migrate_volume(context, volume_id, host,
|
self.migrate_volume(context, volume.id, host,
|
||||||
new_type_id=new_type_id)
|
new_type_id=new_type_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
_retype_error(context, volume_id, old_reservations,
|
_retype_error(context, volume, old_reservations,
|
||||||
new_reservations, status_update)
|
new_reservations, status_update)
|
||||||
else:
|
else:
|
||||||
model_update = {'volume_type_id': new_type_id,
|
model_update = {'volume_type_id': new_type_id,
|
||||||
@ -2175,7 +2183,8 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
'status': status_update['status']}
|
'status': status_update['status']}
|
||||||
if retype_model_update:
|
if retype_model_update:
|
||||||
model_update.update(retype_model_update)
|
model_update.update(retype_model_update)
|
||||||
self.db.volume_update(context, volume_id, model_update)
|
volume.update(model_update)
|
||||||
|
volume.save()
|
||||||
|
|
||||||
if old_reservations:
|
if old_reservations:
|
||||||
QUOTAS.commit(context, old_reservations, project_id=project_id)
|
QUOTAS.commit(context, old_reservations, project_id=project_id)
|
||||||
@ -2183,7 +2192,7 @@ class VolumeManager(manager.SchedulerDependentManager):
|
|||||||
QUOTAS.commit(context, new_reservations, project_id=project_id)
|
QUOTAS.commit(context, new_reservations, project_id=project_id)
|
||||||
self.publish_service_capabilities(context)
|
self.publish_service_capabilities(context)
|
||||||
LOG.info(_LI("Retype volume completed successfully."),
|
LOG.info(_LI("Retype volume completed successfully."),
|
||||||
resource=volume_ref)
|
resource=volume)
|
||||||
|
|
||||||
def manage_existing(self, ctxt, volume_id, ref=None):
|
def manage_existing(self, ctxt, volume_id, ref=None):
|
||||||
try:
|
try:
|
||||||
|
@ -81,6 +81,7 @@ class VolumeAPI(object):
|
|||||||
args. Forwarding CGSnapshot object instead of CGSnapshot_id.
|
args. Forwarding CGSnapshot object instead of CGSnapshot_id.
|
||||||
1.32 - Adds support for sending objects over RPC in create_volume().
|
1.32 - Adds support for sending objects over RPC in create_volume().
|
||||||
1.33 - Adds support for sending objects over RPC in delete_volume().
|
1.33 - Adds support for sending objects over RPC in delete_volume().
|
||||||
|
1.34 - Adds support for sending objects over RPC in retype().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.0'
|
BASE_RPC_API_VERSION = '1.0'
|
||||||
@ -253,14 +254,20 @@ class VolumeAPI(object):
|
|||||||
|
|
||||||
def retype(self, ctxt, volume, new_type_id, dest_host,
|
def retype(self, ctxt, volume, new_type_id, dest_host,
|
||||||
migration_policy='never', reservations=None):
|
migration_policy='never', reservations=None):
|
||||||
new_host = utils.extract_host(volume['host'])
|
|
||||||
cctxt = self.client.prepare(server=new_host, version='1.12')
|
|
||||||
host_p = {'host': dest_host.host,
|
host_p = {'host': dest_host.host,
|
||||||
'capabilities': dest_host.capabilities}
|
'capabilities': dest_host.capabilities}
|
||||||
cctxt.cast(ctxt, 'retype', volume_id=volume['id'],
|
msg_args = {'volume_id': volume.id, 'new_type_id': new_type_id,
|
||||||
new_type_id=new_type_id, host=host_p,
|
'host': host_p, 'migration_policy': migration_policy,
|
||||||
migration_policy=migration_policy,
|
'reservations': reservations}
|
||||||
reservations=reservations)
|
if self.client.can_send_version('1.34'):
|
||||||
|
version = '1.34'
|
||||||
|
msg_args['volume'] = volume
|
||||||
|
else:
|
||||||
|
version = '1.12'
|
||||||
|
|
||||||
|
new_host = utils.extract_host(volume.host)
|
||||||
|
cctxt = self.client.prepare(server=new_host, version=version)
|
||||||
|
cctxt.cast(ctxt, 'retype', **msg_args)
|
||||||
|
|
||||||
def manage_existing(self, ctxt, volume, ref):
|
def manage_existing(self, ctxt, volume, ref):
|
||||||
new_host = utils.extract_host(volume['host'])
|
new_host = utils.extract_host(volume['host'])
|
||||||
|
Loading…
Reference in New Issue
Block a user