From f79bb53a39b137f5a836210707f3e351b177f6d0 Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Sun, 13 Sep 2020 22:57:05 +1200 Subject: [PATCH] Support getting backups of a specific project Change-Id: I6a6778ddbb1ee92da28a709be91b04873fe749fe --- api-ref/source/backups.inc | 2 + .../notes/victoria-list-project-backups.yaml | 3 + trove/backup/models.py | 7 +- trove/backup/service.py | 4 +- .../backup/test_backup_controller.py | 66 ++++++++++++++++++- 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/victoria-list-project-backups.yaml diff --git a/api-ref/source/backups.inc b/api-ref/source/backups.inc index 8b52f230b4..61828ef9e5 100644 --- a/api-ref/source/backups.inc +++ b/api-ref/source/backups.inc @@ -20,6 +20,8 @@ using query string parameters. The following filters are supported: - ``all_projects=True/False`` - Return the list of backups for all the projects, this is an admin only param by default. - ``datastore={datastore}`` - Return a list of backups of the same datastore. +- ``project_id={project_id}`` - Get backups of a specific project. Admin + required. Normal response codes: 200 diff --git a/releasenotes/notes/victoria-list-project-backups.yaml b/releasenotes/notes/victoria-list-project-backups.yaml new file mode 100644 index 0000000000..b45638b9cb --- /dev/null +++ b/releasenotes/notes/victoria-list-project-backups.yaml @@ -0,0 +1,3 @@ +--- +features: + - The admin user is able to get backups of a specific project. diff --git a/trove/backup/models.py b/trove/backup/models.py index 95d8683f7e..10c58ad2fb 100644 --- a/trove/backup/models.py +++ b/trove/backup/models.py @@ -186,13 +186,16 @@ class Backup(object): return query.all(), marker @classmethod - def list(cls, context, datastore=None, instance_id=None, + def list(cls, context, datastore=None, instance_id=None, project_id=None, all_projects=False): query = DBBackup.query() filters = [DBBackup.deleted == 0] - if not all_projects: + if project_id: + filters.append(DBBackup.tenant_id == project_id) + elif not all_projects: filters.append(DBBackup.tenant_id == context.project_id) + if instance_id: filters.append(DBBackup.instance_id == instance_id) diff --git a/trove/backup/service.py b/trove/backup/service.py index 095ee9c904..ed783199ab 100644 --- a/trove/backup/service.py +++ b/trove/backup/service.py @@ -44,10 +44,11 @@ class BackupController(wsgi.Controller): LOG.debug("Listing backups for tenant %s", tenant_id) datastore = req.GET.get('datastore') instance_id = req.GET.get('instance_id') + project_id = req.GET.get('project_id') all_projects = strutils.bool_from_string(req.GET.get('all_projects')) context = req.environ[wsgi.CONTEXT_KEY] - if all_projects: + if project_id or all_projects: policy.authorize_on_tenant(context, 'backup:index:all_projects') else: policy.authorize_on_tenant(context, 'backup:index') @@ -56,6 +57,7 @@ class BackupController(wsgi.Controller): context, datastore=datastore, instance_id=instance_id, + project_id=project_id, all_projects=all_projects ) view = views.BackupViews(backups) diff --git a/trove/tests/unittests/backup/test_backup_controller.py b/trove/tests/unittests/backup/test_backup_controller.py index d1f73a4b68..cf6a88026c 100644 --- a/trove/tests/unittests/backup/test_backup_controller.py +++ b/trove/tests/unittests/backup/test_backup_controller.py @@ -13,20 +13,36 @@ # License for the specific language governing permissions and limitations # under the License. # +from unittest import mock +import uuid + import jsonschema from testtools.matchers import Equals + +from trove.backup import models +from trove.backup import state from trove.backup.service import BackupController from trove.common import apischema +from trove.common import context +from trove.common import wsgi from trove.tests.unittests import trove_testtools +from trove.tests.unittests.util import util class TestBackupController(trove_testtools.TestCase): - def setUp(self): super(TestBackupController, self).setUp() self.uuid = "d6338c9c-3cc8-4313-b98f-13cc0684cf15" self.invalid_uuid = "ead-edsa-e23-sdf-23" self.controller = BackupController() + self.context = context.TroveContext(project_id=str(uuid.uuid4())) + util.init_db() + + def tearDown(self): + super(TestBackupController, self).tearDown() + backups = models.DBBackup.find_all(tenant_id=self.context.project_id) + for backup in backups: + backup.delete() def test_validate_create_complete(self): body = {"backup": {"instance": self.uuid, @@ -87,3 +103,51 @@ class TestBackupController(trove_testtools.TestCase): self.assertThat(errors[0].message, Equals("'%s' does not match '%s'" % (self.invalid_uuid, apischema.uuid['pattern']))) + + def test_list_by_project(self): + req = mock.MagicMock(GET={'project_id': self.context.project_id}, + environ={wsgi.CONTEXT_KEY: self.context}, + url='http://localhost') + instance_id = str(uuid.uuid4()) + backup_name = str(uuid.uuid4()) + location = 'https://object-storage.com/tenant/database_backups/backup' + models.DBBackup.create(tenant_id=self.context.project_id, + name=backup_name, + state=state.BackupState.NEW, + instance_id=instance_id, + deleted=False, + size=2.0, + location=location) + + res = self.controller.index(req, 'fake_tenant_id') + + self.assertEqual(200, res.status) + backups = res.data(None)['backups'] + self.assertGreaterEqual(len(backups), 1) + our_backup = None + for backup in backups: + if backup['name'] == backup_name: + our_backup = backup + break + self.assertIsNotNone(our_backup) + expected = { + 'name': backup_name, + 'locationRef': location, + 'instance_id': instance_id, + 'size': 2.0, + 'status': 'NEW', + } + self.assertTrue( + set(expected.items()).issubset(set(our_backup.items())) + ) + + # Get backups of unknown project + req = mock.MagicMock(GET={'project_id': str(uuid.uuid4())}, + environ={wsgi.CONTEXT_KEY: self.context}, + url='http://localhost') + + res = self.controller.index(req, 'fake_tenant_id') + + self.assertEqual(200, res.status) + backups = res.data(None)['backups'] + self.assertEqual(0, len(backups))