Merge "Implement capability to extend existing volume."

This commit is contained in:
Jenkins
2013-06-19 07:54:24 +00:00
committed by Gerrit Code Review
9 changed files with 132 additions and 1 deletions

View File

@@ -186,6 +186,21 @@ class VolumeActionsController(wsgi.Controller):
raise webob.exc.HTTPBadRequest(explanation=msg)
return {'os-volume_upload_image': response}
@wsgi.action('os-extend')
def _extend(self, req, id, body):
"""Extend size of volume."""
context = req.environ['cinder.context']
volume = self.volume_api.get(context, id)
try:
val = int(body['os-extend']['new_size'])
except ValueError:
msg = _("New volume size must be specified as an integer.")
raise webob.exc.HTTPBadRequest(explanation=msg)
size = body['os-extend']['new_size']
self.volume_api.extend(context, volume, size)
return webob.Response(status_int=202)
class Volume_actions(extensions.ExtensionDescriptor):
"""Enable volume actions

View File

@@ -104,6 +104,21 @@ class VolumeActionsTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_extend_volume(self):
def fake_extend_volume(*args, **kwargs):
return {}
self.stubs.Set(volume.API, 'extend',
fake_extend_volume)
body = {'os-extend': {'new_size': 5}}
req = webob.Request.blank('/v2/fake/volumes/1/action')
req.method = "POST"
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def stub_volume_get(self, context, volume_id):
volume = stubs.stub_volume(volume_id)

View File

@@ -25,6 +25,7 @@
"volume:get_snapshot": [],
"volume:get_all_snapshots": [],
"volume:update_snapshot": [],
"volume:extend": [],
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],

View File

@@ -1180,6 +1180,39 @@ class VolumeTestCase(test.TestCase):
self.assertEqual(snapshots[1].id, u'3')
self.assertEqual(snapshots[2].id, u'4')
def test_extend_volume(self):
"""Test volume can be extended."""
# create a volume and assign to host
volume = self._create_volume(2)
self.volume.create_volume(self.context, volume['id'])
volume['status'] = 'available'
volume['host'] = 'fakehost'
volume_api = cinder.volume.api.API()
# Extend fails when new_size < orig_size
self.assertRaises(exception.InvalidInput,
volume_api.extend,
self.context,
volume,
1)
# Extend fails when new_size == orig_size
self.assertRaises(exception.InvalidInput,
volume_api.extend,
self.context,
volume,
2)
# works when new_size > orig_size
volume_api.extend(self.context, volume, 3)
volume = db.volume_get(context.get_admin_context(), volume['id'])
self.assertEquals(volume['size'], 3)
# clean up
self.volume.delete_volume(self.context, volume['id'])
class DriverTestCase(test.TestCase):
"""Base Test class for Drivers."""

View File

@@ -780,6 +780,55 @@ class API(base.Base):
"image_name": recv_metadata.get('name', None)}
return response
@wrap_check_policy
def extend(self, context, volume, new_size):
if volume['status'] != 'available':
msg = _('Volume status must be available to extend.')
raise exception.InvalidVolume(reason=msg)
size_increase = (int(new_size)) - volume['size']
if size_increase <= 0:
msg = (_("New size for extend must be greater "
"than current size. (current: %(size)s, "
"extended: %(new_size)s)") % {'new_size': new_size,
'size': volume['size']})
raise exception.InvalidInput(reason=msg)
try:
reservations = QUOTAS.reserve(context, gigabytes=+size_increase)
except exception.OverQuota as exc:
overs = exc.kwargs['overs']
usages = exc.kwargs['usages']
quotas = exc.kwargs['quotas']
def _consumed(name):
return (usages[name]['reserved'] + usages[name]['in_use'])
if 'gigabytes' in overs:
msg = _("Quota exceeded for %(s_pid)s, "
"tried to extend volume by "
"%(s_size)sG, (%(d_consumed)dG of %(d_quota)dG "
"already consumed)")
LOG.warn(msg % {'s_pid': context.project_id,
's_size': size_increase,
'd_consumed': _consumed('gigabytes'),
'd_quota': quotas['gigabytes']})
raise exception.VolumeSizeExceedsAvailableQuota()
self.update(context, volume, {'status': 'extending'})
try:
self.volume_rpcapi.extend_volume(context, volume, new_size)
except Exception:
with excutils.save_and_reraise_exception():
try:
self.update(context, volume, {'status': 'error_extending'})
finally:
QUOTAS.rollback(context, reservations)
self.update(context, volume, {'size': new_size})
QUOTAS.commit(context, reservations)
self.update(context, volume, {'status': 'available'})
class HostAPI(base.Base):
def __init__(self):

View File

@@ -198,6 +198,10 @@ class VolumeDriver(object):
"""Clean up after an interrupted image copy."""
pass
def extend_volume(self, volume, new_size):
msg = _("Extend volume not implemented")
raise NotImplementedError(msg)
class ISCSIDriver(VolumeDriver):
"""Executes commands relating to ISCSI volumes.

View File

@@ -108,7 +108,7 @@ MAPPING = {
class VolumeManager(manager.SchedulerDependentManager):
"""Manages attachable block storage devices."""
RPC_API_VERSION = '1.4'
RPC_API_VERSION = '1.6'
def __init__(self, volume_driver=None, service_name=None,
*args, **kwargs):
@@ -756,3 +756,7 @@ class VolumeManager(manager.SchedulerDependentManager):
volume_utils.notify_about_snapshot_usage(
context, snapshot, event_suffix,
extra_usage_info=extra_usage_info, host=self.host)
def extend_volume(self, context, volume_id, new_size):
volume_ref = self.db.volume_get(context, volume_id)
self.driver.extend_volume(volume_ref, new_size)

View File

@@ -40,6 +40,7 @@ class VolumeAPI(cinder.openstack.common.rpc.proxy.RpcProxy):
1.4 - Add request_spec, filter_properties and
allow_reschedule arguments to create_volume().
1.5 - Add accept_transfer
1.6 - Add extend_volume
'''
BASE_RPC_API_VERSION = '1.0'
@@ -137,3 +138,11 @@ class VolumeAPI(cinder.openstack.common.rpc.proxy.RpcProxy):
volume_id=volume['id']),
topic=rpc.queue_get_for(ctxt, self.topic, volume['host']),
version='1.5')
def extend_volume(self, ctxt, volume, new_size):
self.cast(ctxt,
self.make_msg('extend_volume',
volume_id=volume['id'],
new_size=new_size),
topic=rpc.queue_get_for(ctxt, self.topic, volume['host']),
version='1.6')

View File

@@ -10,6 +10,7 @@
"volume:get_volume_metadata": [],
"volume:get_snapshot": [],
"volume:get_all_snapshots": [],
"volume:extend": [],
"volume_extension:types_manage": [["rule:admin_api"]],
"volume_extension:types_extra_specs": [["rule:admin_api"]],