Merge "Move retype quota checks to API"

This commit is contained in:
Jenkins
2015-12-22 01:51:12 +00:00
committed by Gerrit Code Review
6 changed files with 108 additions and 40 deletions

View File

@@ -216,6 +216,7 @@ class SchedulerManager(manager.Manager):
context, ex, request_spec, msg) context, ex, request_spec, msg)
reservations = request_spec.get('quota_reservations') reservations = request_spec.get('quota_reservations')
old_reservations = request_spec.get('old_reservations', None)
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.')
@@ -245,7 +246,9 @@ class SchedulerManager(manager.Manager):
else: else:
volume_rpcapi.VolumeAPI().retype(context, volume, volume_rpcapi.VolumeAPI().retype(context, volume,
new_type['id'], tgt_host, new_type['id'], tgt_host,
migration_policy, reservations) migration_policy,
reservations,
old_reservations)
def manage_existing(self, context, topic, volume_id, def manage_existing(self, context, topic, volume_id,
request_spec, filter_properties=None): request_spec, filter_properties=None):

View File

@@ -4843,6 +4843,14 @@ class VolumeMigrationTestCase(VolumeTestCase):
project_id=project_id, project_id=project_id,
**reserve_opts) **reserve_opts)
old_reserve_opts = {'volumes': -1, 'gigabytes': -volume.size}
QUOTAS.add_volume_type_opts(self.context,
old_reserve_opts,
old_vol_type['id'])
old_reservations = QUOTAS.reserve(self.context,
project_id=project_id,
**old_reserve_opts)
with mock.patch.object(self.volume.driver, 'retype') as _retype,\ with mock.patch.object(self.volume.driver, 'retype') as _retype,\
mock.patch.object(volume_types, 'volume_types_diff') as _diff,\ mock.patch.object(volume_types, 'volume_types_diff') as _diff,\
mock.patch.object(self.volume, 'migrate_volume') as _mig,\ mock.patch.object(self.volume, 'migrate_volume') as _mig,\
@@ -4860,6 +4868,7 @@ class VolumeMigrationTestCase(VolumeTestCase):
vol_type['id'], host_obj, vol_type['id'], host_obj,
migration_policy=policy, migration_policy=policy,
reservations=reservations, reservations=reservations,
old_reservations=old_reservations,
volume=volume) volume=volume)
else: else:
self.assertRaises(exc, self.volume.retype, self.assertRaises(exc, self.volume.retype,
@@ -4867,6 +4876,7 @@ class VolumeMigrationTestCase(VolumeTestCase):
vol_type['id'], host_obj, vol_type['id'], host_obj,
migration_policy=policy, migration_policy=policy,
reservations=reservations, reservations=reservations,
old_reservations=old_reservations,
volume=volume) volume=volume)
# get volume/quota properties # get volume/quota properties

View File

@@ -19,6 +19,7 @@ import copy
import mock import mock
from oslo_config import cfg from oslo_config import cfg
import oslo_messaging as messaging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from cinder import context from cinder import context
@@ -459,27 +460,52 @@ class VolumeRpcAPITestCase(test.TestCase):
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=self.fake_reservations,
version='1.34') old_reservations=self.fake_reservations,
can_send_version.assert_called_once_with('1.34') version='1.37')
can_send_version.assert_called_once_with('1.37')
@mock.patch('oslo_messaging.RPCClient.can_send_version', def test_retype_version_134(self):
return_value=False)
def test_retype_old(self, can_send_version):
class FakeHost(object): class FakeHost(object):
def __init__(self): def __init__(self):
self.host = 'host' self.host = 'host'
self.capabilities = {} self.capabilities = {}
dest_host = FakeHost() dest_host = FakeHost()
self._test_volume_api('retype', with mock.patch.object(messaging.RPCClient,
rpc_method='cast', 'can_send_version',
volume=self.fake_volume_obj, side_effect=[False, True]) as can_send_version:
new_type_id='fake', self._test_volume_api('retype',
dest_host=dest_host, rpc_method='cast',
migration_policy='never', volume=self.fake_volume_obj,
reservations=None, new_type_id='fake',
version='1.12') dest_host=dest_host,
can_send_version.assert_called_once_with('1.34') migration_policy='never',
reservations=self.fake_reservations,
old_reservations=self.fake_reservations,
version='1.34')
can_send_version.assert_any_call('1.37')
can_send_version.assert_any_call('1.34')
def test_retype_version_112(self):
class FakeHost(object):
def __init__(self):
self.host = 'host'
self.capabilities = {}
dest_host = FakeHost()
with mock.patch.object(messaging.RPCClient,
'can_send_version',
side_effect=[False, False]) as can_send_version:
self._test_volume_api('retype',
rpc_method='cast',
volume=self.fake_volume_obj,
new_type_id='fake',
dest_host=dest_host,
migration_policy='never',
reservations=self.fake_reservations,
old_reservations=self.fake_reservations,
version='1.12')
can_send_version.assert_any_call('1.37')
can_send_version.assert_any_call('1.34')
def test_manage_existing(self): def test_manage_existing(self):
self._test_volume_api('manage_existing', self._test_volume_api('manage_existing',

View File

@@ -1506,6 +1506,22 @@ class API(base.Base):
reservations = quota_utils.get_volume_type_reservation(context, volume, reservations = quota_utils.get_volume_type_reservation(context, volume,
vol_type_id) vol_type_id)
# Get old reservations
try:
reserve_opts = {'volumes': -1, 'gigabytes': -volume.size}
QUOTAS.add_volume_type_opts(context,
reserve_opts,
old_vol_type_id)
old_reservations = QUOTAS.reserve(context,
project_id=volume.project_id,
**reserve_opts)
except Exception:
volume.status = volume.previous_status
volume.save()
msg = _("Failed to update quota usage while retyping volume.")
LOG.exception(msg, resource=volume)
raise exception.CinderException(msg)
self.update(context, volume, {'status': 'retyping', self.update(context, volume, {'status': 'retyping',
'previous_status': volume.status}) 'previous_status': volume.status})
@@ -1513,7 +1529,8 @@ class API(base.Base):
'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,
'old_reservations': old_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,

View File

@@ -197,7 +197,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.36' RPC_API_VERSION = '1.37'
target = messaging.Target(version=RPC_API_VERSION) target = messaging.Target(version=RPC_API_VERSION)
@@ -2069,7 +2069,8 @@ 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, volume=None): migration_policy='never', reservations=None,
volume=None, old_reservations=None):
def _retype_error(context, volume, old_reservations, def _retype_error(context, volume, old_reservations,
new_reservations, status_update): new_reservations, status_update):
@@ -2108,22 +2109,26 @@ class VolumeManager(manager.SchedulerDependentManager):
volume.update(status_update) volume.update(status_update)
volume.save() volume.save()
# Get old reservations # If old_reservations has been passed in from the API, we should
try: # skip quotas.
reserve_opts = {'volumes': -1, 'gigabytes': -volume.size} # TODO(ntpttr): These reservation checks are left in to be backwards
QUOTAS.add_volume_type_opts(context, # compatible with Liberty and can be removed in N.
reserve_opts, if not old_reservations:
volume.volume_type_id) # Get old reservations
old_reservations = QUOTAS.reserve(context, try:
project_id=project_id, reserve_opts = {'volumes': -1, 'gigabytes': -volume.size}
**reserve_opts) QUOTAS.add_volume_type_opts(context,
except Exception: reserve_opts,
volume.update(status_update) volume.volume_type_id)
volume.save() old_reservations = QUOTAS.reserve(context,
LOG.exception(_LE("Failed to update usages " project_id=project_id,
"while retyping volume.")) **reserve_opts)
raise exception.CinderException(_("Failed to get old volume type" except Exception:
" quota reservations")) volume.update(status_update)
volume.save()
msg = _("Failed to update quota usage while retyping volume.")
LOG.exception(msg, resource=volume)
raise exception.CinderException(msg)
# We already got the new reservations # We already got the new reservations
new_reservations = reservations new_reservations = reservations

View File

@@ -85,6 +85,8 @@ class VolumeAPI(object):
1.35 - Adds support for sending objects over RPC in extend_volume(). 1.35 - Adds support for sending objects over RPC in extend_volume().
1.36 - Adds support for sending objects over RPC in migrate_volume(), 1.36 - Adds support for sending objects over RPC in migrate_volume(),
migrate_volume_completion(), and update_migrated_volume(). migrate_volume_completion(), and update_migrated_volume().
1.37 - Adds old_reservations parameter to retype to support quota
checks in the API.
""" """
BASE_RPC_API_VERSION = '1.0' BASE_RPC_API_VERSION = '1.0'
@@ -279,17 +281,22 @@ class VolumeAPI(object):
return cctxt.call(ctxt, 'migrate_volume_completion', **msg_args) return cctxt.call(ctxt, 'migrate_volume_completion', **msg_args)
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,
old_reservations=None):
host_p = {'host': dest_host.host, host_p = {'host': dest_host.host,
'capabilities': dest_host.capabilities} 'capabilities': dest_host.capabilities}
msg_args = {'volume_id': volume.id, 'new_type_id': new_type_id, msg_args = {'volume_id': volume.id, 'new_type_id': new_type_id,
'host': host_p, 'migration_policy': migration_policy, 'host': host_p, 'migration_policy': migration_policy,
'reservations': reservations} 'reservations': reservations}
if self.client.can_send_version('1.34'): if self.client.can_send_version('1.37'):
version = '1.34' version = '1.37'
msg_args['volume'] = volume msg_args.update(volume=volume, old_reservations=old_reservations)
else: else:
version = '1.12' 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) new_host = utils.extract_host(volume.host)
cctxt = self.client.prepare(server=new_host, version=version) cctxt = self.client.prepare(server=new_host, version=version)