diff --git a/cinder/cmd/volume_usage_audit.py b/cinder/cmd/volume_usage_audit.py index ba8d38f316f..20da655cf41 100644 --- a/cinder/cmd/volume_usage_audit.py +++ b/cinder/cmd/volume_usage_audit.py @@ -246,4 +246,75 @@ def main(): LOG.exception(_LE("Delete snapshot notification failed: %s"), exc_msg, resource=snapshot_ref) + backups = db.backup_get_active_by_window(admin_context, + begin, end) + + LOG.debug("Found %d backups", len(backups)) + for backup_ref in backups: + try: + LOG.debug("Send notification for " + " <%(extra_info)s>", + {'backup_id': backup_ref.id, + 'project_id': backup_ref.project_id, + 'extra_info': extra_info}) + cinder.volume.utils.notify_about_backup_usage(admin_context, + backup_ref, + 'exists', + extra_info) + except Exception as exc_msg: + LOG.error(_LE("Exists backups notification failed: %s"), + exc_msg) + + if (CONF.send_actions and + backup_ref.created_at > begin and + backup_ref.created_at < end): + try: + local_extra_info = { + 'audit_period_beginning': str(backup_ref.created_at), + 'audit_period_ending': str(backup_ref.created_at), + } + LOG.debug("Send create notification for " + " " + " <%(extra_info)s>", + {'backup_id': backup_ref.id, + 'project_id': backup_ref.project_id, + 'extra_info': local_extra_info}) + cinder.volume.utils.notify_about_backup_usage( + admin_context, + backup_ref, + 'create.start', extra_usage_info=local_extra_info) + cinder.volume.utils.notify_about_backup_usage( + admin_context, + backup_ref, + 'create.end', extra_usage_info=local_extra_info) + except Exception as exc_msg: + LOG.error(_LE("Create backup notification failed: %s"), + exc_msg) + + if (CONF.send_actions and backup_ref.deleted_at and + backup_ref.deleted_at > begin and + backup_ref.deleted_at < end): + try: + local_extra_info = { + 'audit_period_beginning': str(backup_ref.deleted_at), + 'audit_period_ending': str(backup_ref.deleted_at), + } + LOG.debug("Send delete notification for " + " " + " <%(extra_info)s>", + {'backup_id': backup_ref.id, + 'project_id': backup_ref.project_id, + 'extra_info': local_extra_info}) + cinder.volume.utils.notify_about_backup_usage( + admin_context, + backup_ref, + 'delete.start', extra_usage_info=local_extra_info) + cinder.volume.utils.notify_about_backup_usage( + admin_context, + backup_ref, + 'delete.end', extra_usage_info=local_extra_info) + except Exception as exc_msg: + LOG.error(_LE("Delete backup notification failed: %s"), + exc_msg) + LOG.debug("Volume usage audit completed") diff --git a/cinder/db/api.py b/cinder/db/api.py index 560e6c6db59..42a294e19f6 100644 --- a/cinder/db/api.py +++ b/cinder/db/api.py @@ -1155,6 +1155,14 @@ def backup_get_all_by_volume(context, volume_id, filters=None): filters=filters) +def backup_get_active_by_window(context, begin, end=None, project_id=None): + """Get all the backups inside the window. + + Specifying a project_id will filter for a certain project. + """ + return IMPL.backup_get_active_by_window(context, begin, end, project_id) + + def backup_update(context, backup_id, values): """Set the given properties on a backup and update it. diff --git a/cinder/db/sqlalchemy/api.py b/cinder/db/sqlalchemy/api.py index 81a74338804..ca631cd08d0 100644 --- a/cinder/db/sqlalchemy/api.py +++ b/cinder/db/sqlalchemy/api.py @@ -4696,6 +4696,21 @@ def backup_get_all_by_volume(context, volume_id, filters=None): return _backup_get_all(context, filters) +@require_context +def backup_get_active_by_window(context, begin, end=None, project_id=None): + """Return backups that were active during window.""" + + query = model_query(context, models.Backup, read_deleted="yes") + query = query.filter(or_(models.Backup.deleted_at == None, # noqa + models.Backup.deleted_at > begin)) + if end: + query = query.filter(models.Backup.created_at < end) + if project_id: + query = query.filter_by(project_id=project_id) + + return query.all() + + @handle_db_data_error @require_context def backup_create(context, values): diff --git a/cinder/tests/unit/fake_constants.py b/cinder/tests/unit/fake_constants.py index d80e5d49683..e27d22cc587 100644 --- a/cinder/tests/unit/fake_constants.py +++ b/cinder/tests/unit/fake_constants.py @@ -20,6 +20,8 @@ ATTACHMENT2_ID = 'ac2439fe-c071-468f-94e3-547bedb95de0' BACKUP_ID = '707844eb-6d8a-4ac1-8b98-618e1c0b3a3a' BACKUP2_ID = '40e8462a-c9d8-462f-a810-b732a1790535' BACKUP3_ID = '30ae7641-017e-4221-a642-855687c8bd71' +BACKUP4_ID = '23f8605b-8273-4f49-9b3d-1eeca81a63c2' +BACKUP5_ID = '50c97b22-51ea-440b-8d01-ded20a55d7e0' CGSNAPSHOT_ID = '5e34cce3-bc97-46b7-a127-5cfb95ef445d' CGSNAPSHOT_NAME = 'cgsnapshot-5e34cce3-bc97-46b7-a127-5cfb95ef445d' CGSNAPSHOT2_ID = '5c36d762-d6ba-4f04-bd07-88a298cc410a' diff --git a/cinder/tests/unit/test_cmd.py b/cinder/tests/unit/test_cmd.py index 9946a96017a..6de696e7508 100644 --- a/cinder/tests/unit/test_cmd.py +++ b/cinder/tests/unit/test_cmd.py @@ -1754,6 +1754,72 @@ class TestCinderVolumeUsageAuditCmd(test.TestCase): extra_usage_info=local_extra_info_delete) ]) + @mock.patch('cinder.volume.utils.notify_about_backup_usage') + @mock.patch('cinder.db.backup_get_active_by_window') + @mock.patch('cinder.volume.utils.notify_about_volume_usage') + @mock.patch('cinder.db.volume_get_active_by_window') + @mock.patch('cinder.utils.last_completed_audit_period') + @mock.patch('cinder.rpc.init') + @mock.patch('cinder.version.version_string') + @mock.patch('cinder.context.get_admin_context') + def test_main_send_backup_error(self, get_admin_context, + version_string, rpc_init, + last_completed_audit_period, + volume_get_active_by_window, + notify_about_volume_usage, + backup_get_active_by_window, + notify_about_backup_usage): + CONF.set_override('send_actions', True) + CONF.set_override('start_time', '2014-01-01 01:00:00') + CONF.set_override('end_time', '2014-02-02 02:00:00') + begin = datetime.datetime(2014, 1, 1, 1, 0) + end = datetime.datetime(2014, 2, 2, 2, 0) + ctxt = context.RequestContext('fake-user', 'fake-project') + get_admin_context.return_value = ctxt + last_completed_audit_period.return_value = (begin, end) + backup1_created = datetime.datetime(2014, 1, 1, 2, 0) + backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0) + backup1 = mock.MagicMock(id=fake.BACKUP_ID, + project_id=fake.PROJECT_ID, + created_at=backup1_created, + deleted_at=backup1_deleted) + volume_get_active_by_window.return_value = [] + backup_get_active_by_window.return_value = [backup1] + extra_info = { + 'audit_period_beginning': str(begin), + 'audit_period_ending': str(end), + } + local_extra_info_create = { + 'audit_period_beginning': str(backup1.created_at), + 'audit_period_ending': str(backup1.created_at), + } + local_extra_info_delete = { + 'audit_period_beginning': str(backup1.deleted_at), + 'audit_period_ending': str(backup1.deleted_at), + } + + notify_about_backup_usage.side_effect = Exception() + + volume_usage_audit.main() + + get_admin_context.assert_called_once_with() + self.assertEqual('cinder', CONF.project) + self.assertEqual(CONF.version, version.version_string()) + rpc_init.assert_called_once_with(CONF) + last_completed_audit_period.assert_called_once_with() + volume_get_active_by_window.assert_called_once_with(ctxt, begin, end) + self.assertFalse(notify_about_volume_usage.called) + notify_about_backup_usage.assert_any_call(ctxt, backup1, 'exists', + extra_info) + notify_about_backup_usage.assert_any_call( + ctxt, backup1, 'create.start', + extra_usage_info=local_extra_info_create) + notify_about_backup_usage.assert_any_call( + ctxt, backup1, 'delete.start', + extra_usage_info=local_extra_info_delete) + + @mock.patch('cinder.volume.utils.notify_about_backup_usage') + @mock.patch('cinder.db.backup_get_active_by_window') @mock.patch('cinder.volume.utils.notify_about_snapshot_usage') @mock.patch('cinder.objects.snapshot.SnapshotList.get_active_by_window') @mock.patch('cinder.volume.utils.notify_about_volume_usage') @@ -1767,7 +1833,8 @@ class TestCinderVolumeUsageAuditCmd(test.TestCase): def test_main(self, get_admin_context, log_setup, get_logger, version_string, rpc_init, last_completed_audit_period, volume_get_active_by_window, notify_about_volume_usage, - snapshot_get_active_by_window, notify_about_snapshot_usage): + snapshot_get_active_by_window, notify_about_snapshot_usage, + backup_get_active_by_window, notify_about_backup_usage): CONF.set_override('send_actions', True) CONF.set_override('start_time', '2014-01-01 01:00:00') CONF.set_override('end_time', '2014-02-02 02:00:00') @@ -1812,6 +1879,22 @@ class TestCinderVolumeUsageAuditCmd(test.TestCase): 'audit_period_ending': str(snapshot1.deleted_at), } + backup1_created = datetime.datetime(2014, 1, 1, 2, 0) + backup1_deleted = datetime.datetime(2014, 1, 1, 3, 0) + backup1 = mock.MagicMock(id=fake.BACKUP_ID, + project_id=fake.PROJECT_ID, + created_at=backup1_created, + deleted_at=backup1_deleted) + backup_get_active_by_window.return_value = [backup1] + extra_info_backup_create = { + 'audit_period_beginning': str(backup1.created_at), + 'audit_period_ending': str(backup1.created_at), + } + extra_info_backup_delete = { + 'audit_period_beginning': str(backup1.deleted_at), + 'audit_period_ending': str(backup1.deleted_at), + } + volume_usage_audit.main() get_admin_context.assert_called_once_with() @@ -1845,3 +1928,15 @@ class TestCinderVolumeUsageAuditCmd(test.TestCase): mock.call(ctxt, snapshot1, 'delete.end', extra_usage_info=extra_info_snapshot_delete) ]) + + notify_about_backup_usage.assert_has_calls([ + mock.call(ctxt, backup1, 'exists', extra_info), + mock.call(ctxt, backup1, 'create.start', + extra_usage_info=extra_info_backup_create), + mock.call(ctxt, backup1, 'create.end', + extra_usage_info=extra_info_backup_create), + mock.call(ctxt, backup1, 'delete.start', + extra_usage_info=extra_info_backup_delete), + mock.call(ctxt, backup1, 'delete.end', + extra_usage_info=extra_info_backup_delete) + ]) diff --git a/cinder/tests/unit/test_volume.py b/cinder/tests/unit/test_volume.py index f10f5f15d3f..af3e974c3a8 100644 --- a/cinder/tests/unit/test_volume.py +++ b/cinder/tests/unit/test_volume.py @@ -5924,7 +5924,6 @@ class GetActiveByWindowTestCase(BaseVolumeTestCase): 'deleted': True, 'status': 'deleted', 'deleted_at': datetime.datetime(1, 2, 1, 1, 1, 1), }, - { 'id': fake.VOLUME2_ID, 'host': 'devstack', @@ -5998,6 +5997,48 @@ class GetActiveByWindowTestCase(BaseVolumeTestCase): } ] + self.db_back_attrs = [ + { + 'id': fake.BACKUP_ID, + 'host': 'devstack', + 'project_id': fake.PROJECT_ID, + 'created_at': datetime.datetime(1, 1, 1, 1, 1, 1), + 'deleted': 1, + 'status': 'deleted', + 'deleted_at': datetime.datetime(1, 2, 1, 1, 1, 1) + }, + { + 'id': fake.BACKUP2_ID, + 'host': 'devstack', + 'project_id': fake.PROJECT_ID, + 'created_at': datetime.datetime(1, 1, 1, 1, 1, 1), + 'deleted': 1, + 'status': 'deleted', + 'deleted_at': datetime.datetime(1, 3, 10, 1, 1, 1) + }, + { + 'id': fake.BACKUP3_ID, + 'host': 'devstack', + 'project_id': fake.PROJECT_ID, + 'created_at': datetime.datetime(1, 1, 1, 1, 1, 1), + 'deleted': 1, + 'status': 'deleted', + 'deleted_at': datetime.datetime(1, 5, 1, 1, 1, 1) + }, + { + 'id': fake.BACKUP4_ID, + 'host': 'devstack', + 'project_id': fake.PROJECT_ID, + 'created_at': datetime.datetime(1, 3, 10, 1, 1, 1), + }, + { + 'id': fake.BACKUP5_ID, + 'host': 'devstack', + 'project_id': fake.PROJECT_ID, + 'created_at': datetime.datetime(1, 5, 1, 1, 1, 1), + }, + ] + def test_volume_get_active_by_window(self): # Find all all volumes valid within a timeframe window. @@ -6069,6 +6110,38 @@ class GetActiveByWindowTestCase(BaseVolumeTestCase): self.assertEqual(snap4.id, snapshots[2].id) self.assertEqual(fake.VOLUME_ID, snapshots[2].volume_id) + def test_backup_get_active_by_window(self): + # Find all backups valid within a timeframe window. + db.volume_create(self.context, {'id': fake.VOLUME_ID}) + for i in range(5): + self.db_back_attrs[i]['volume_id'] = fake.VOLUME_ID + + # Not in window + db.backup_create(self.ctx, self.db_back_attrs[0]) + + # In - deleted in window + db.backup_create(self.ctx, self.db_back_attrs[1]) + + # In - deleted after window + db.backup_create(self.ctx, self.db_back_attrs[2]) + + # In - created in window + db.backup_create(self.ctx, self.db_back_attrs[3]) + + # Not of window + db.backup_create(self.ctx, self.db_back_attrs[4]) + + backups = db.backup_get_active_by_window( + self.context, + datetime.datetime(1, 3, 1, 1, 1, 1), + datetime.datetime(1, 4, 1, 1, 1, 1), + project_id=fake.PROJECT_ID + ) + self.assertEqual(3, len(backups)) + self.assertEqual(fake.BACKUP2_ID, backups[0].id) + self.assertEqual(fake.BACKUP3_ID, backups[1].id) + self.assertEqual(fake.BACKUP4_ID, backups[2].id) + class DriverTestCase(test.TestCase): """Base Test class for Drivers."""