Merge "Implement capability to extend existing volume."
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]],
 | 
			
		||||
 
 | 
			
		||||
@@ -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."""
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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')
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]],
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user