from oslo_log import log as logging
import oslo_messaging as messaging
-from oslo_utils import strutils
import webob
from webob import exc
from cinder.i18n import _
from cinder import objects
from cinder import rpc
+from cinder import utils
from cinder import volume
# 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)
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')
"""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."""
def detail(self, request, volume):
"""Detailed view of a single volume."""
- return {
+ volume_ref = {
'volume': {
'id': volume.get('id'),
'status': volume.get('status'),
'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."""
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):
"""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,
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'
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)
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)
import uuid
+import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import webob
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',
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",
"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',
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"
},
}
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"
},
}
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',
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)
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',
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',
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"
# 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)
import uuid
+import mock
from oslo_config import cfg
from oslo_serialization import jsonutils
import webob
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',
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"
"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'
"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',
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"
},
}
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"
},
}
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',
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',
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',
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"
# 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)
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,
'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)
'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)
'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):
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
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 = 'fake_topic'
self.fake_args = (1, 2, 3)
self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'}
{})
self.assertFalse(_mock_sleep.called)
+ @mock.patch('cinder.db.volume_get')
@mock.patch('cinder.scheduler.driver.Scheduler.host_passes_filters')
@mock.patch('cinder.db.volume_update')
def test_migrate_volume_exception_returns_volume_state(
- self, _mock_volume_update, _mock_host_passes):
+ self, _mock_volume_update, _mock_host_passes,
+ _mock_volume_get):
# Test NoValidHost exception behavior for migrate_volume_to_host.
# Puts the volume in 'error_migrating' state and eats the exception.
- _mock_host_passes.side_effect = exception.NoValidHost(reason="")
- fake_volume_id = 1
+ fake_updates = {'migration_status': 'error'}
+ self._test_migrate_volume_exception_returns_volume_state(
+ _mock_volume_update, _mock_host_passes, _mock_volume_get,
+ 'available', fake_updates)
+
+ @mock.patch('cinder.db.volume_get')
+ @mock.patch('cinder.scheduler.driver.Scheduler.host_passes_filters')
+ @mock.patch('cinder.db.volume_update')
+ def test_migrate_volume_exception_returns_volume_state_maintenance(
+ self, _mock_volume_update, _mock_host_passes,
+ _mock_volume_get):
+ fake_updates = {'status': 'available',
+ 'migration_status': 'error'}
+ self._test_migrate_volume_exception_returns_volume_state(
+ _mock_volume_update, _mock_host_passes, _mock_volume_get,
+ 'maintenance', fake_updates)
+
+ def _test_migrate_volume_exception_returns_volume_state(
+ self, _mock_volume_update, _mock_host_passes,
+ _mock_volume_get, status, fake_updates):
+ volume = tests_utils.create_volume(self.context,
+ status=status,
+ previous_status='available')
+ fake_volume_id = volume.id
topic = 'fake_topic'
request_spec = {'volume_id': fake_volume_id}
+ _mock_host_passes.side_effect = exception.NoValidHost(reason="")
+ _mock_volume_get.return_value = volume
self.manager.migrate_volume_to_host(self.context, topic,
fake_volume_id, 'host', True,
filter_properties={})
_mock_volume_update.assert_called_once_with(self.context,
fake_volume_id,
- {'migration_status': None})
+ fake_updates)
_mock_host_passes.assert_called_once_with(self.context, 'host',
request_spec, {})
_mock_vol_update):
# Test NoValidHost exception behavior for retype.
# Puts the volume in original state and eats the exception.
- fake_volume_id = 1
+ volume = tests_utils.create_volume(self.context,
+ status='retyping',
+ previous_status='in-use')
+ instance_uuid = '12345678-1234-5678-1234-567812345678'
+ volume = tests_utils.attach_volume(self.context, volume['id'],
+ instance_uuid, None, '/dev/fake')
+ fake_volume_id = volume.id
topic = 'fake_topic'
- volume_id = fake_volume_id
request_spec = {'volume_id': fake_volume_id, 'volume_type': {'id': 3},
'migration_policy': 'on-demand'}
- vol_info = {'id': fake_volume_id, 'status': 'in-use',
- 'volume_attachment': [{'id': 'fake_id',
- 'instance_uuid': 'foo',
- 'attached_host': None}]}
-
- _mock_vol_get.return_value = vol_info
+ _mock_vol_get.return_value = volume
_mock_vol_update.return_value = {'status': 'in-use'}
_mock_find_retype_host = mock.Mock(
side_effect=exception.NoValidHost(reason=""))
orig_retype = self.manager.driver.find_retype_host
self.manager.driver.find_retype_host = _mock_find_retype_host
- self.manager.retype(self.context, topic, volume_id,
+ self.manager.retype(self.context, topic, fake_volume_id,
request_spec=request_spec,
filter_properties={})
self._clear_patch = mock.patch('cinder.volume.utils.clear_volume',
autospec=True)
self._clear_patch.start()
+ self.expected_status = 'available'
def tearDown(self):
super(VolumeTestCase, self).tearDown()
False,
FAKE_METADATA_TYPE.fake_type)
+ def test_update_volume_metadata_maintenance(self):
+ """Test update volume metadata with different metadata type."""
+ test_meta1 = {'fake_key1': 'fake_value1'}
+ FAKE_METADATA_TYPE = enum.Enum('METADATA_TYPES', 'fake_type')
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.update_volume_metadata,
+ self.context,
+ volume,
+ test_meta1,
+ False,
+ FAKE_METADATA_TYPE.fake_type)
+
def test_delete_volume_metadata_with_metatype(self):
"""Test delete volume metadata with different metadata type."""
test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
'fake_key1',
FAKE_METADATA_TYPE.fake_type)
+ def test_delete_volume_metadata_maintenance(self):
+ """Test delete volume metadata in maintenance."""
+ FAKE_METADATA_TYPE = enum.Enum('METADATA_TYPES', 'fake_type')
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.delete_volume_metadata,
+ self.context,
+ volume,
+ 'fake_key1',
+ FAKE_METADATA_TYPE.fake_type)
+
+ def test_volume_attach_in_maintenance(self):
+ """Test attach the volume in maintenance."""
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.attach,
+ self.context,
+ volume, None, None, None, None)
+
+ def test_volume_detach_in_maintenance(self):
+ """Test detach the volume in maintenance."""
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.detach,
+ self.context,
+ volume, None)
+
+ def test_initialize_connection_maintenance(self):
+ """Test initialize connection in maintenance."""
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.initialize_connection,
+ self.context,
+ volume,
+ None)
+
+ def test_accept_transfer_maintenance(self):
+ """Test accept transfer in maintenance."""
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.accept_transfer,
+ self.context,
+ volume,
+ None, None)
+
+ def test_copy_volume_to_image_maintenance(self):
+ """Test copy volume to image in maintenance."""
+ test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'}
+ volume = tests_utils.create_volume(self.context, metadata=test_meta1,
+ **self.volume_params)
+ volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.copy_volume_to_image,
+ self.context,
+ volume,
+ test_meta1,
+ force=True)
+
@mock.patch.object(cinder.volume.api.API, 'list_availability_zones')
def test_create_volume_uses_default_availability_zone(self, mock_list_az):
"""Test setting availability_zone correctly during volume create."""
self.assertTrue(volume_get.called)
self.assertTrue(volume_update.called)
- def test_reserve_volume_bad_status(self):
+ def test_reserve_volume_in_attaching(self):
+ self._test_reserve_volume_bad_status('attaching')
+
+ def test_reserve_volume_in_maintenance(self):
+ self._test_reserve_volume_bad_status('maintenance')
+
+ def _test_reserve_volume_bad_status(self, status):
fake_volume = {
'id': self.FAKE_UUID,
- 'status': 'attaching'
+ 'status': status
}
with mock.patch.object(db, 'volume_get') as mock_volume_get:
'fake_name',
'fake_description')
+ def test_create_snapshot_failed_maintenance(self):
+ """Test exception handling when create snapshot in maintenance."""
+ test_volume = tests_utils.create_volume(
+ self.context,
+ **self.volume_params)
+ self.volume.create_volume(self.context, test_volume['id'])
+ test_volume['status'] = 'maintenance'
+ volume_api = cinder.volume.api.API()
+ self.assertRaises(exception.InvalidVolume,
+ volume_api.create_snapshot,
+ self.context,
+ test_volume,
+ 'fake_name',
+ 'fake_description')
+
@mock.patch.object(QUOTAS, 'commit',
side_effect=exception.QuotaError(
'Snapshot quota commit failed!'))
'fake_description')
def test_cannot_delete_volume_in_use(self):
+ """Test volume can't be deleted in in-use status."""
+ self._test_cannot_delete_volume('in-use')
+
+ def test_cannot_delete_volume_maintenance(self):
+ """Test volume can't be deleted in maintenance status."""
+ self._test_cannot_delete_volume('maintenance')
+
+ def _test_cannot_delete_volume(self, status):
"""Test volume can't be deleted in invalid stats."""
# create a volume and assign to host
volume = tests_utils.create_volume(self.context, **self.volume_params)
self.volume.create_volume(self.context, volume['id'])
- volume['status'] = 'in-use'
+ volume['status'] = status
volume['host'] = 'fakehost'
volume_api = cinder.volume.api.API()
volume_api.begin_detaching(self.context, volume)
volume_get.assert_called_once_with(self.context, volume['id'])
+ volume_get.reset_mock()
+ volume['status'] = "maintenance"
+ self.assertRaises(exception.InvalidVolume, volume_api.begin_detaching,
+ self.context, volume)
+ volume_get.assert_called_once_with(self.context, volume['id'])
+
def test_begin_roll_detaching_volume(self):
"""Test begin_detaching and roll_detaching functions."""
vol = db.volume_get(context.get_admin_context(), volume['id'])
self.assertEqual('test update name', vol['display_name'])
+ def test_volume_api_update_maintenance(self):
+ # create a raw vol
+ volume = tests_utils.create_volume(self.context, **self.volume_params)
+ volume['status'] = 'maintenance'
+ # use volume.api to update name
+ volume_api = cinder.volume.api.API()
+ update_dict = {'display_name': 'test update name'}
+ self.assertRaises(exception.InvalidVolume, volume_api.update,
+ self.context, volume, update_dict)
+
def test_volume_api_update_snapshot(self):
# create raw snapshot
volume = tests_utils.create_volume(self.context, **self.volume_params)
# check volume properties
volume = db.volume_get(context.get_admin_context(), volume['id'])
self.assertEqual('newhost', volume['host'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('success', volume['migration_status'])
- def test_migrate_volume_error(self):
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'available'})
+ def _fake_create_volume(self, ctxt, volume, host, req_spec, filters,
+ allow_reschedule=True):
+ return db.volume_update(ctxt, volume['id'],
+ {'status': self.expected_status})
+ def test_migrate_volume_error(self):
with mock.patch.object(self.volume.driver, 'migrate_volume') as \
mock_migrate,\
mock.patch.object(self.volume.driver, 'create_export') as \
host_obj,
False)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
@mock.patch.object(nova.API, 'update_server_volume')
fake_volume = tests_utils.create_volume(self.context, size=1,
host=CONF.host)
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'available'})
-
host_obj = {'host': 'newhost', 'capabilities': {}}
with mock.patch.object(self.volume.driver, 'migrate_volume') as \
mock_migrate_volume,\
mock.patch.object(self.volume.driver, 'copy_volume_data'), \
mock.patch.object(self.volume.driver, 'delete_volume') as \
delete_volume:
- create_volume.side_effect = fake_create_volume
+ create_volume.side_effect = self._fake_create_volume
self.volume.migrate_volume(self.context, fake_volume['id'],
host_obj, True)
volume = db.volume_get(context.get_admin_context(),
fake_volume['id'])
self.assertEqual('newhost', volume['host'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('success', volume['migration_status'])
self.assertFalse(mock_migrate_volume.called)
self.assertFalse(delete_volume.called)
self.assertTrue(rpc_delete_volume.called)
self.assertTrue(update_migrated_volume.called)
def test_migrate_volume_generic_copy_error(self):
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'available'})
-
with mock.patch.object(self.volume.driver, 'migrate_volume'),\
mock.patch.object(volume_rpcapi.VolumeAPI, 'create_volume')\
as mock_create_volume,\
# Exception case at migrate_volume_generic
# source_volume['migration_status'] is 'migrating'
- mock_create_volume.side_effect = fake_create_volume
+ mock_create_volume.side_effect = self._fake_create_volume
mock_copy_volume.side_effect = processutils.ProcessExecutionError
volume = tests_utils.create_volume(self.context, size=0,
host=CONF.host)
host_obj,
True)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
def test_clean_temporary_volume(self):
self.assertIsNone(volume['migration_status'])
def test_migrate_volume_generic_create_volume_error(self):
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'error'})
-
+ self.expected_status = 'error'
with mock.patch.object(self.volume.driver, 'migrate_volume'), \
mock.patch.object(volume_rpcapi.VolumeAPI, 'create_volume') as \
mock_create_volume, \
clean_temporary_volume:
# Exception case at the creation of the new temporary volume
- mock_create_volume.side_effect = fake_create_volume
+ mock_create_volume.side_effect = self._fake_create_volume
volume = tests_utils.create_volume(self.context, size=0,
host=CONF.host)
host_obj = {'host': 'newhost', 'capabilities': {}}
host_obj,
True)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
self.assertTrue(clean_temporary_volume.called)
+ self.expected_status = 'available'
def test_migrate_volume_generic_timeout_error(self):
CONF.set_override("migration_create_volume_timeout_secs", 2)
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'creating'})
-
with mock.patch.object(self.volume.driver, 'migrate_volume'), \
mock.patch.object(volume_rpcapi.VolumeAPI, 'create_volume') as \
mock_create_volume, \
mock.patch.object(time, 'sleep'):
# Exception case at the timeout of the volume creation
- mock_create_volume.side_effect = fake_create_volume
+ self.expected_status = 'creating'
+ mock_create_volume.side_effect = self._fake_create_volume
volume = tests_utils.create_volume(self.context, size=0,
host=CONF.host)
host_obj = {'host': 'newhost', 'capabilities': {}}
host_obj,
True)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
self.assertTrue(clean_temporary_volume.called)
+ self.expected_status = 'available'
def test_migrate_volume_generic_create_export_error(self):
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'available'})
-
with mock.patch.object(self.volume.driver, 'migrate_volume'),\
mock.patch.object(volume_rpcapi.VolumeAPI, 'create_volume')\
as mock_create_volume,\
mock_create_export:
# Exception case at create_export
- mock_create_volume.side_effect = fake_create_volume
+ mock_create_volume.side_effect = self._fake_create_volume
mock_copy_volume.side_effect = processutils.ProcessExecutionError
mock_create_export.side_effect = processutils.ProcessExecutionError
volume = tests_utils.create_volume(self.context, size=0,
host_obj,
True)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
def test_migrate_volume_generic_migrate_volume_completion_error(self):
- def fake_create_volume(ctxt, volume, host, req_spec, filters,
- allow_reschedule=True):
- db.volume_update(ctxt, volume['id'],
- {'status': 'available'})
-
def fake_migrate_volume_completion(ctxt, volume_id, new_volume_id,
error=False):
db.volume_update(ctxt, volume['id'],
# Exception case at delete_volume
# source_volume['migration_status'] is 'completing'
- mock_create_volume.side_effect = fake_create_volume
+ mock_create_volume.side_effect = self._fake_create_volume
mock_migrate_compl.side_effect = fake_migrate_volume_completion
volume = tests_utils.create_volume(self.context, size=0,
host=CONF.host)
host_obj,
True)
volume = db.volume_get(context.get_admin_context(), volume['id'])
- self.assertIsNone(volume['migration_status'])
+ self.assertEqual('error', volume['migration_status'])
self.assertEqual('available', volume['status'])
def _test_migrate_volume_completion(self, status='available',
instance_uuid=None, attached_host=None,
- retyping=False):
+ retyping=False,
+ previous_status='available'):
def fake_attach_volume(ctxt, volume, instance_uuid, host_name,
mountpoint, mode):
tests_utils.attach_volume(ctxt, volume['id'],
old_volume = tests_utils.create_volume(self.context, size=0,
host=CONF.host,
status=initial_status,
- migration_status='migrating')
+ migration_status='migrating',
+ previous_status=previous_status)
attachment_id = None
if status == 'in-use':
vol = tests_utils.attach_volume(self.context, old_volume['id'],
'in-use',
'83c969d5-065e-4c9c-907d-5394bc2e98e2',
'some-host',
- retyping=True)
+ retyping=True,
+ previous_status='in-use')
def test_migrate_volume_completion_migrate_available(self):
self._test_migrate_volume_completion()
self._test_migrate_volume_completion(
'in-use',
'83c969d5-065e-4c9c-907d-5394bc2e98e2',
- 'some-host')
+ 'some-host',
+ retyping=False,
+ previous_status='in-use')
def test_retype_setup_fail_volume_is_available(self):
"""Verify volume is still available if retype prepare failed."""
host=CONF.host, status='retyping',
volume_type_id=old_vol_type['id'],
replication_status=rep_status)
+ volume['previous_status'] = 'available'
if snap:
self._create_snapshot(volume['id'], size=volume['size'])
if driver or diff_equal:
project_id=project_id,
**reserve_opts)
- with mock.patch.object(self.volume.driver, 'retype') as _retype:
- with mock.patch.object(volume_types, 'volume_types_diff') as _diff:
- with mock.patch.object(self.volume, 'migrate_volume') as _mig:
- _retype.return_value = driver
- _diff.return_value = ({}, diff_equal)
- if migrate_exc:
- _mig.side_effect = KeyError
- else:
- _mig.return_value = True
-
- if not exc:
- self.volume.retype(self.context, volume['id'],
- vol_type['id'], host_obj,
- migration_policy=policy,
- reservations=reservations)
- else:
- self.assertRaises(exc, self.volume.retype,
- self.context, volume['id'],
- vol_type['id'], host_obj,
- migration_policy=policy,
- reservations=reservations)
+ with mock.patch.object(self.volume.driver, 'retype') as _retype,\
+ mock.patch.object(volume_types, 'volume_types_diff') as _diff,\
+ mock.patch.object(self.volume, 'migrate_volume') as _mig,\
+ mock.patch.object(db, 'volume_get') as get_volume:
+ get_volume.return_value = volume
+ _retype.return_value = driver
+ _diff.return_value = ({}, diff_equal)
+ if migrate_exc:
+ _mig.side_effect = KeyError
+ else:
+ _mig.return_value = True
+
+ if not exc:
+ self.volume.retype(self.context, volume['id'],
+ vol_type['id'], host_obj,
+ migration_policy=policy,
+ reservations=reservations)
+ else:
+ self.assertRaises(exc, self.volume.retype,
+ self.context, volume['id'],
+ vol_type['id'], host_obj,
+ migration_policy=policy,
+ reservations=reservations)
+ get_volume.assert_called_once_with(self.context, volume['id'])
# get volume/quota properties
volume = db.volume_get(elevated, volume['id'])
from oslo_log import log as logging
from oslo_utils import encodeutils
from oslo_utils import importutils
+from oslo_utils import strutils
from oslo_utils import timeutils
import retrying
import six
return (devpath, st)
+def get_bool_param(param_string, params):
+ param = params.get(param_string, False)
+ if not is_valid_boolstr(param):
+ msg = _('Value %(param)s for %(param_string)s is not a '
+ 'boolean.') % {'param': param, 'param_string': param_string}
+ raise exception.InvalidParameterValue(err=msg)
+
+ return strutils.bool_from_string(param, strict=True)
+
+
def get_blkdev_major_minor(path, lookup_for_file=True):
"""Get 'major:minor' number of block device.
safe = True
return safe
+ def _is_volume_migrating(self, volume):
+ # The migration status 'none' means no migration has ever been done
+ # before. The migration status 'error' means the previous migration
+ # failed. The migration status 'success' means the previous migration
+ # succeeded. The migration status 'deleting' means the source volume
+ # fails to delete after a migration.
+ # All of the statuses above means the volume is not in the process
+ # of a migration.
+ return volume['migration_status'] not in (None, 'deleting',
+ 'error', 'success')
+
def create(self, context, size, name, description, snapshot=None,
image_id=None, volume_type=None, metadata=None,
availability_zone=None, source_volume=None,
'vol_status': volume['status']})
raise exception.InvalidVolume(reason=msg)
- if volume['migration_status'] not in (None, 'deleting'):
+ if self._is_volume_migrating(volume):
# Volume is migrating, wait until done
LOG.info(_LI('Unable to delete volume: %s, '
'volume is currently migrating.'), volume['id'])
@wrap_check_policy
def update(self, context, volume, fields):
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI("Unable to update volume, "
+ "because it is in maintenance."), resource=volume)
+ msg = _("The volume cannot be updated during maintenance.")
+ raise exception.InvalidVolume(reason=msg)
+
vref = self.db.volume_update(context, volume['id'], fields)
LOG.info(_LI("Volume updated successfully."), resource=vref)
# If we are in the middle of a volume migration, we don't want the user
# to see that the volume is 'detaching'. Having 'migration_status' set
# will have the same effect internally.
- if volume['migration_status']:
+ if self._is_volume_migrating(volume):
return
if (volume['status'] != 'in-use' or
@wrap_check_policy
def attach(self, context, volume, instance_uuid, host_name,
mountpoint, mode):
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to attach volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The volume cannot be attached in maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
volume_metadata = self.get_volume_admin_metadata(context.elevated(),
volume)
if 'readonly' not in volume_metadata:
@wrap_check_policy
def detach(self, context, volume, attachment_id):
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to detach volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The volume cannot be detached in maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
detach_results = self.volume_rpcapi.detach_volume(context, volume,
attachment_id)
LOG.info(_LI("Detach volume completed successfully."),
@wrap_check_policy
def initialize_connection(self, context, volume, connector):
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to initialize the connection for '
+ 'volume, because it is in '
+ 'maintenance.'), resource=volume)
+ msg = _("The volume connection cannot be initialized in "
+ "maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
init_results = self.volume_rpcapi.initialize_connection(context,
volume,
connector)
@wrap_check_policy
def accept_transfer(self, context, volume, new_user, new_project):
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to accept transfer for volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The volume cannot accept transfer in maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
results = self.volume_rpcapi.accept_transfer(context,
volume,
new_user,
cgsnapshot_id):
check_policy(context, 'create_snapshot', volume)
- if volume['migration_status'] is not None:
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to create the snapshot for volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The snapshot cannot be created when the volume is in "
+ "maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
+ if self._is_volume_migrating(volume):
# Volume is migrating, wait until done
msg = _("Snapshot cannot be created while volume is migrating.")
raise exception.InvalidVolume(reason=msg)
def _create_snapshot_in_db_validate(self, context, volume, force):
check_policy(context, 'create_snapshot', volume)
- if volume['migration_status'] is not None:
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to create the snapshot for volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The snapshot cannot be created when the volume is in "
+ "maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
+ if self._is_volume_migrating(volume):
# Volume is migrating, wait until done
msg = _("Snapshot cannot be created while volume is migrating.")
raise exception.InvalidVolume(reason=msg)
def delete_volume_metadata(self, context, volume,
key, meta_type=common.METADATA_TYPES.user):
"""Delete the given metadata item from a volume."""
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to delete the volume metadata, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The volume metadata cannot be deleted when the volume "
+ "is in maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
self.db.volume_metadata_delete(context, volume['id'], key, meta_type)
LOG.info(_LI("Delete volume metadata completed successfully."),
resource=volume)
`metadata` argument will be deleted.
"""
+ if volume['status'] == 'maintenance':
+ LOG.info(_LI('Unable to update the metadata for volume, '
+ 'because it is in maintenance.'), resource=volume)
+ msg = _("The volume metadata cannot be updated when the volume "
+ "is in maintenance mode.")
+ raise exception.InvalidVolume(reason=msg)
if delete:
_metadata = metadata
else:
resource=volume)
@wrap_check_policy
- def migrate_volume(self, context, volume, host, force_host_copy):
+ def migrate_volume(self, context, volume, host, force_host_copy,
+ lock_volume):
"""Migrate the volume to the specified host."""
- # We only handle "available" volumes for now
if volume['status'] not in ['available', 'in-use']:
msg = _('Volume %(vol_id)s status must be available or in-use, '
'but current status is: '
LOG.error(msg)
raise exception.InvalidVolume(reason=msg)
- # Make sure volume is not part of a migration
- if volume['migration_status'] is not None:
+ # Make sure volume is not part of a migration.
+ if self._is_volume_migrating(volume):
msg = _("Volume %s is already part of an active "
"migration.") % volume['id']
LOG.error(msg)
LOG.error(msg)
raise exception.InvalidHost(reason=msg)
- self.update(context, volume, {'migration_status': 'starting'})
+ # When the migration of an available volume starts, both the status
+ # and the migration status of the volume will be changed.
+ # If the admin sets lock_volume flag to True, the volume
+ # status is changed to 'maintenance', telling users
+ # that this volume is in maintenance mode, and no action is allowed
+ # on this volume, e.g. attach, detach, retype, migrate, etc.
+ updates = {'migration_status': 'starting',
+ 'previous_status': volume['status']}
+ if lock_volume and volume['status'] == 'available':
+ updates['status'] = 'maintenance'
+ self.update(context, volume, updates)
# Call the scheduler to ensure that the host exists and that it can
# accept the volume
LOG.error(msg)
raise exception.InvalidVolume(reason=msg)
- if volume['migration_status'] is not None:
+ if self._is_volume_migrating(volume):
msg = (_("Volume %s is already part of an active migration.")
% volume['id'])
LOG.error(msg)
reservations = quota_utils.get_volume_type_reservation(context, volume,
vol_type_id)
- self.update(context, volume, {'status': 'retyping'})
+ self.update(context, volume, {'status': 'retyping',
+ 'previous_status': volume['status']})
request_spec = {'volume_properties': volume,
'volume_id': volume['id'],
raise exception.InvalidVolume(
reason=_("volume is not local to this node"))
- is_migrating = volume_ref['migration_status'] is not None
+ # The status 'deleting' is not included, because it only applies to
+ # the source volume to be deleted after a migration. No quota
+ # needs to be handled for it.
+ is_migrating = volume_ref['migration_status'] not in (None, 'error',
+ 'success')
is_migrating_dest = (is_migrating and
volume_ref['migration_status'].startswith(
'target:'))
host_name_sanitized,
mountpoint,
mode)
- if volume['migration_status']:
- self.db.volume_update(context, volume_id,
- {'migration_status': None})
self._notify_about_volume_usage(context, volume, "attach.end")
LOG.info(_LI("Attach volume completed successfully."),
resource=volume)
self._clean_temporary_volume(ctxt, volume['id'],
new_volume['id'])
- def _get_original_status(self, volume):
- attachments = volume['volume_attachment']
- if not attachments:
- return 'available'
- else:
- return 'in-use'
-
def _clean_temporary_volume(self, ctxt, volume_id, new_volume_id,
clean_db_only=False):
volume = self.db.volume_get(ctxt, volume_id)
new_volume = self.db.volume_get(ctxt, new_volume_id)
rpcapi = volume_rpcapi.VolumeAPI()
- orig_volume_status = self._get_original_status(volume)
+ orig_volume_status = volume['previous_status']
if error:
LOG.info(_LI("migrate_volume_completion is cleaning up an error "
"for volume %(vol1)s (temporary volume %(vol2)s"),
{'vol1': volume['id'], 'vol2': new_volume['id']})
rpcapi.delete_volume(ctxt, new_volume)
- updates = {'migration_status': None, 'status': orig_volume_status}
+ updates = {'migration_status': 'error',
+ 'status': orig_volume_status}
self.db.volume_update(ctxt, volume_id, updates)
return volume_id
# asynchronously delete the destination id
__, updated_new = self.db.finish_volume_migration(
ctxt, volume_id, new_volume_id)
- if orig_volume_status == 'in-use':
- updates = {'migration_status': 'completing',
- 'status': orig_volume_status}
- else:
- updates = {'migration_status': None}
- self.db.volume_update(ctxt, volume_id, updates)
+ updates = {'status': orig_volume_status,
+ 'previous_status': volume['status'],
+ 'migration_status': 'success'}
if orig_volume_status == 'in-use':
attachments = volume['volume_attachment']
attachment['attached_host'],
attachment['mountpoint'],
'rw')
+ self.db.volume_update(ctxt, volume_id, updates)
# Asynchronous deletion of the source volume in the back-end (now
# pointed by the target volume id)
LOG.error(_LE('Failed to request async delete of migration source '
'vol %(vol)s: %(err)s'),
{'vol': volume_id, 'err': ex})
+ updates = {'migration_status': 'success',
+ 'status': orig_volume_status,
+ 'previous_status': volume['status']}
LOG.info(_LI("Complete-Migrate volume completed successfully."),
resource=volume)
moved = False
status_update = None
- if volume_ref['status'] == 'retyping':
- status_update = {'status': self._get_original_status(volume_ref)}
+ if volume_ref['status'] in ('retyping', 'maintenance'):
+ status_update = {'status': volume_ref['previous_status']}
self.db.volume_update(ctxt, volume_ref['id'],
{'migration_status': 'migrating'})
host)
if moved:
updates = {'host': host['host'],
- 'migration_status': None}
+ 'migration_status': 'success',
+ 'previous_status': volume_ref['status']}
if status_update:
updates.update(status_update)
if model_update:
updates)
except Exception:
with excutils.save_and_reraise_exception():
- updates = {'migration_status': None}
+ updates = {'migration_status': 'error'}
if status_update:
updates.update(status_update)
self.db.volume_update(ctxt, volume_ref['id'], updates)
new_type_id)
except Exception:
with excutils.save_and_reraise_exception():
- updates = {'migration_status': None}
+ updates = {'migration_status': 'error'}
if status_update:
updates.update(status_update)
self.db.volume_update(ctxt, volume_ref['id'], updates)
context = ctxt.elevated()
volume_ref = self.db.volume_get(ctxt, volume_id)
- status_update = {'status': self._get_original_status(volume_ref)}
+ status_update = {'status': volume_ref['previous_status']}
if context.project_id != volume_ref['project_id']:
project_id = volume_ref['project_id']
else: