diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index bd82ae6850f..d56376136fa 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -407,8 +407,10 @@ def _service_query(context, session=None, read_deleted='no', host=None, @require_admin_context def service_destroy(context, service_id): query = _service_query(context, id=service_id) - if not query.update(models.Service.delete_values()): + updated_values = models.Service.delete_values() + if not query.update(updated_values): raise exception.ServiceNotFound(service_id=service_id) + return updated_values @require_admin_context @@ -614,7 +616,7 @@ def quota_destroy(context, project_id, resource): session = get_session() with session.begin(): quota_ref = _quota_get(context, project_id, resource, session=session) - quota_ref.delete(session=session) + return quota_ref.delete(session=session) ################### @@ -717,7 +719,7 @@ def quota_class_destroy(context, class_name, resource): with session.begin(): quota_class_ref = _quota_class_get(context, class_name, resource, session=session) - quota_class_ref.delete(session=session) + return quota_class_ref.delete(session=session) @require_admin_context @@ -1287,13 +1289,14 @@ def volume_destroy(context, volume_id): session = get_session() now = timeutils.utcnow() with session.begin(): + updated_values = {'status': 'deleted', + 'deleted': True, + 'deleted_at': now, + 'updated_at': literal_column('updated_at'), + 'migration_status': None} model_query(context, models.Volume, session=session).\ filter_by(id=volume_id).\ - update({'status': 'deleted', - 'deleted': True, - 'deleted_at': now, - 'updated_at': literal_column('updated_at'), - 'migration_status': None}) + update(updated_values) model_query(context, models.VolumeMetadata, session=session).\ filter_by(volume_id=volume_id).\ update({'deleted': True, @@ -1309,6 +1312,8 @@ def volume_destroy(context, volume_id): update({'deleted': True, 'deleted_at': now, 'updated_at': literal_column('updated_at')}) + del updated_values['updated_at'] + return updated_values @require_admin_context @@ -2208,19 +2213,23 @@ def snapshot_create(context, values): @require_admin_context @_retry_on_deadlock def snapshot_destroy(context, snapshot_id): + utcnow = timeutils.utcnow() session = get_session() with session.begin(): + updated_values = {'status': 'deleted', + 'deleted': True, + 'deleted_at': utcnow, + 'updated_at': literal_column('updated_at')} model_query(context, models.Snapshot, session=session).\ filter_by(id=snapshot_id).\ - update({'status': 'deleted', - 'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + update(updated_values) model_query(context, models.SnapshotMetadata, session=session).\ filter_by(snapshot_id=snapshot_id).\ update({'deleted': True, - 'deleted_at': timeutils.utcnow(), + 'deleted_at': utcnow, 'updated_at': literal_column('updated_at')}) + del updated_values['updated_at'] + return updated_values @require_context @@ -2928,6 +2937,7 @@ def volume_type_qos_specs_get(context, type_id): @require_admin_context @_retry_on_deadlock def volume_type_destroy(context, id): + utcnow = timeutils.utcnow() session = get_session() with session.begin(): _volume_type_get(context, id, session) @@ -2937,19 +2947,22 @@ def volume_type_destroy(context, id): LOG.error(_LE('VolumeType %s deletion failed, ' 'VolumeType in use.'), id) raise exception.VolumeTypeInUse(volume_type_id=id) + updated_values = {'deleted': True, + 'deleted_at': utcnow, + 'updated_at': literal_column('updated_at')} model_query(context, models.VolumeTypes, session=session).\ filter_by(id=id).\ - update({'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + update(updated_values) model_query(context, models.VolumeTypeExtraSpecs, session=session).\ filter_by(volume_type_id=id).\ update({'deleted': True, - 'deleted_at': timeutils.utcnow(), + 'deleted_at': utcnow, 'updated_at': literal_column('updated_at')}) model_query(context, models.VolumeTypeProjects, session=session, read_deleted="int_no").filter_by( volume_type_id=id).soft_delete(synchronize_session=False) + del updated_values['updated_at'] + return updated_values @require_context @@ -3357,13 +3370,16 @@ def qos_specs_delete(context, qos_specs_id): session = get_session() with session.begin(): _qos_specs_get_ref(context, qos_specs_id, session) + updated_values = {'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')} session.query(models.QualityOfServiceSpecs).\ filter(or_(models.QualityOfServiceSpecs.id == qos_specs_id, models.QualityOfServiceSpecs.specs_id == qos_specs_id)).\ - update({'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + update(updated_values) + del updated_values['updated_at'] + return updated_values @require_admin_context @@ -3868,12 +3884,15 @@ def backup_update(context, backup_id, values): @require_admin_context def backup_destroy(context, backup_id): + updated_values = {'status': fields.BackupStatus.DELETED, + 'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')} model_query(context, models.Backup).\ filter_by(id=backup_id).\ - update({'status': fields.BackupStatus.DELETED, - 'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + update(updated_values) + del updated_values['updated_at'] + return updated_values ############################### @@ -3959,6 +3978,7 @@ def transfer_create(context, values): @require_context @_retry_on_deadlock def transfer_destroy(context, transfer_id): + utcnow = timeutils.utcnow() session = get_session() with session.begin(): transfer_ref = _transfer_get(context, @@ -3976,11 +3996,16 @@ def transfer_destroy(context, transfer_id): volume_ref['status'] = 'available' volume_ref.update(volume_ref) volume_ref.save(session=session) + updated_values = {'deleted': True, + 'deleted_at': utcnow, + 'updated_at': literal_column('updated_at')} model_query(context, models.Transfer, session=session).\ filter_by(id=transfer_id).\ update({'deleted': True, - 'deleted_at': timeutils.utcnow(), + 'deleted_at': utcnow, 'updated_at': literal_column('updated_at')}) + del updated_values['updated_at'] + return updated_values @require_context @@ -4204,14 +4229,21 @@ def consistencygroup_update(context, consistencygroup_id, values): @require_admin_context def consistencygroup_destroy(context, consistencygroup_id): + utcnow = timeutils.utcnow() session = get_session() with session.begin(): + updated_values = {'status': fields.ConsistencyGroupStatus.DELETED, + 'deleted': True, + 'deleted_at': utcnow, + 'updated_at': literal_column('updated_at')} model_query(context, models.ConsistencyGroup, session=session).\ filter_by(id=consistencygroup_id).\ update({'status': fields.ConsistencyGroupStatus.DELETED, 'deleted': True, - 'deleted_at': timeutils.utcnow(), + 'deleted_at': utcnow, 'updated_at': literal_column('updated_at')}) + del updated_values['updated_at'] + return updated_values def cg_has_cgsnapshot_filter(): @@ -4406,12 +4438,15 @@ def cgsnapshot_update(context, cgsnapshot_id, values): def cgsnapshot_destroy(context, cgsnapshot_id): session = get_session() with session.begin(): + updated_values = {'status': 'deleted', + 'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')} model_query(context, models.Cgsnapshot, session=session).\ filter_by(id=cgsnapshot_id).\ - update({'status': 'deleted', - 'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + update(updated_values) + del updated_values['updated_at'] + return updated_values def cgsnapshot_creating_from_src(): @@ -4587,11 +4622,14 @@ def message_destroy(context, message): session = get_session() now = timeutils.utcnow() with session.begin(): + updated_values = {'deleted': True, + 'deleted_at': now, + 'updated_at': literal_column('updated_at')} (model_query(context, models.Message, session=session). filter_by(id=message.get('id')). - update({'deleted': True, - 'deleted_at': now, - 'updated_at': literal_column('updated_at')})) + update(updated_values)) + del updated_values['updated_at'] + return updated_values ############################### diff --git a/cinder/db/sqlalchemy/models.py b/cinder/db/sqlalchemy/models.py index 8edfcd2e770..5a57967b577 100644 --- a/cinder/db/sqlalchemy/models.py +++ b/cinder/db/sqlalchemy/models.py @@ -52,8 +52,10 @@ class CinderBase(models.TimestampMixin, def delete(self, session): """Delete this object.""" - self.update(self.delete_values()) + updated_values = self.delete_values() + self.update(updated_values) self.save(session=session) + return updated_values class Service(BASE, CinderBase): diff --git a/cinder/objects/backup.py b/cinder/objects/backup.py index 9f5778f42ee..dec8ffd7687 100644 --- a/cinder/objects/backup.py +++ b/cinder/objects/backup.py @@ -122,7 +122,9 @@ class Backup(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - db.backup_destroy(self._context, self.id) + updated_values = db.backup_destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @staticmethod def decode_record(backup_url): diff --git a/cinder/objects/cgsnapshot.py b/cinder/objects/cgsnapshot.py index d5ad3772e49..ca3cc4f1cca 100644 --- a/cinder/objects/cgsnapshot.py +++ b/cinder/objects/cgsnapshot.py @@ -114,7 +114,9 @@ class CGSnapshot(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - db.cgsnapshot_destroy(self._context, self.id) + updated_values = db.cgsnapshot_destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @base.CinderObjectRegistry.register diff --git a/cinder/objects/consistencygroup.py b/cinder/objects/consistencygroup.py index 14ff6585cc3..ea3b72cf9b5 100644 --- a/cinder/objects/consistencygroup.py +++ b/cinder/objects/consistencygroup.py @@ -136,7 +136,10 @@ class ConsistencyGroup(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - db.consistencygroup_destroy(self._context, self.id) + updated_values = db.consistencygroup_destroy(self._context, + self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @base.CinderObjectRegistry.register diff --git a/cinder/objects/qos_specs.py b/cinder/objects/qos_specs.py index 16384bcf1c9..467d373069a 100644 --- a/cinder/objects/qos_specs.py +++ b/cinder/objects/qos_specs.py @@ -169,10 +169,11 @@ class QualityOfServiceSpecs(base.CinderPersistentObject, if self.volume_types: if not force: raise exception.QoSSpecsInUse(specs_id=self.id) - else: - # remove all association - db.qos_specs_disassociate_all(self._context, self.id) - db.qos_specs_delete(self._context, self.id) + # remove all association + db.qos_specs_disassociate_all(self._context, self.id) + updated_values = db.qos_specs_delete(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @base.CinderObjectRegistry.register diff --git a/cinder/objects/service.py b/cinder/objects/service.py index c1e7dce466c..b7075c16404 100644 --- a/cinder/objects/service.py +++ b/cinder/objects/service.py @@ -95,7 +95,9 @@ class Service(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - db.service_destroy(self._context, self.id) + updated_values = db.service_destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @classmethod def _get_minimum_version(cls, attribute, context, binary): diff --git a/cinder/objects/snapshot.py b/cinder/objects/snapshot.py index ebcfc7dfcc7..166702dff73 100644 --- a/cinder/objects/snapshot.py +++ b/cinder/objects/snapshot.py @@ -178,7 +178,9 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject, self.obj_reset_changes() def destroy(self): - db.snapshot_destroy(self._context, self.id) + updated_values = db.snapshot_destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: diff --git a/cinder/objects/volume.py b/cinder/objects/volume.py index 7a62f26ecfe..4de8645d59d 100644 --- a/cinder/objects/volume.py +++ b/cinder/objects/volume.py @@ -327,7 +327,9 @@ class Volume(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - db.volume_destroy(self._context, self.id) + updated_values = db.volume_destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: diff --git a/cinder/objects/volume_type.py b/cinder/objects/volume_type.py index d9828f81ec7..fbcc7ae68b9 100644 --- a/cinder/objects/volume_type.py +++ b/cinder/objects/volume_type.py @@ -115,7 +115,9 @@ class VolumeType(base.CinderPersistentObject, base.CinderObject, def destroy(self): with self.obj_as_admin(): - volume_types.destroy(self._context, self.id) + updated_values = volume_types.destroy(self._context, self.id) + self.update(updated_values) + self.obj_reset_changes(updated_values.keys()) @base.CinderObjectRegistry.register diff --git a/cinder/tests/unit/objects/test_backup.py b/cinder/tests/unit/objects/test_backup.py index 037bbf24c40..12c633ef564 100644 --- a/cinder/tests/unit/objects/test_backup.py +++ b/cinder/tests/unit/objects/test_backup.py @@ -13,6 +13,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz import six from cinder.db.sqlalchemy import models @@ -77,13 +79,22 @@ class TestBackup(test_objects.BaseObjectsTestCase): backup_update.assert_called_once_with(self.context, backup.id, {'display_name': 'foobar'}) - @mock.patch('cinder.db.backup_destroy') - def test_destroy(self, backup_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.backup_destroy') + def test_destroy(self, backup_destroy, utcnow_mock): + backup_destroy.return_value = { + 'status': fields.BackupStatus.DELETED, + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} backup = objects.Backup(context=self.context, id=fake.BACKUP_ID) backup.destroy() self.assertTrue(backup_destroy.called) admin_context = backup_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) + self.assertTrue(backup.deleted) + self.assertEqual(fields.BackupStatus.DELETED, backup.status) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + backup.deleted_at) def test_obj_field_temp_volume_snapshot_id(self): backup = objects.Backup(context=self.context, diff --git a/cinder/tests/unit/objects/test_cgsnapshot.py b/cinder/tests/unit/objects/test_cgsnapshot.py index 7d7d09dbcab..42696122bf3 100644 --- a/cinder/tests/unit/objects/test_cgsnapshot.py +++ b/cinder/tests/unit/objects/test_cgsnapshot.py @@ -13,6 +13,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz import six from cinder import exception @@ -81,14 +83,23 @@ class TestCGSnapshot(test_objects.BaseObjectsTestCase): cgsnapshot.obj_get_changes()) self.assertRaises(exception.ObjectActionError, cgsnapshot.save) - @mock.patch('cinder.db.cgsnapshot_destroy') - def test_destroy(self, cgsnapshot_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.cgsnapshot_destroy') + def test_destroy(self, cgsnapshot_destroy, utcnow_mock): + cgsnapshot_destroy.return_value = { + 'status': 'deleted', + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} cgsnapshot = objects.CGSnapshot(context=self.context, id=fake.CGSNAPSHOT_ID) cgsnapshot.destroy() self.assertTrue(cgsnapshot_destroy.called) admin_context = cgsnapshot_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) + self.assertTrue(cgsnapshot.deleted) + self.assertEqual('deleted', cgsnapshot.status) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + cgsnapshot.deleted_at) @mock.patch('cinder.objects.consistencygroup.ConsistencyGroup.get_by_id') @mock.patch('cinder.objects.snapshot.SnapshotList.get_all_for_cgsnapshot') diff --git a/cinder/tests/unit/objects/test_consistencygroup.py b/cinder/tests/unit/objects/test_consistencygroup.py index b5577aa9fdb..b5b9571799f 100644 --- a/cinder/tests/unit/objects/test_consistencygroup.py +++ b/cinder/tests/unit/objects/test_consistencygroup.py @@ -13,6 +13,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz import six from cinder import exception @@ -146,14 +148,24 @@ class TestConsistencyGroup(test_objects.BaseObjectsTestCase): mock_vol_get_all_by_group.assert_called_once_with(self.context, consistencygroup.id) - @mock.patch('cinder.db.consistencygroup_destroy') - def test_destroy(self, consistencygroup_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_destroy') + def test_destroy(self, consistencygroup_destroy, utcnow_mock): + consistencygroup_destroy.return_value = { + 'status': fields.ConsistencyGroupStatus.DELETED, + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} consistencygroup = objects.ConsistencyGroup( context=self.context, id=fake.CONSISTENCY_GROUP_ID) consistencygroup.destroy() self.assertTrue(consistencygroup_destroy.called) admin_context = consistencygroup_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) + self.assertTrue(consistencygroup.deleted) + self.assertEqual(fields.ConsistencyGroupStatus.DELETED, + consistencygroup.status) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + consistencygroup.deleted_at) @mock.patch('cinder.db.sqlalchemy.api.consistencygroup_get') def test_refresh(self, consistencygroup_get): diff --git a/cinder/tests/unit/objects/test_qos.py b/cinder/tests/unit/objects/test_qos.py index eba7e399a3b..cb88b2f4ce4 100644 --- a/cinder/tests/unit/objects/test_qos.py +++ b/cinder/tests/unit/objects/test_qos.py @@ -11,6 +11,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz from cinder.db.sqlalchemy import models from cinder import exception @@ -78,17 +80,25 @@ class TestQos(test_objects.BaseObjectsTestCase): mock.call(self.context, fake.OBJECT_ID, 'key_to_remove1'), mock.call(self.context, fake.OBJECT_ID, 'key_to_remove2')]) + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) @mock.patch('cinder.objects.VolumeTypeList.get_all_types_for_qos', return_value=None) - @mock.patch('cinder.db.qos_specs_delete') - def test_destroy_no_vol_types(self, qos_fake_delete, fake_get_vol_types): + @mock.patch('cinder.db.sqlalchemy.api.qos_specs_delete') + def test_destroy_no_vol_types(self, qos_fake_delete, fake_get_vol_types, + utcnow_mock): + qos_fake_delete.return_value = { + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} qos_object = objects.QualityOfServiceSpecs._from_db_object( self.context, objects.QualityOfServiceSpecs(), fake_qos) qos_object.destroy() - qos_fake_delete.assert_called_once_with(self.context, fake_qos['id']) + qos_fake_delete.assert_called_once_with(mock.ANY, fake_qos['id']) + self.assertTrue(qos_object.deleted) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + qos_object.deleted_at) - @mock.patch('cinder.db.qos_specs_delete') + @mock.patch('cinder.db.sqlalchemy.api.qos_specs_delete') @mock.patch('cinder.db.qos_specs_disassociate_all') @mock.patch('cinder.objects.VolumeTypeList.get_all_types_for_qos') def test_destroy_with_vol_types(self, fake_get_vol_types, @@ -100,7 +110,7 @@ class TestQos(test_objects.BaseObjectsTestCase): self.assertRaises(exception.QoSSpecsInUse, qos_object.destroy) qos_object.destroy(force=True) - qos_fake_delete.assert_called_once_with(self.context, fake_qos['id']) + qos_fake_delete.assert_called_once_with(mock.ANY, fake_qos['id']) qos_fake_disassociate.assert_called_once_with( self.context, fake_qos['id']) diff --git a/cinder/tests/unit/objects/test_service.py b/cinder/tests/unit/objects/test_service.py index e0e97ca9f5e..f82496beef6 100644 --- a/cinder/tests/unit/objects/test_service.py +++ b/cinder/tests/unit/objects/test_service.py @@ -13,6 +13,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz import six from cinder import objects @@ -69,14 +71,21 @@ class TestService(test_objects.BaseObjectsTestCase): service_update.assert_called_once_with(self.context, service.id, {'topic': 'foobar'}) - @mock.patch('cinder.db.service_destroy') - def test_destroy(self, service_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.service_destroy') + def test_destroy(self, service_destroy, utcnow_mock): + service_destroy.return_value = { + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} db_service = fake_service.fake_db_service() service = objects.Service._from_db_object( self.context, objects.Service(), db_service) with mock.patch.object(service._context, 'elevated') as elevated_ctx: service.destroy() service_destroy.assert_called_once_with(elevated_ctx(), 123) + self.assertTrue(service.deleted) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + service.deleted_at) @mock.patch('cinder.db.sqlalchemy.api.service_get') def test_refresh(self, service_get): diff --git a/cinder/tests/unit/objects/test_snapshot.py b/cinder/tests/unit/objects/test_snapshot.py index 0e92a896418..3b2547a943c 100644 --- a/cinder/tests/unit/objects/test_snapshot.py +++ b/cinder/tests/unit/objects/test_snapshot.py @@ -14,6 +14,8 @@ import copy import mock +from oslo_utils import timeutils +import pytz import six from cinder.db.sqlalchemy import models @@ -112,12 +114,21 @@ class TestSnapshot(test_objects.BaseObjectsTestCase): {'key1': 'value1'}, True) - @mock.patch('cinder.db.snapshot_destroy') - def test_destroy(self, snapshot_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.snapshot_destroy') + def test_destroy(self, snapshot_destroy, utcnow_mock): + snapshot_destroy.return_value = { + 'status': 'deleted', + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} snapshot = objects.Snapshot(context=self.context, id=fake.SNAPSHOT_ID) snapshot.destroy() snapshot_destroy.assert_called_once_with(self.context, fake.SNAPSHOT_ID) + self.assertTrue(snapshot.deleted) + self.assertEqual('deleted', snapshot.status) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + snapshot.deleted_at) @mock.patch('cinder.db.snapshot_metadata_delete') def test_delete_metadata_key(self, snapshot_metadata_delete): diff --git a/cinder/tests/unit/objects/test_volume.py b/cinder/tests/unit/objects/test_volume.py index 6c5d875576b..a0a79e98b2a 100644 --- a/cinder/tests/unit/objects/test_volume.py +++ b/cinder/tests/unit/objects/test_volume.py @@ -14,6 +14,8 @@ import ddt import mock +from oslo_utils import timeutils +import pytz import six from cinder import context @@ -132,8 +134,13 @@ class TestVolume(test_objects.BaseObjectsTestCase): volume.snapshots = objects.SnapshotList() self.assertRaises(exception.ObjectActionError, volume.save) - @mock.patch('cinder.db.volume_destroy') - def test_destroy(self, volume_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.volume_destroy') + def test_destroy(self, volume_destroy, utcnow_mock): + volume_destroy.return_value = { + 'status': 'deleted', + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} db_volume = fake_volume.fake_db_volume() volume = objects.Volume._from_db_object(self.context, objects.Volume(), db_volume) @@ -141,6 +148,11 @@ class TestVolume(test_objects.BaseObjectsTestCase): self.assertTrue(volume_destroy.called) admin_context = volume_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) + self.assertTrue(volume.deleted) + self.assertEqual('deleted', volume.status) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + volume.deleted_at) + self.assertIsNone(volume.migration_status) def test_obj_fields(self): volume = objects.Volume(context=self.context, id=fake.VOLUME_ID, diff --git a/cinder/tests/unit/objects/test_volume_type.py b/cinder/tests/unit/objects/test_volume_type.py index 4a4a945423c..7f837acfc0d 100644 --- a/cinder/tests/unit/objects/test_volume_type.py +++ b/cinder/tests/unit/objects/test_volume_type.py @@ -13,6 +13,8 @@ # under the License. import mock +from oslo_utils import timeutils +import pytz import six from cinder import objects @@ -79,8 +81,12 @@ class TestVolumeType(test_objects.BaseObjectsTestCase): volume_type.name, volume_type.description) - @mock.patch('cinder.volume.volume_types.destroy') - def test_destroy(self, volume_type_destroy): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=timeutils.utcnow()) + @mock.patch('cinder.db.sqlalchemy.api.volume_type_destroy') + def test_destroy(self, volume_type_destroy, utcnow_mock): + volume_type_destroy.return_value = { + 'deleted': True, + 'deleted_at': utcnow_mock.return_value} db_volume_type = fake_volume.fake_db_volume_type() volume_type = objects.VolumeType._from_db_object(self.context, objects.VolumeType(), @@ -89,6 +95,9 @@ class TestVolumeType(test_objects.BaseObjectsTestCase): self.assertTrue(volume_type_destroy.called) admin_context = volume_type_destroy.call_args[0][0] self.assertTrue(admin_context.is_admin) + self.assertTrue(volume_type.deleted) + self.assertEqual(utcnow_mock.return_value.replace(tzinfo=pytz.UTC), + volume_type.deleted_at) @mock.patch('cinder.db.sqlalchemy.api._volume_type_get_full') def test_refresh(self, volume_type_get): diff --git a/cinder/tests/unit/test_db_api.py b/cinder/tests/unit/test_db_api.py index d2132699904..c4083a2276b 100644 --- a/cinder/tests/unit/test_db_api.py +++ b/cinder/tests/unit/test_db_api.py @@ -19,6 +19,7 @@ import datetime import enum import mock from oslo_config import cfg +from oslo_utils import timeutils from oslo_utils import uuidutils import six @@ -38,6 +39,7 @@ CONF = cfg.CONF THREE = 3 THREE_HUNDREDS = 300 ONE_HUNDREDS = 100 +UTC_NOW = timeutils.utcnow() def _quota_reserve(context, project_id): @@ -142,11 +144,14 @@ class DBAPIServiceTestCase(BaseTest): for key, value in self._get_base_values().items(): self.assertEqual(value, service[key]) - def test_service_destroy(self): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=UTC_NOW) + def test_service_destroy(self, utcnow_mock): service1 = self._create_service({}) service2 = self._create_service({'host': 'fake_host2'}) - db.service_destroy(self.ctxt, service1['id']) + self.assertDictEqual( + {'deleted': True, 'deleted_at': UTC_NOW}, + db.service_destroy(self.ctxt, service1['id'])) self.assertRaises(exception.ServiceNotFound, db.service_get, self.ctxt, service1['id']) self._assertEqualObjects( @@ -393,9 +398,13 @@ class DBAPIVolumeTestCase(BaseTest): self._assertEqualObjects(volume, db.volume_get(self.ctxt, volume['id'])) - def test_volume_destroy(self): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=UTC_NOW) + def test_volume_destroy(self, utcnow_mock): volume = db.volume_create(self.ctxt, {}) - db.volume_destroy(self.ctxt, volume['id']) + self.assertDictEqual( + {'status': 'deleted', 'deleted': True, 'deleted_at': UTC_NOW, + 'migration_status': None}, + db.volume_destroy(self.ctxt, volume['id'])) self.assertRaises(exception.VolumeNotFound, db.volume_get, self.ctxt, volume['id']) @@ -1803,8 +1812,11 @@ class DBAPIQuotaClassTestCase(BaseTest): qc = db.quota_class_get(self.ctxt, 'test_qc', 'test_resource') self._assertEqualObjects(self.sample_qc, qc) - def test_quota_class_destroy(self): - db.quota_class_destroy(self.ctxt, 'test_qc', 'test_resource') + @mock.patch('oslo_utils.timeutils.utcnow', return_value=UTC_NOW) + def test_quota_class_destroy(self, utcnow_mock): + self.assertDictEqual( + {'deleted': True, 'deleted_at': UTC_NOW}, + db.quota_class_destroy(self.ctxt, 'test_qc', 'test_resource')) self.assertRaises(exception.QuotaClassNotFound, db.quota_class_get, self.ctxt, 'test_qc', 'test_resource') @@ -1909,10 +1921,12 @@ class DBAPIQuotaTestCase(BaseTest): 'volumes': {'reserved': 1, 'in_use': 0}}, quota_usage) - def test_quota_destroy(self): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=UTC_NOW) + def test_quota_destroy(self, utcnow_mock): db.quota_create(self.ctxt, 'project1', 'resource1', 41) - self.assertIsNone(db.quota_destroy(self.ctxt, 'project1', - 'resource1')) + self.assertDictEqual( + {'deleted': True, 'deleted_at': UTC_NOW}, + db.quota_destroy(self.ctxt, 'project1', 'resource1')) self.assertRaises(exception.ProjectQuotaNotFound, db.quota_get, self.ctxt, 'project1', 'resource1') @@ -2134,9 +2148,13 @@ class DBAPIBackupTestCase(BaseTest): self._assertEqualObjects(updated_values, updated_backup, self._ignored_keys) - def test_backup_destroy(self): + @mock.patch('oslo_utils.timeutils.utcnow', return_value=UTC_NOW) + def test_backup_destroy(self, utcnow_mock): for backup in self.created: - db.backup_destroy(self.ctxt, backup['id']) + self.assertDictEqual( + {'status': fields.BackupStatus.DELETED, 'deleted': True, + 'deleted_at': UTC_NOW}, + db.backup_destroy(self.ctxt, backup['id'])) self.assertFalse(db.backup_get_all(self.ctxt)) def test_backup_not_found(self): diff --git a/cinder/tests/unit/test_qos_specs.py b/cinder/tests/unit/test_qos_specs.py index 81835e381fb..68a747d5f71 100644 --- a/cinder/tests/unit/test_qos_specs.py +++ b/cinder/tests/unit/test_qos_specs.py @@ -22,6 +22,7 @@ import six import time from oslo_db import exception as db_exc +from oslo_utils import timeutils from cinder import context from cinder import db @@ -146,7 +147,8 @@ class QoSSpecsTestCase(test.TestCase): return vol_types def fake_db_delete(context, id): - pass + return {'deleted': True, + 'deleted_at': timeutils.utcnow()} def fake_disassociate_all(context, id): pass diff --git a/cinder/volume/volume_types.py b/cinder/volume/volume_types.py index 161317dd50e..7e7ed7d06b4 100644 --- a/cinder/volume/volume_types.py +++ b/cinder/volume/volume_types.py @@ -90,9 +90,8 @@ def destroy(context, id): if id is None: msg = _("id cannot be None") raise exception.InvalidVolumeType(reason=msg) - else: - elevated = context if context.is_admin else context.elevated() - db.volume_type_destroy(elevated, id) + elevated = context if context.is_admin else context.elevated() + return db.volume_type_destroy(elevated, id) def get_all_types(context, inactive=0, filters=None, marker=None,