Add user messages for some volume snapshot actions
Also adds tests to help keep the message_field module consistent. Co-authored-by: TommyLike <tommylikehu@gmail.com> Co-authored-by: Brian Rosmaita <rosmaita.fossdev@gmail.com> Change-Id: I8a16c9742cadfee1a75a6d738945476a9fc7c19b
This commit is contained in:
parent
2c14226392
commit
46bec90f03
@ -27,6 +27,7 @@ from cinder.i18n import _
|
||||
class Resource(object):
|
||||
|
||||
VOLUME = 'VOLUME'
|
||||
VOLUME_SNAPSHOT = 'VOLUME_SNAPSHOT'
|
||||
|
||||
|
||||
class Action(object):
|
||||
@ -40,6 +41,10 @@ class Action(object):
|
||||
EXTEND_VOLUME = ('007', _('extend volume'))
|
||||
CREATE_VOLUME_FROM_BACKEND = ('008',
|
||||
_('create volume from backend storage'))
|
||||
SNAPSHOT_CREATE = ('009', _('create snapshot'))
|
||||
SNAPSHOT_DELETE = ('010', _('delete snapshot'))
|
||||
SNAPSHOT_UPDATE = ('011', _('update snapshot'))
|
||||
SNAPSHOT_METADATA_UPDATE = ('012', _('update snapshot metadata'))
|
||||
|
||||
ALL = (SCHEDULE_ALLOCATE_VOLUME,
|
||||
ATTACH_VOLUME,
|
||||
@ -48,7 +53,11 @@ class Action(object):
|
||||
COPY_IMAGE_TO_VOLUME,
|
||||
UNMANAGE_VOLUME,
|
||||
EXTEND_VOLUME,
|
||||
CREATE_VOLUME_FROM_BACKEND
|
||||
CREATE_VOLUME_FROM_BACKEND,
|
||||
SNAPSHOT_CREATE,
|
||||
SNAPSHOT_DELETE,
|
||||
SNAPSHOT_UPDATE,
|
||||
SNAPSHOT_METADATA_UPDATE,
|
||||
)
|
||||
|
||||
|
||||
@ -85,6 +94,12 @@ class Detail(object):
|
||||
DRIVER_FAILED_CREATE = (
|
||||
'012',
|
||||
_('Driver failed to create the volume.'))
|
||||
SNAPSHOT_CREATE_ERROR = ('013', _("Snapshot failed to create."))
|
||||
SNAPSHOT_UPDATE_METADATA_FAILED = (
|
||||
'014',
|
||||
_("Volume snapshot update metadata failed."))
|
||||
SNAPSHOT_IS_BUSY = ('015', _("Snapshot is busy."))
|
||||
SNAPSHOT_DELETE_ERROR = ('016', _("Snapshot failed to delete."))
|
||||
|
||||
ALL = (UNKNOWN_ERROR,
|
||||
DRIVER_NOT_INITIALIZED,
|
||||
@ -97,7 +112,12 @@ class Detail(object):
|
||||
NOTIFY_COMPUTE_SERVICE_FAILED,
|
||||
DRIVER_FAILED_EXTEND,
|
||||
SIGNATURE_VERIFICATION_FAILED,
|
||||
DRIVER_FAILED_CREATE)
|
||||
DRIVER_FAILED_CREATE,
|
||||
SNAPSHOT_CREATE_ERROR,
|
||||
SNAPSHOT_UPDATE_METADATA_FAILED,
|
||||
SNAPSHOT_IS_BUSY,
|
||||
SNAPSHOT_DELETE_ERROR,
|
||||
)
|
||||
|
||||
# Exception and detail mappings
|
||||
EXCEPTION_DETAIL_MAPPINGS = {
|
||||
@ -108,6 +128,7 @@ class Detail(object):
|
||||
'BackupLimitExceeded',
|
||||
'SnapshotLimitExceeded'],
|
||||
NOT_ENOUGH_SPACE_FOR_IMAGE: ['ImageTooBig'],
|
||||
SNAPSHOT_IS_BUSY: ['SnapshotIsBusy'],
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,11 +30,32 @@ class MessageFieldTest(test.TestCase):
|
||||
action_ids = [x[0] for x in message_field.Action.ALL]
|
||||
self.assertEqual(len(action_ids), len(set(action_ids)))
|
||||
|
||||
def test_all_action_fields_in_ALL(self):
|
||||
"""Assert that all and only defined fields are in the ALL tuple"""
|
||||
defined_fields = [k for k in message_field.Action.__dict__.keys()
|
||||
if k != 'ALL' and not k.startswith('__')]
|
||||
for d in defined_fields:
|
||||
self.assertIn(getattr(message_field.Action, d),
|
||||
message_field.Action.ALL)
|
||||
self.assertEqual(len(message_field.Action.ALL),
|
||||
len(defined_fields))
|
||||
|
||||
def test_unique_detail_ids(self):
|
||||
"""Assert that no detail_id is duplicated."""
|
||||
detail_ids = [x[0] for x in message_field.Detail.ALL]
|
||||
self.assertEqual(len(detail_ids), len(set(detail_ids)))
|
||||
|
||||
def test_all_detail_fields_in_ALL(self):
|
||||
"""Assert that all and only defined fields are in the ALL tuple"""
|
||||
defined_fields = [k for k in message_field.Detail.__dict__.keys()
|
||||
if k != 'ALL' and not k.startswith('__')
|
||||
and k != 'EXCEPTION_DETAIL_MAPPINGS']
|
||||
for d in defined_fields:
|
||||
self.assertIn(getattr(message_field.Detail, d),
|
||||
message_field.Detail.ALL)
|
||||
self.assertEqual(len(message_field.Detail.ALL),
|
||||
len(defined_fields))
|
||||
|
||||
known_exceptions = [
|
||||
name for name, _ in
|
||||
inspect.getmembers(exception, inspect.isclass)]
|
||||
|
148
cinder/tests/unit/volume/test_volume_manager.py
Normal file
148
cinder/tests/unit/volume/test_volume_manager.py
Normal file
@ -0,0 +1,148 @@
|
||||
# Copyright 2019, Red Hat Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Tests for Volume Manager Code."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder.message import message_field
|
||||
from cinder.tests.unit import volume as base
|
||||
from cinder.volume import manager as vol_manager
|
||||
|
||||
|
||||
class VolumeManagerTestCase(base.BaseVolumeTestCase):
|
||||
|
||||
@mock.patch('cinder.message.api.API.create')
|
||||
@mock.patch('cinder.utils.require_driver_initialized')
|
||||
@mock.patch('cinder.volume.manager.VolumeManager.'
|
||||
'_notify_about_snapshot_usage')
|
||||
def test_create_snapshot_driver_not_initialized_generates_user_message(
|
||||
self, fake_notify, fake_init, fake_msg_create):
|
||||
manager = vol_manager.VolumeManager()
|
||||
|
||||
fake_init.side_effect = exception.CinderException()
|
||||
fake_snapshot = mock.MagicMock(id='22')
|
||||
fake_context = mock.MagicMock()
|
||||
fake_context.elevated.return_value = fake_context
|
||||
|
||||
ex = self.assertRaises(exception.CinderException,
|
||||
manager.create_snapshot,
|
||||
fake_context,
|
||||
fake_snapshot)
|
||||
|
||||
# make sure a user message was generated
|
||||
fake_msg_create.assert_called_once_with(
|
||||
fake_context,
|
||||
action=message_field.Action.SNAPSHOT_CREATE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=fake_snapshot['id'],
|
||||
exception=ex,
|
||||
detail=message_field.Detail.SNAPSHOT_CREATE_ERROR)
|
||||
|
||||
@mock.patch('cinder.message.api.API.create')
|
||||
@mock.patch('cinder.utils.require_driver_initialized')
|
||||
@mock.patch('cinder.volume.manager.VolumeManager.'
|
||||
'_notify_about_snapshot_usage')
|
||||
def test_create_snapshot_metadata_update_failure_generates_user_message(
|
||||
self, fake_notify, fake_init, fake_msg_create):
|
||||
manager = vol_manager.VolumeManager()
|
||||
|
||||
fake_driver = mock.MagicMock()
|
||||
fake_driver.create_snapshot.return_value = False
|
||||
manager.driver = fake_driver
|
||||
|
||||
fake_vol_ref = mock.MagicMock()
|
||||
fake_vol_ref.bootable.return_value = True
|
||||
fake_db = mock.MagicMock()
|
||||
fake_db.volume_get.return_value = fake_vol_ref
|
||||
fake_exp = exception.CinderException()
|
||||
fake_db.volume_glance_metadata_copy_to_snapshot.side_effect = fake_exp
|
||||
manager.db = fake_db
|
||||
|
||||
fake_snapshot = mock.MagicMock(id='86')
|
||||
fake_context = mock.MagicMock()
|
||||
fake_context.elevated.return_value = fake_context
|
||||
|
||||
self.assertRaises(exception.CinderException,
|
||||
manager.create_snapshot,
|
||||
fake_context,
|
||||
fake_snapshot)
|
||||
|
||||
# make sure a user message was generated
|
||||
fake_msg_create.assert_called_once_with(
|
||||
fake_context,
|
||||
action=message_field.Action.SNAPSHOT_CREATE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=fake_snapshot['id'],
|
||||
exception=fake_exp,
|
||||
detail=message_field.Detail.SNAPSHOT_UPDATE_METADATA_FAILED)
|
||||
|
||||
@mock.patch('cinder.message.api.API.create')
|
||||
@mock.patch('cinder.utils.require_driver_initialized')
|
||||
@mock.patch('cinder.volume.manager.VolumeManager.'
|
||||
'_notify_about_snapshot_usage')
|
||||
def test_delete_snapshot_when_busy_generates_user_message(
|
||||
self, fake_notify, fake_init, fake_msg_create):
|
||||
manager = vol_manager.VolumeManager()
|
||||
|
||||
fake_snapshot = mock.MagicMock(id='0', project_id='1')
|
||||
fake_context = mock.MagicMock()
|
||||
fake_context.elevated.return_value = fake_context
|
||||
fake_exp = exception.SnapshotIsBusy(snapshot_name='Fred')
|
||||
fake_init.side_effect = fake_exp
|
||||
|
||||
manager.delete_snapshot(fake_context, fake_snapshot)
|
||||
|
||||
# make sure a user message was generated
|
||||
fake_msg_create.assert_called_once_with(
|
||||
fake_context,
|
||||
action=message_field.Action.SNAPSHOT_DELETE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=fake_snapshot['id'],
|
||||
exception=fake_exp)
|
||||
|
||||
@mock.patch('cinder.message.api.API.create')
|
||||
@mock.patch('cinder.utils.require_driver_initialized')
|
||||
@mock.patch('cinder.volume.manager.VolumeManager.'
|
||||
'_notify_about_snapshot_usage')
|
||||
def test_delete_snapshot_general_exception_generates_user_message(
|
||||
self, fake_notify, fake_init, fake_msg_create):
|
||||
manager = vol_manager.VolumeManager()
|
||||
|
||||
fake_snapshot = mock.MagicMock(id='0', project_id='1')
|
||||
fake_context = mock.MagicMock()
|
||||
fake_context.elevated.return_value = fake_context
|
||||
|
||||
class LocalException(Exception):
|
||||
pass
|
||||
|
||||
fake_exp = LocalException()
|
||||
# yeah, this isn't where it would be coming from in real life,
|
||||
# but it saves mocking out a bunch more stuff
|
||||
fake_init.side_effect = fake_exp
|
||||
|
||||
self.assertRaises(LocalException,
|
||||
manager.delete_snapshot,
|
||||
fake_context,
|
||||
fake_snapshot)
|
||||
|
||||
# make sure a user message was generated
|
||||
fake_msg_create.assert_called_once_with(
|
||||
fake_context,
|
||||
action=message_field.Action.SNAPSHOT_DELETE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=fake_snapshot['id'],
|
||||
exception=fake_exp,
|
||||
detail=message_field.Detail.SNAPSHOT_DELETE_ERROR)
|
@ -1149,10 +1149,17 @@ class VolumeManager(manager.CleanableManager,
|
||||
snapshot.update(model_update)
|
||||
snapshot.save()
|
||||
|
||||
except Exception:
|
||||
except Exception as create_error:
|
||||
with excutils.save_and_reraise_exception():
|
||||
snapshot.status = fields.SnapshotStatus.ERROR
|
||||
snapshot.save()
|
||||
self.message_api.create(
|
||||
context,
|
||||
action=message_field.Action.SNAPSHOT_CREATE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=snapshot['id'],
|
||||
exception=create_error,
|
||||
detail=message_field.Detail.SNAPSHOT_CREATE_ERROR)
|
||||
|
||||
vol_ref = self.db.volume_get(context, snapshot.volume_id)
|
||||
if vol_ref.bootable:
|
||||
@ -1172,6 +1179,14 @@ class VolumeManager(manager.CleanableManager,
|
||||
resource=snapshot)
|
||||
snapshot.status = fields.SnapshotStatus.ERROR
|
||||
snapshot.save()
|
||||
self.message_api.create(
|
||||
context,
|
||||
action=message_field.Action.SNAPSHOT_CREATE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=snapshot['id'],
|
||||
exception=ex,
|
||||
detail=message_field.Detail.SNAPSHOT_UPDATE_METADATA_FAILED
|
||||
)
|
||||
raise exception.MetadataCopyFailure(reason=six.text_type(ex))
|
||||
|
||||
snapshot.status = fields.SnapshotStatus.AVAILABLE
|
||||
@ -1213,16 +1228,29 @@ class VolumeManager(manager.CleanableManager,
|
||||
self.driver.unmanage_snapshot(snapshot)
|
||||
else:
|
||||
self.driver.delete_snapshot(snapshot)
|
||||
except exception.SnapshotIsBusy:
|
||||
except exception.SnapshotIsBusy as busy_error:
|
||||
LOG.error("Delete snapshot failed, due to snapshot busy.",
|
||||
resource=snapshot)
|
||||
snapshot.status = fields.SnapshotStatus.AVAILABLE
|
||||
snapshot.save()
|
||||
self.message_api.create(
|
||||
context,
|
||||
action=message_field.Action.SNAPSHOT_DELETE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=snapshot['id'],
|
||||
exception=busy_error)
|
||||
return
|
||||
except Exception:
|
||||
except Exception as delete_error:
|
||||
with excutils.save_and_reraise_exception():
|
||||
snapshot.status = fields.SnapshotStatus.ERROR_DELETING
|
||||
snapshot.save()
|
||||
self.message_api.create(
|
||||
context,
|
||||
action=message_field.Action.SNAPSHOT_DELETE,
|
||||
resource_type=message_field.Resource.VOLUME_SNAPSHOT,
|
||||
resource_uuid=snapshot['id'],
|
||||
exception=delete_error,
|
||||
detail=message_field.Detail.SNAPSHOT_DELETE_ERROR)
|
||||
|
||||
# Get reservations
|
||||
reservations = None
|
||||
|
Loading…
Reference in New Issue
Block a user