diff --git a/doc/source/admin/identity-use-trusts.rst b/doc/source/admin/identity-use-trusts.rst index a4e12f3f34..b84620e13e 100644 --- a/doc/source/admin/identity-use-trusts.rst +++ b/doc/source/admin/identity-use-trusts.rst @@ -59,7 +59,19 @@ The delegation parameters are: Removing Expired Trusts =========================================================== -In the SQL trust stores expired trusts are not automatically -removed. These trusts can be removed with:: +In the SQL trust stores expired and soft deleted trusts, that are not +automatically removed. These trusts can be removed with:: - $ keystone-manage trust_flush + $ keystone-manage trust_flush [options] + + OPTIONS (optional): + + --project-id : + To purge trusts of given project-id. + --trustor-user-id : + To purge trusts of given trustor-id. + --trustee-user-id : + To purge trusts of given trustee-id. + --date : + To purge trusts older than date. If no date is supplied + keystone-manage will use the system clock time at runtime. diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index 461834c815..2e7afcfc84 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -15,6 +15,7 @@ from __future__ import absolute_import from __future__ import print_function +import datetime import os import sys import uuid @@ -668,24 +669,48 @@ class TrustFlush(BaseApp): parser = super(TrustFlush, cls).add_argument_parser(subparsers) parser.add_argument('--project-id', default=None, - help=('The id of the project of which the expired ' + help=('The id of the project of which the ' + 'expired or non-expired soft-deleted ' 'trusts is to be purged')) parser.add_argument('--trustor-user-id', default=None, - help=('The id of the trustor of which the expired ' + help=('The id of the trustor of which the ' + 'expired or non-expired soft-deleted ' 'trusts is to be purged')) parser.add_argument('--trustee-user-id', default=None, - help=('The id of the trustee of which the expired ' + help=('The id of the trustee of which the ' + 'expired or non-expired soft-deleted ' 'trusts is to be purged')) + parser.add_argument('--date', default=datetime.datetime.utcnow(), + help=('The date of which the expired or ' + 'non-expired soft-deleted trusts older ' + 'than that will be purged. The format of ' + 'the date to be "DD-MM-YYYY". If no date ' + 'is supplied keystone-manage will use the ' + 'system clock time at runtime')) return parser @classmethod def main(cls): drivers = backends.load_backends() trust_manager = drivers['trust_api'] + if CONF.command.date: + if not isinstance(CONF.command.date, datetime.datetime): + try: + CONF.command.date = datetime.datetime.strptime( + CONF.command.date, '%d-%m-%Y') + except KeyError: + raise ValueError("'%s'Invalid input for date, should be " + "DD-MM-YYYY", CONF.command.date) + else: + LOG.info("No date is supplied, keystone-manage will use the " + "system clock time at runtime ") + trust_manager.flush_expired_and_soft_deleted_trusts( project_id=CONF.command.project_id, trustor_user_id=CONF.command.trustor_user_id, - trustee_user_id=CONF.command.trustee_user_id) + trustee_user_id=CONF.command.trustee_user_id, + date=CONF.command.date + ) class MappingPurge(BaseApp): diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index 8f1b08cf4a..25f05be9e3 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -1639,6 +1639,7 @@ class TestTrustFlush(unit.SQLDriverOverrides, unit.BaseTestCase): self.project_id = parent.command_project_id self.trustor_user_id = parent.command_trustor_user_id self.trustee_user_id = parent.command_trustee_user_id + self.date = parent.command_date def setUp(self): # Set up preset cli options and a parser @@ -1661,6 +1662,7 @@ class TestTrustFlush(unit.SQLDriverOverrides, unit.BaseTestCase): self.command_project_id = None self.command_trustor_user_id = None self.command_trustee_user_id = None + self.command_date = datetime.datetime.utcnow() self.useFixture(fixtures.MockPatchObject( CONF, 'command', self.FakeConfCommand(self))) @@ -1674,6 +1676,26 @@ class TestTrustFlush(unit.SQLDriverOverrides, unit.BaseTestCase): trust = cli.TrustFlush() trust.main() + def test_trust_flush_with_invalid_date(self): + self.command_project_id = None + self.command_trustor_user_id = None + self.command_trustee_user_id = None + self.command_date = '4/10/92' + self.useFixture(fixtures.MockPatchObject( + CONF, 'command', self.FakeConfCommand(self))) + + def fake_load_backends(): + return dict( + trust_api=keystone.trust.core.Manager()) + + self.useFixture(fixtures.MockPatch( + 'keystone.server.backends.load_backends', + side_effect=fake_load_backends)) + # Clear backend dependencies, since cli loads these manually + provider_api.ProviderAPIs._clear_registry_instances() + trust = cli.TrustFlush() + self.assertRaises(ValueError, trust.main) + class TestMappingEngineTester(unit.BaseTestCase): diff --git a/keystone/tests/unit/trust/test_backends.py b/keystone/tests/unit/trust/test_backends.py index e3062586b9..ca1fb222be 100644 --- a/keystone/tests/unit/trust/test_backends.py +++ b/keystone/tests/unit/trust/test_backends.py @@ -206,7 +206,8 @@ class TrustTests(object): trust_ref2, roles) self.assertIsNotNone(trust_data) - PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts() + PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -236,7 +237,8 @@ class TrustTests(object): PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( project_id=self.tenant_bar['id'], trustor_user_id=self.user_foo['id'], - trustee_user_id=self.user_two['id']) + trustee_user_id=self.user_two['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref1['id'], trusts[0]['id']) @@ -265,7 +267,8 @@ class TrustTests(object): PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( trustor_user_id=self.user_foo['id'], - trustee_user_id=self.user_two['id']) + trustee_user_id=self.user_two['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -294,7 +297,8 @@ class TrustTests(object): PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( project_id=self.tenant_bar['id'], - trustee_user_id=self.user_two['id']) + trustee_user_id=self.user_two['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -323,7 +327,8 @@ class TrustTests(object): PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( project_id=self.tenant_bar['id'], - trustor_user_id=self.user_foo['id']) + trustor_user_id=self.user_foo['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -351,7 +356,8 @@ class TrustTests(object): self.assertIsNotNone(trust_data) PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( - project_id=self.tenant_bar['id']) + project_id=self.tenant_bar['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -378,7 +384,8 @@ class TrustTests(object): trust_ref2, roles) self.assertIsNotNone(trust_data) PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( - trustee_user_id=self.user_two['id']) + trustee_user_id=self.user_two['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -406,7 +413,8 @@ class TrustTests(object): self.assertIsNotNone(trust_data) PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( - trustor_user_id=self.user_foo['id']) + trustor_user_id=self.user_foo['id'], + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref2['id'], trusts[0]['id']) @@ -434,7 +442,8 @@ class TrustTests(object): self.assertIsNotNone(trust_data) PROVIDERS.trust_api.delete_trust(trust_ref2['id']) - PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts() + PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 1) self.assertEqual(trust_ref1['id'], trusts[0]['id']) @@ -469,6 +478,44 @@ class TrustTests(object): trust_ref3, roles) self.assertIsNotNone(trust_data) - PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts() + PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( + date=datetime.datetime.utcnow()) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 2) + + def test_flush_expired_trusts_with_date(self): + roles = [{"id": "member"}, + {"id": "other"}, + {"id": "browser"}] + trust_ref1 = core.new_trust_ref( + self.user_foo['id'], self.user_two['id'], + project_id=self.tenant_bar['id']) + trust_ref1['expires_at'] = \ + timeutils.utcnow() + datetime.timedelta(minutes=10) + trust_ref2 = core.new_trust_ref( + self.user_two['id'], self.user_two['id'], + project_id=self.tenant_bar['id']) + trust_ref2['expires_at'] = \ + timeutils.utcnow() + datetime.timedelta(minutes=30) + trust_ref3 = core.new_trust_ref( + self.user_two['id'], self.user_foo['id'], + project_id=self.tenant_bar['id']) + trust_ref3['expires_at'] = \ + timeutils.utcnow() - datetime.timedelta(minutes=30) + + trust_data = PROVIDERS.trust_api.create_trust(trust_ref1['id'], + trust_ref1, roles) + self.assertIsNotNone(trust_data) + trust_data = PROVIDERS.trust_api.create_trust(trust_ref2['id'], + trust_ref2, roles) + self.assertIsNotNone(trust_data) + trust_data = PROVIDERS.trust_api.create_trust(trust_ref3['id'], + trust_ref3, roles) + self.assertIsNotNone(trust_data) + fake_date = timeutils.utcnow() + datetime.timedelta(minutes=15) + PROVIDERS.trust_api.flush_expired_and_soft_deleted_trusts( + date=fake_date + ) + trusts = self.trust_api.list_trusts() + self.assertEqual(len(trusts), 1) + self.assertEqual(trust_ref2['id'], trusts[0]['id']) diff --git a/keystone/trust/backends/sql.py b/keystone/trust/backends/sql.py index c76a69038f..76f2c32d68 100644 --- a/keystone/trust/backends/sql.py +++ b/keystone/trust/backends/sql.py @@ -193,15 +193,15 @@ class Trust(base.TrustDriverBase): def flush_expired_and_soft_deleted_trusts(self, project_id=None, trustor_user_id=None, - trustee_user_id=None): + trustee_user_id=None, + date=None): with sql.session_for_write() as session: query = session.query(TrustModel) query = query.\ filter(sqlalchemy.or_(TrustModel.deleted_at.isnot(None), sqlalchemy.and_( TrustModel.expires_at.isnot(None), - TrustModel.expires_at < - timeutils.utcnow()))) + TrustModel.expires_at < date))) if project_id: query = query.filter_by(project_id=project_id) if trustor_user_id: diff --git a/releasenotes/notes/bug-1473292-c21481e6aec29ec2.yaml b/releasenotes/notes/bug-1473292-c21481e6aec29ec2.yaml index 7ac11d94b4..c35581187b 100644 --- a/releasenotes/notes/bug-1473292-c21481e6aec29ec2.yaml +++ b/releasenotes/notes/bug-1473292-c21481e6aec29ec2.yaml @@ -11,11 +11,13 @@ feature: $ keystone-manage trust-flush [Options] - Options: + Options (optional): - project-id : To purge trusts of given project-id. - trustor-user-id : To purge trusts of given trustor-id. - trustee-user-id : To purge trusts of given trustee-id. + --project-id : To purge trusts of given project-id. + --trustor-user-id : To purge trusts of given trustor-id. + --trustee-user-id : To purge trusts of given trustee-id. + --date : To purge trusts older than date. It will purge trusts older than + current if date not given.