Volume status management during migration

This patch proposes a new implementation for the status and
the migration_status for volumes.

* The initial migration_status is None, meaning no migration has been
done; Migration_status 'error' means the previous migration failed.
Migration_status 'success' means the previous migration succeeded.

* If the key 'lock_volume' is set to True from the request, the volume
status should be set to 'maintenance' during migration and goes
back to its original status after migration. Otherwise, if the
key 'lock_volume' is set to False, the volume status will remain the
same as its original status. The default value for lock_volume is
False and it applies to the available volume.

* From the REST's perspectives, all the create, update and delete
actions are not allowed if the volume is in 'maintenance', because
it means the volume is out of service. If it is not in maintenance
mode, the migration can be interrupted if other requests are
issued, e.g. attach. For the termination of migration, another
patch will target to resolve it.

DocImpact
APIImpact The key 'lock_volume' has been added into the API,
telling the volume to change the status to 'maintenance' or not.
The migration_status has been added into results returned
from volume list command, if the request is from an admin.

Change-Id: Ia86421f2d6fce61dcfeb073f8e7b9c9dde517373
Partial-implements: blueprint migration-improvement
changes/12/186312/92
Vincent Hou 8 years ago
parent e7c6d6d0cc
commit 21bc0537e0

@ -14,7 +14,6 @@
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_utils import strutils
import webob
from webob import exc
@ -26,6 +25,7 @@ from cinder import exception
from cinder.i18n import _
from cinder import objects
from cinder import rpc
from cinder import utils
from cinder import volume
@ -127,12 +127,12 @@ class VolumeAdminController(AdminController):
# Perhaps we don't even want any definitions in the abstract
# parent class?
valid_status = AdminController.valid_status.union(
set(['attaching', 'in-use', 'detaching']))
('attaching', 'in-use', 'detaching', 'maintenance'))
valid_attach_status = set(['detached', 'attached', ])
valid_migration_status = set(['migrating', 'error',
'completing', 'none',
'starting', ])
valid_attach_status = ('detached', 'attached',)
valid_migration_status = ('migrating', 'error',
'success', 'completing',
'none', 'starting',)
def _update(self, *args, **kwargs):
db.volume_update(*args, **kwargs)
@ -220,15 +220,11 @@ class VolumeAdminController(AdminController):
try:
host = params['host']
except KeyError:
raise exc.HTTPBadRequest(explanation=_("Must specify 'host'"))
force_host_copy = params.get('force_host_copy', 'False')
try:
force_host_copy = strutils.bool_from_string(force_host_copy,
strict=True)
except ValueError as e:
msg = (_("Invalid value for force_host_copy: '%s'") % e.message)
raise exc.HTTPBadRequest(explanation=msg)
self.volume_api.migrate_volume(context, volume, host, force_host_copy)
raise exc.HTTPBadRequest(explanation=_("Must specify 'host'."))
force_host_copy = utils.get_bool_param('force_host_copy', params)
lock_volume = utils.get_bool_param('lock_volume', params)
self.volume_api.migrate_volume(context, volume, host, force_host_copy,
lock_volume)
return webob.Response(status_int=202)
@wsgi.action('os-migrate_volume_completion')

@ -39,7 +39,7 @@ class ViewBuilder(common.ViewBuilder):
"""Detailed view of a list of volumes."""
return self._list_view(self.detail, request, volumes,
volume_count,
coll_name=self._collection_name + '/detail')
self._collection_name + '/detail')
def summary(self, request, volume):
"""Generic, non-detailed view of an volume."""
@ -54,7 +54,7 @@ class ViewBuilder(common.ViewBuilder):
def detail(self, request, volume):
"""Detailed view of a single volume."""
return {
volume_ref = {
'volume': {
'id': volume.get('id'),
'status': volume.get('status'),
@ -74,9 +74,13 @@ class ViewBuilder(common.ViewBuilder):
'encrypted': self._is_volume_encrypted(volume),
'replication_status': volume.get('replication_status'),
'consistencygroup_id': volume.get('consistencygroup_id'),
'multiattach': volume.get('multiattach')
'multiattach': volume.get('multiattach'),
}
}
if request.environ['cinder.context'].is_admin:
volume_ref['volume']['migration_status'] = (
volume.get('migration_status'))
return volume_ref
def _is_volume_encrypted(self, volume):
"""Determine if volume is encrypted."""

@ -30,12 +30,17 @@ class Controller(wsgi.Controller):
super(Controller, self).__init__()
def _get_metadata(self, context, volume_id):
# The metadata is at the second position of the tuple returned
# from _get_volume_and_metadata
return self._get_volume_and_metadata(context, volume_id)[1]
def _get_volume_and_metadata(self, context, volume_id):
try:
volume = self.volume_api.get(context, volume_id)
meta = self.volume_api.get_volume_metadata(context, volume)
except exception.VolumeNotFound as error:
raise webob.exc.HTTPNotFound(explanation=error.msg)
return meta
return (volume, meta)
@wsgi.serializers(xml=common.MetadataTemplate)
def index(self, req, volume_id):
@ -133,14 +138,13 @@ class Controller(wsgi.Controller):
"""Deletes an existing metadata."""
context = req.environ['cinder.context']
metadata = self._get_metadata(context, volume_id)
volume, metadata = self._get_volume_and_metadata(context, volume_id)
if id not in metadata:
msg = _("Metadata item was not found")
raise webob.exc.HTTPNotFound(explanation=msg)
try:
volume = self.volume_api.get(context, volume_id)
self.volume_api.delete_volume_metadata(
context,
volume,

@ -1285,8 +1285,9 @@ def volume_detached(context, volume_id, attachment_id):
if not remain_attachment:
# Hide status update from user if we're performing volume migration
# or uploading it to image
if (not volume_ref['migration_status'] and
not (volume_ref['status'] == 'uploading')):
if ((not volume_ref['migration_status'] and
not (volume_ref['status'] == 'uploading')) or
volume_ref['migration_status'] in ('success', 'error')):
volume_ref['status'] = 'available'
volume_ref['attach_status'] = 'detached'

@ -146,7 +146,14 @@ class SchedulerManager(manager.Manager):
self._wait_for_scheduler()
def _migrate_volume_set_error(self, context, ex, request_spec):
volume_state = {'volume_state': {'migration_status': None}}
volume = db.volume_get(context, request_spec['volume_id'])
if volume.status == 'maintenance':
previous_status = (
volume.previous_status or 'maintenance')
volume_state = {'volume_state': {'migration_status': 'error',
'status': previous_status}}
else:
volume_state = {'volume_state': {'migration_status': 'error'}}
self._set_volume_state_and_notify('migrate_volume_to_host',
volume_state,
context, ex, request_spec)
@ -183,12 +190,9 @@ class SchedulerManager(manager.Manager):
volume_ref, msg, reservations):
if reservations:
QUOTAS.rollback(context, reservations)
if (volume_ref['volume_attachment'] is None or
len(volume_ref['volume_attachment']) == 0):
orig_status = 'available'
else:
orig_status = 'in-use'
volume_state = {'volume_state': {'status': orig_status}}
previous_status = (
volume_ref.previous_status or volume_ref.status)
volume_state = {'volume_state': {'status': previous_status}}
self._set_volume_state_and_notify('retype', volume_state,
context, ex, request_spec, msg)

@ -15,6 +15,7 @@
import uuid
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import webob
@ -200,26 +201,42 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, req, self.req_id, 'key6')
def test_delete(self):
self.stubs.Set(cinder.db, 'volume_metadata_get',
return_volume_metadata)
self.stubs.Set(cinder.db, 'volume_metadata_delete',
delete_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_delete')
@mock.patch.object(cinder.db, 'volume_metadata_get')
def test_delete(self, metadata_get, metadata_delete):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_volume_metadata
metadata_delete.side_effect = delete_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key2')
req.method = 'DELETE'
res = self.controller.delete(req, self.req_id, 'key2')
self.assertEqual(200, res.status_int)
def test_delete_nonexistent_volume(self):
self.stubs.Set(cinder.db, 'volume_metadata_get',
return_volume_metadata)
self.stubs.Set(cinder.db, 'volume_metadata_delete',
return_volume_nonexistent)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res = self.controller.delete(req, self.req_id, 'key2')
self.assertEqual(200, res.status_int)
get_volume.assert_called_with(fake_context, self.req_id)
@mock.patch.object(cinder.db, 'volume_metadata_delete')
@mock.patch.object(cinder.db, 'volume_metadata_get')
def test_delete_nonexistent_volume(self, metadata_get, metadata_delete):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_volume_metadata
metadata_delete.side_effect = return_volume_nonexistent
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'DELETE'
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req, self.req_id, 'key1')
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req,
self.req_id, 'key1')
get_volume.assert_called_with(fake_context, self.req_id)
def test_delete_meta_not_found(self):
self.stubs.Set(cinder.db, 'volume_metadata_get',
@ -229,31 +246,40 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req, self.req_id, 'key6')
def test_create(self):
self.stubs.Set(cinder.db, 'volume_metadata_get',
return_empty_volume_metadata)
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
req = fakes.HTTPRequest.blank('/v1/volume_metadata')
@mock.patch.object(cinder.db, 'volume_metadata_update')
@mock.patch.object(cinder.db, 'volume_metadata_get')
def test_create(self, metadata_get, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_empty_volume_metadata
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank('/v2/volume_metadata')
req.method = 'POST'
req.content_type = "application/json"
body = {"metadata": {"key1": "value1",
"key2": "value2",
"key3": "value3", }}
req.body = jsonutils.dumps(body)
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(body, res_dict)
def test_create_with_keys_in_uppercase_and_lowercase(self):
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(body, res_dict)
@mock.patch.object(cinder.db, 'volume_metadata_update')
@mock.patch.object(cinder.db, 'volume_metadata_get')
def test_create_with_keys_in_uppercase_and_lowercase(self, metadata_get,
metadata_update):
# if the keys in uppercase_and_lowercase, should return the one
# which server added
self.stubs.Set(cinder.db, 'volume_metadata_get',
return_empty_volume_metadata)
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata_insensitive)
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_empty_volume_metadata
metadata_update.side_effect = return_create_volume_metadata_insensitive
req = fakes.HTTPRequest.blank('/v1/volume_metadata')
req = fakes.HTTPRequest.blank('/v2/volume_metadata')
req.method = 'POST'
req.content_type = "application/json"
body = {"metadata": {"key1": "value1",
@ -267,8 +293,13 @@ class volumeMetaDataTest(test.TestCase):
"key3": "value3",
"KEY4": "value4"}}
req.body = jsonutils.dumps(body)
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(expected, res_dict)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(expected, res_dict)
def test_create_empty_body(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
@ -321,9 +352,11 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.create, req, self.req_id, body)
def test_update_all(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_new_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_all(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_new_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
@ -335,15 +368,24 @@ class volumeMetaDataTest(test.TestCase):
},
}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
def test_update_all_with_keys_in_uppercase_and_lowercase(self):
self.stubs.Set(cinder.db, 'volume_metadata_get',
return_create_volume_metadata)
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_new_volume_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(cinder.db, 'volume_metadata_update')
@mock.patch.object(cinder.db, 'volume_metadata_get')
def test_update_all_with_keys_in_uppercase_and_lowercase(self,
metadata_get,
metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_create_volume_metadata
metadata_update.side_effect = return_new_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
@ -363,21 +405,54 @@ class volumeMetaDataTest(test.TestCase):
},
}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, body)
self.assertEqual(expected, res_dict)
def test_update_all_empty_container(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_empty_container_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, body)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_all_empty_container(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_empty_container_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
expected = {'metadata': {}}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, expected)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_item_value_too_long(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": ("a" * 260)}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
req.environ['cinder.context'] = fake_context
self.assertEqual(expected, res_dict)
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, "key1", body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_all_malformed_container(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
@ -392,18 +467,24 @@ class volumeMetaDataTest(test.TestCase):
self.controller.update_all, req, self.req_id,
expected)
def test_update_all_malformed_data(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_all_malformed_data(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
expected = {'metadata': ['asdf']}
req.body = jsonutils.dumps(expected)
req.environ['cinder.context'] = fake_context
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update_all, req, self.req_id,
expected)
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update_all, req, self.req_id,
expected)
def test_update_all_nonexistent_volume(self):
self.stubs.Set(cinder.db, 'volume_get', return_volume_nonexistent)
@ -416,17 +497,25 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.update_all, req, '100', body)
def test_update_item(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_item(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
res_dict = self.controller.update(req, self.req_id, 'key1', body)
expected = {'meta': {'key1': 'value1'}}
self.assertEqual(expected, res_dict)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update(req, self.req_id, 'key1', body)
expected = {'meta': {'key1': 'value1'}}
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_item_nonexistent_volume(self):
self.stubs.Set(cinder.db, 'volume_get',
@ -452,43 +541,47 @@ class volumeMetaDataTest(test.TestCase):
self.controller.update, req, self.req_id, 'key1',
None)
def test_update_item_empty_key(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_item_empty_key(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"": "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update, req, self.req_id, '', body)
def test_update_item_key_too_long(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update, req, self.req_id,
'', body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_update_item_key_too_long(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {("a" * 260): "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
req.environ['cinder.context'] = fake_context
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, ("a" * 260), body)
def test_update_item_value_too_long(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": ("a" * 260)}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, "key1", body)
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, ("a" * 260), body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_item_too_many_keys(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
@ -516,9 +609,11 @@ class volumeMetaDataTest(test.TestCase):
self.controller.update, req, self.req_id, 'bad',
body)
def test_invalid_metadata_items_on_create(self):
self.stubs.Set(cinder.db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(cinder.db, 'volume_metadata_update')
def test_invalid_metadata_items_on_create(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'POST'
req.headers["content-type"] = "application/json"
@ -526,17 +621,32 @@ class volumeMetaDataTest(test.TestCase):
# test for long key
data = {"metadata": {"a" * 260: "value1"}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
# test for long value
data = {"metadata": {"key": "v" * 260}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
# test for empty key.
data = {"metadata": {"": "value1"}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, self.req_id, data)

@ -15,6 +15,7 @@
import uuid
import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import webob
@ -201,26 +202,61 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, req, self.req_id, 'key6')
def test_delete(self):
self.stubs.Set(db, 'volume_metadata_get',
return_volume_metadata)
self.stubs.Set(db, 'volume_metadata_delete',
delete_volume_metadata)
@mock.patch.object(db, 'volume_metadata_delete')
@mock.patch.object(db, 'volume_metadata_get')
def test_delete(self, metadata_get, metadata_delete):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_volume_metadata
metadata_delete.side_effect = delete_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key2')
req.method = 'DELETE'
res = self.controller.delete(req, self.req_id, 'key2')
self.assertEqual(200, res.status_int)
def test_delete_nonexistent_volume(self):
self.stubs.Set(db, 'volume_metadata_get',
return_volume_metadata)
self.stubs.Set(db, 'volume_metadata_delete',
return_volume_nonexistent)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res = self.controller.delete(req, self.req_id, 'key2')
self.assertEqual(200, res.status_int)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_delete')
@mock.patch.object(db, 'volume_metadata_get')
def test_delete_volume_maintenance(self, metadata_get, metadata_delete):
fake_volume = {'id': self.req_id, 'status': 'maintenance'}
fake_context = mock.Mock()
metadata_get.side_effect = return_volume_metadata
metadata_delete.side_effect = delete_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key2')
req.method = 'DELETE'
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(exception.InvalidVolume,
self.controller.delete, req,
self.req_id, 'key2')
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_delete')
@mock.patch.object(db, 'volume_metadata_get')
def test_delete_nonexistent_volume(self, metadata_get, metadata_delete):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_volume_metadata
metadata_delete.side_effect = return_volume_nonexistent
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'DELETE'
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req, self.req_id, 'key1')
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req,
self.req_id, 'key1')
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_delete_meta_not_found(self):
self.stubs.Set(db, 'volume_metadata_get',
@ -230,12 +266,13 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete, req, self.req_id, 'key6')
def test_create(self):
self.stubs.Set(db, 'volume_metadata_get',
return_empty_volume_metadata)
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(db, 'volume_metadata_update')
@mock.patch.object(db, 'volume_metadata_get')
def test_create(self, metadata_get, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_empty_volume_metadata
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank('/v2/volume_metadata')
req.method = 'POST'
req.content_type = "application/json"
@ -243,16 +280,47 @@ class volumeMetaDataTest(test.TestCase):
"key2": "value2",
"key3": "value3", }}
req.body = jsonutils.dumps(body)
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(body, res_dict)
def test_create_with_keys_in_uppercase_and_lowercase(self):
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(body, res_dict)
@mock.patch.object(db, 'volume_metadata_update')
@mock.patch.object(db, 'volume_metadata_get')
def test_create_volume_maintenance(self, metadata_get, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'maintenance'}
fake_context = mock.Mock()
metadata_get.side_effect = return_empty_volume_metadata
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank('/v2/volume_metadata')
req.method = 'POST'
req.content_type = "application/json"
body = {"metadata": {"key1": "value1",
"key2": "value2",
"key3": "value3", }}
req.body = jsonutils.dumps(body)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(exception.InvalidVolume,
self.controller.create,
req, self.req_id, body)
@mock.patch.object(db, 'volume_metadata_update')
@mock.patch.object(db, 'volume_metadata_get')
def test_create_with_keys_in_uppercase_and_lowercase(self, metadata_get,
metadata_update):
# if the keys in uppercase_and_lowercase, should return the one
# which server added
self.stubs.Set(db, 'volume_metadata_get',
return_empty_volume_metadata)
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata_insensitive)
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_empty_volume_metadata
metadata_update.side_effect = return_create_volume_metadata_insensitive
req = fakes.HTTPRequest.blank('/v2/volume_metadata')
req.method = 'POST'
@ -268,8 +336,13 @@ class volumeMetaDataTest(test.TestCase):
"key3": "value3",
"KEY4": "value4"}}
req.body = jsonutils.dumps(body)
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(expected, res_dict)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.create(req, self.req_id, body)
self.assertEqual(expected, res_dict)
def test_create_empty_body(self):
self.stubs.Set(db, 'volume_metadata_update',
@ -322,9 +395,11 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.create, req, self.req_id, body)
def test_update_all(self):
self.stubs.Set(db, 'volume_metadata_update',
return_new_volume_metadata)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_all(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_new_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
@ -336,15 +411,51 @@ class volumeMetaDataTest(test.TestCase):
},
}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
def test_update_all_with_keys_in_uppercase_and_lowercase(self):
self.stubs.Set(db, 'volume_metadata_get',
return_create_volume_metadata)
self.stubs.Set(db, 'volume_metadata_update',
return_new_volume_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_all_volume_maintenance(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'maintenance'}
fake_context = mock.Mock()
metadata_update.side_effect = return_new_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
expected = {
'metadata': {
'key10': 'value10',
'key99': 'value99',
'KEY20': 'value20',
},
}
req.body = jsonutils.dumps(expected)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(exception.InvalidVolume,
self.controller.update_all, req,
self.req_id, expected)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
@mock.patch.object(db, 'volume_metadata_get')
def test_update_all_with_keys_in_uppercase_and_lowercase(self,
metadata_get,
metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_get.side_effect = return_create_volume_metadata
metadata_update.side_effect = return_new_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
@ -364,21 +475,33 @@ class volumeMetaDataTest(test.TestCase):
},
}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, body)
self.assertEqual(expected, res_dict)
def test_update_all_empty_container(self):
self.stubs.Set(db, 'volume_metadata_update',
return_empty_container_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, body)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_all_empty_container(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_empty_container_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'PUT'
req.content_type = "application/json"
expected = {'metadata': {}}
req.body = jsonutils.dumps(expected)
res_dict = self.controller.update_all(req, self.req_id, expected)
req.environ['cinder.context'] = fake_context
self.assertEqual(expected, res_dict)
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update_all(req, self.req_id, expected)
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_all_malformed_container(self):
self.stubs.Set(db, 'volume_metadata_update',
@ -417,17 +540,46 @@ class volumeMetaDataTest(test.TestCase):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.update_all, req, '100', body)
def test_update_item(self):
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_item(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
res_dict = self.controller.update(req, self.req_id, 'key1', body)
expected = {'meta': {'key1': 'value1'}}
self.assertEqual(expected, res_dict)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
res_dict = self.controller.update(req, self.req_id, 'key1', body)
expected = {'meta': {'key1': 'value1'}}
self.assertEqual(expected, res_dict)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_item_volume_maintenance(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'maintenance'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(exception.InvalidVolume,
self.controller.update, req,
self.req_id, 'key1', body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_item_nonexistent_volume(self):
self.stubs.Set(db, 'volume_get',
@ -453,43 +605,68 @@ class volumeMetaDataTest(test.TestCase):
self.controller.update, req, self.req_id, 'key1',
None)
def test_update_item_empty_key(self):
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_item_empty_key(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"": "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update, req, self.req_id, '', body)
def test_update_item_key_too_long(self):
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.update, req, self.req_id,
'', body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_item_key_too_long(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {("a" * 260): "value1"}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, ("a" * 260), body)
def test_update_item_value_too_long(self):
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, ("a" * 260), body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
@mock.patch.object(db, 'volume_metadata_update')
def test_update_item_value_too_long(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url + '/key1')
req.method = 'PUT'
body = {"meta": {"key1": ("a" * 260)}}
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
req.environ['cinder.context'] = fake_context
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, "key1", body)
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.update,
req, self.req_id, "key1", body)
self.assertFalse(metadata_update.called)
get_volume.assert_called_once_with(fake_context, self.req_id)
def test_update_item_too_many_keys(self):
self.stubs.Set(db, 'volume_metadata_update',
@ -517,9 +694,11 @@ class volumeMetaDataTest(test.TestCase):
self.controller.update, req, self.req_id, 'bad',
body)
def test_invalid_metadata_items_on_create(self):
self.stubs.Set(db, 'volume_metadata_update',
return_create_volume_metadata)
@mock.patch.object(db, 'volume_metadata_update')
def test_invalid_metadata_items_on_create(self, metadata_update):
fake_volume = {'id': self.req_id, 'status': 'available'}
fake_context = mock.Mock()
metadata_update.side_effect = return_create_volume_metadata
req = fakes.HTTPRequest.blank(self.url)
req.method = 'POST'
req.headers["content-type"] = "application/json"
@ -527,17 +706,32 @@ class volumeMetaDataTest(test.TestCase):
# test for long key
data = {"metadata": {"a" * 260: "value1"}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
# test for long value
data = {"metadata": {"key": "v" * 260}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
self.controller.create, req, self.req_id, data)
# test for empty key.
data = {"metadata": {"": "value1"}}
req.body = jsonutils.dumps(data)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, self.req_id, data)
req.environ['cinder.context'] = fake_context
with mock.patch.object(self.controller.volume_api,
'get') as get_volume:
get_volume.return_value = fake_volume
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, self.req_id, data)

@ -161,33 +161,39 @@ class VolumeApiTest(test.TestCase):
metadata=None,
attachments=None,
volume_type=stubs.DEFAULT_VOL_TYPE,
status=stubs.DEFAULT_VOL_STATUS):
status=stubs.DEFAULT_VOL_STATUS,
with_migration_status=False):
metadata = metadata or {}
attachments = attachments or []
return {'volume':
{'attachments': attachments,
'availability_zone': availability_zone,
'bootable': 'false',
'consistencygroup_id': consistencygroup_id,
'created_at': datetime.datetime(1900, 1, 1, 1, 1, 1),
'description': description,
'id': stubs.DEFAULT_VOL_ID,
'links':
[{'href': 'http://localhost/v2/fakeproject/volumes/1',
'rel': 'self'},
{'href': 'http://localhost/fakeproject/volumes/1',
'rel': 'bookmark'}],
'metadata': metadata,
'name': name,
'replication_status': 'disabled',
'multiattach': False,
'size': size,
'snapshot_id': snapshot_id,
'source_volid': source_volid,
'status': status,
'user_id': 'fakeuser',
'volume_type': volume_type,
'encrypted': False}}
volume = {'volume':
{'attachments': attachments,
'availability_zone': availability_zone,
'bootable': 'false',
'consistencygroup_id': consistencygroup_id,
'created_at': datetime.datetime(1900, 1, 1, 1, 1, 1),
'description': description,
'id': stubs.DEFAULT_VOL_ID,
'links':
[{'href': 'http://localhost/v2/fakeproject/volumes/1',
'rel': 'self'},
{'href': 'http://localhost/fakeproject/volumes/1',
'rel': 'bookmark'}],
'metadata': metadata,
'name': name,
'replication_status': 'disabled',
'multiattach': False,
'size': size,
'snapshot_id': snapshot_id,
'source_volid': source_volid,
'status': status,
'user_id': 'fakeuser',
'volume_type': volume_type,
'encrypted': False}}
if with_migration_status:
volume['volume']['migration_status'] = None
return volume
def _expected_volume_api_create_kwargs(self, snapshot=None,
availability_zone=DEFAULT_AZ,
@ -652,7 +658,8 @@ class VolumeApiTest(test.TestCase):
'host_name': None,
'device': '/',
}],
metadata={'key': 'value', 'readonly': 'True'})
metadata={'key': 'value', 'readonly': 'True'},
with_migration_status=True)
self.assertEqual(expected, res_dict)
self.assertEqual(2, len(self.notifier.notifications))
self.assertTrue(mock_validate.called)
@ -758,7 +765,8 @@ class VolumeApiTest(test.TestCase):
'host_name': None,
'id': '1',
'volume_id': stubs.DEFAULT_VOL_ID}],
metadata={'key': 'value', 'readonly': 'True'})
metadata={'key': 'value', 'readonly': 'True'},
with_migration_status=True)
expected = {'volumes': [exp_vol['volume']]}
self.assertEqual(expected, res_dict)
@ -1174,7 +1182,8 @@ class VolumeApiTest(test.TestCase):
'server_id': stubs.FAKE_UUID,
'host_name': None,
'device': '/'}],
metadata={'key': 'value', 'readonly': 'True'})
metadata={'key': 'value', 'readonly': 'True'},
with_migration_status=True)
self.assertEqual(expected, res_dict)
def test_volume_show_with_encrypted_volume(self):

@ -28,6 +28,7 @@ from cinder.scheduler import filter_scheduler
from cinder.scheduler import manager
from cinder import test
from cinder.tests.unit import fake_consistencygroup
from cinder.tests.unit import utils as tests_utils
CONF = cfg.CONF
@ -47,7 +48,7 @@ class SchedulerManagerTestCase(test.TestCase):
self.flags(scheduler_driver=self.driver_cls_name)
self.manager = self.manager_cls()
self.manager._startup_delay = False
self.context = context.RequestContext('fake_user', 'fake_project')
self.context = context.get_admin_context()
self.topic = '