cinder/cinder/tests/api/contrib/test_admin_actions.py

350 lines
15 KiB
Python

import shutil
import tempfile
import webob
from cinder import context
from cinder import db
from cinder import exception
from cinder.openstack.common import jsonutils
from cinder import test
from cinder.tests.api import fakes
from cinder.tests.api.v2 import stubs
from cinder.volume import api as volume_api
def app():
# no auth, just let environ['cinder.context'] pass through
api = fakes.router.APIRouter()
mapper = fakes.urlmap.URLMap()
mapper['/v2'] = api
return mapper
class AdminActionsTest(test.TestCase):
def setUp(self):
super(AdminActionsTest, self).setUp()
self.tempdir = tempfile.mkdtemp()
self.flags(rpc_backend='cinder.openstack.common.rpc.impl_fake')
self.flags(lock_path=self.tempdir)
self.volume_api = volume_api.API()
def tearDown(self):
shutil.rmtree(self.tempdir)
super(AdminActionsTest, self).tearDown()
def test_reset_status_as_admin(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request status of 'error'
req.body = jsonutils.dumps({'os-reset_status': {'status': 'error'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
volume = db.volume_get(ctx, volume['id'])
# status changed to 'error'
self.assertEquals(volume['status'], 'error')
def test_reset_status_as_non_admin(self):
# current status is 'error'
volume = db.volume_create(context.get_admin_context(),
{'status': 'error'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request changing status to available
req.body = jsonutils.dumps({'os-reset_status': {'status':
'available'}})
# non-admin context
req.environ['cinder.context'] = context.RequestContext('fake', 'fake')
resp = req.get_response(app())
# request is not authorized
self.assertEquals(resp.status_int, 403)
volume = db.volume_get(context.get_admin_context(), volume['id'])
# status is still 'error'
self.assertEquals(volume['status'], 'error')
def test_malformed_reset_status_body(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# malformed request body
req.body = jsonutils.dumps({'os-reset_status': {'x-status': 'bad'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# bad request
self.assertEquals(resp.status_int, 400)
volume = db.volume_get(ctx, volume['id'])
# status is still 'available'
self.assertEquals(volume['status'], 'available')
def test_invalid_status_for_volume(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# 'invalid' is not a valid status
req.body = jsonutils.dumps({'os-reset_status': {'status': 'invalid'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# bad request
self.assertEquals(resp.status_int, 400)
volume = db.volume_get(ctx, volume['id'])
# status is still 'available'
self.assertEquals(volume['status'], 'available')
def test_reset_status_for_missing_volume(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# missing-volume-id
req = webob.Request.blank('/v2/fake/volumes/%s/action' %
'missing-volume-id')
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# malformed request body
req.body = jsonutils.dumps({'os-reset_status': {'status':
'available'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# not found
self.assertEquals(resp.status_int, 404)
self.assertRaises(exception.NotFound, db.volume_get, ctx,
'missing-volume-id')
def test_reset_attached_status(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available',
'attach_status': 'attached'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request update attach_status to detached
body = {'os-reset_status': {'status': 'available',
'attach_status': 'detached'}}
req.body = jsonutils.dumps(body)
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
volume = db.volume_get(ctx, volume['id'])
# attach_status changed to 'detached'
self.assertEquals(volume['attach_status'], 'detached')
# status un-modified
self.assertEquals(volume['status'], 'available')
def test_invalid_reset_attached_status(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available',
'attach_status': 'detached'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# 'invalid' is not a valid attach_status
body = {'os-reset_status': {'status': 'available',
'attach_status': 'invalid'}}
req.body = jsonutils.dumps(body)
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# bad request
self.assertEquals(resp.status_int, 400)
volume = db.volume_get(ctx, volume['id'])
# status and attach_status un-modified
self.assertEquals(volume['status'], 'available')
self.assertEquals(volume['attach_status'], 'detached')
def test_snapshot_reset_status(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# snapshot in 'error_deleting'
volume = db.volume_create(ctx, {})
snapshot = db.snapshot_create(ctx, {'status': 'error_deleting',
'volume_id': volume['id']})
req = webob.Request.blank('/v2/fake/snapshots/%s/action' %
snapshot['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request status of 'error'
req.body = jsonutils.dumps({'os-reset_status': {'status': 'error'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
snapshot = db.snapshot_get(ctx, snapshot['id'])
# status changed to 'error'
self.assertEquals(snapshot['status'], 'error')
def test_invalid_status_for_snapshot(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# snapshot in 'available'
volume = db.volume_create(ctx, {})
snapshot = db.snapshot_create(ctx, {'status': 'available',
'volume_id': volume['id']})
req = webob.Request.blank('/v2/fake/snapshots/%s/action' %
snapshot['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# 'attaching' is not a valid status for snapshots
req.body = jsonutils.dumps({'os-reset_status': {'status':
'attaching'}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 400)
snapshot = db.snapshot_get(ctx, snapshot['id'])
# status is still 'available'
self.assertEquals(snapshot['status'], 'available')
def test_force_delete(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is creating
volume = db.volume_create(ctx, {'status': 'creating'})
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.body = jsonutils.dumps({'os-force_delete': {}})
# attach admin context to request
req.environ['cinder.context'] = ctx
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
# volume is deleted
self.assertRaises(exception.NotFound, db.volume_get, ctx, volume['id'])
def test_force_delete_snapshot(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is creating
volume = db.volume_create(ctx, {'host': 'test'})
snapshot = db.snapshot_create(ctx, {'status': 'creating',
'volume_size': 1,
'volume_id': volume['id']})
path = '/v2/fake/snapshots/%s/action' % snapshot['id']
req = webob.Request.blank(path)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
req.body = jsonutils.dumps({'os-force_delete': {}})
# attach admin context to request
req.environ['cinder.context'] = ctx
# start service to handle rpc.cast for 'delete snapshot'
svc = self.start_service('volume', host='test')
# make request
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
# snapshot is deleted
self.assertRaises(exception.NotFound, db.snapshot_get, ctx,
snapshot['id'])
# cleanup
svc.stop()
def test_force_detach_volume(self):
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available', 'host': 'test',
'provider_location': ''})
# start service to handle rpc messages for attach requests
svc = self.start_service('volume', host='test')
self.volume_api.reserve_volume(ctx, volume)
self.volume_api.initialize_connection(ctx, volume, {})
mountpoint = '/dev/vbd'
self.volume_api.attach(ctx, volume, stubs.FAKE_UUID, mountpoint)
# volume is attached
volume = db.volume_get(ctx, volume['id'])
self.assertEquals(volume['status'], 'in-use')
self.assertEquals(volume['instance_uuid'], stubs.FAKE_UUID)
self.assertEquals(volume['mountpoint'], mountpoint)
self.assertEquals(volume['attach_status'], 'attached')
# build request to force detach
req = webob.Request.blank('/v2/fake/volumes/%s/action' % volume['id'])
req.method = 'POST'
req.headers['content-type'] = 'application/json'
# request status of 'error'
req.body = jsonutils.dumps({'os-force_detach': None})
# attach admin context to request
req.environ['cinder.context'] = ctx
# make request
resp = req.get_response(app())
# request is accepted
self.assertEquals(resp.status_int, 202)
volume = db.volume_get(ctx, volume['id'])
# status changed to 'available'
self.assertEquals(volume['status'], 'available')
self.assertEquals(volume['instance_uuid'], None)
self.assertEquals(volume['mountpoint'], None)
self.assertEquals(volume['attach_status'], 'detached')
# cleanup
svc.stop()
def test_attach_in_use_volume(self):
"""Test that attaching to an in-use volume fails."""
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available', 'host': 'test',
'provider_location': ''})
# start service to handle rpc messages for attach requests
svc = self.start_service('volume', host='test')
self.volume_api.reserve_volume(ctx, volume)
self.volume_api.initialize_connection(ctx, volume, {})
mountpoint = '/dev/vbd'
self.volume_api.attach(ctx, volume, stubs.FAKE_UUID, mountpoint)
self.assertRaises(exception.InvalidVolume,
self.volume_api.attach,
ctx,
volume,
fakes.get_fake_uuid(),
mountpoint)
# cleanup
svc.stop()
def test_attach_attaching_volume_with_different_instance(self):
"""Test that attaching volume reserved for another instance fails."""
# admin context
ctx = context.RequestContext('admin', 'fake', True)
# current status is available
volume = db.volume_create(ctx, {'status': 'available', 'host': 'test',
'provider_location': ''})
# start service to handle rpc messages for attach requests
svc = self.start_service('volume', host='test')
self.volume_api.initialize_connection(ctx, volume, {})
values = {'status': 'attaching',
'instance_uuid': fakes.get_fake_uuid()}
db.volume_update(ctx, volume['id'], values)
mountpoint = '/dev/vbd'
self.assertRaises(exception.InvalidVolume,
self.volume_api.attach,
ctx,
volume,
stubs.FAKE_UUID,
mountpoint)
# cleanup
svc.stop()