Merge lp:~stevenk/launchpad/remove-unused-sharing-garbo into lp:launchpad

Proposed by Steve Kowalik on 2012-08-22
Status: Merged
Approved by: Steve Kowalik on 2012-08-22
Approved revision: no longer in the source branch.
Merged at revision: 15856
Proposed branch: lp:~stevenk/launchpad/remove-unused-sharing-garbo
Merge into: lp:launchpad
Diff against target: 195 lines (+86/-1)
6 files modified
database/schema/security.cfg (+2/-0)
lib/lp/code/model/branchnamespace.py (+1/-0)
lib/lp/registry/interfaces/accesspolicy.py (+3/-0)
lib/lp/registry/model/accesspolicy.py (+5/-0)
lib/lp/scripts/garbo.py (+59/-1)
lib/lp/scripts/tests/test_garbo.py (+16/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/remove-unused-sharing-garbo
Reviewer Review Type Date Requested Status
William Grant code 2012-08-22 Approve on 2012-08-22
Review via email: mp+120698@code.launchpad.net

Commit Message

Add a daily garbo job that will trawl all products and remove all access policies that are unused.

Description of the Change

Add a daily garbo job that will trawl all products and remove all access policies that are unused. I've tried to minimize the queries used, and make use of the accesspolicy methods.

To post a comment you must log in.
William Grant (wgrant) wrote :

122 + allowed_bug_policies = set(
123 + BUG_POLICY_ALLOWED_TYPES.get(product.bug_sharing_policy, []))
124 + allowed_branch_policies = set(
125 + BRANCH_POLICY_ALLOWED_TYPES.get(
126 + product.branch_sharing_policy, []))

You'll need to either exclude those with *_sharing_policy unset, or default to FREE_INFORMATION_TYPES, otherwise all legacy-configured projects will have their builtin APs reaped.

127 + access_polices = set(

Does it really?

131 + apas = getUtility(IAccessPolicyArtifactSource).findByPolicy(
132 + candidate_aps)
133 + used_aps = set([apa.policy for apa in apas])

This is going to load potentially hundreds of thousands of objects, which is probably not what you want.

review: Needs Fixing (code)
William Grant (wgrant) wrote :

Could you a docstring to the TunableLoop and perhaps some comments describing what it's doing and why? Apart from that it's fine.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2012-08-21 20:41:05 +0000
3+++ database/schema/security.cfg 2012-08-22 23:30:30 +0000
4@@ -2220,6 +2220,8 @@
5
6 [garbo]
7 groups=script,read
8+public.accesspolicy = SELECT, DELETE
9+public.accesspolicygrant = SELECT, DELETE
10 public.account = SELECT, DELETE
11 public.answercontact = SELECT, DELETE
12 public.branch = SELECT, UPDATE
13
14=== modified file 'lib/lp/code/model/branchnamespace.py'
15--- lib/lp/code/model/branchnamespace.py 2012-08-20 13:26:21 +0000
16+++ lib/lp/code/model/branchnamespace.py 2012-08-22 23:30:30 +0000
17@@ -8,6 +8,7 @@
18 'BranchNamespaceSet',
19 'PackageNamespace',
20 'PersonalNamespace',
21+ 'POLICY_ALLOWED_TYPES',
22 'ProductNamespace',
23 ]
24
25
26=== modified file 'lib/lp/registry/interfaces/accesspolicy.py'
27--- lib/lp/registry/interfaces/accesspolicy.py 2012-07-19 03:18:37 +0000
28+++ lib/lp/registry/interfaces/accesspolicy.py 2012-08-22 23:30:30 +0000
29@@ -229,6 +229,9 @@
30 pairs.
31 """
32
33+ def revokeByPolicy(policies):
34+ """Revoke all `IAccessPolicyGrant` for the policies."""
35+
36
37 class IAccessPolicyGrantFlatSource(Interface):
38 """Experimental query utility to search through the flattened schema."""
39
40=== modified file 'lib/lp/registry/model/accesspolicy.py'
41--- lib/lp/registry/model/accesspolicy.py 2012-08-06 03:47:42 +0000
42+++ lib/lp/registry/model/accesspolicy.py 2012-08-22 23:30:30 +0000
43@@ -378,6 +378,11 @@
44 """See `IAccessPolicyGrantSource`."""
45 cls.find(grants).remove()
46
47+ @classmethod
48+ def revokeByPolicy(cls, policies):
49+ """See `IAccessPolicyGrantSource`."""
50+ cls.findByPolicy(policies).remove()
51+
52
53 class AccessPolicyGrantFlat(StormBase):
54 __storm_table__ = 'AccessPolicyGrantFlat'
55
56=== modified file 'lib/lp/scripts/garbo.py'
57--- lib/lp/scripts/garbo.py 2012-08-17 07:18:49 +0000
58+++ lib/lp/scripts/garbo.py 2012-08-22 23:30:30 +0000
59@@ -6,6 +6,7 @@
60 __metaclass__ = type
61 __all__ = [
62 'DailyDatabaseGarbageCollector',
63+ 'FrequentDatabaseGarbageCollector',
64 'HourlyDatabaseGarbageCollector',
65 ]
66
67@@ -46,6 +47,9 @@
68
69 from lp.answers.model.answercontact import AnswerContact
70 from lp.bugs.interfaces.bug import IBugSet
71+from lp.bugs.interfaces.bugtarget import (
72+ POLICY_ALLOWED_TYPES as BUG_POLICY_ALLOWED_TYPES,
73+ )
74 from lp.bugs.model.bug import Bug
75 from lp.bugs.model.bugattachment import BugAttachment
76 from lp.bugs.model.bugnotification import BugNotification
77@@ -56,6 +60,9 @@
78 )
79 from lp.code.enums import BranchVisibilityRule
80 from lp.code.interfaces.revision import IRevisionSet
81+from lp.code.model.branchnamespace import (
82+ POLICY_ALLOWED_TYPES as BRANCH_POLICY_ALLOWED_TYPES,
83+ )
84 from lp.code.model.branchvisibilitypolicy import BranchVisibilityTeamPolicy
85 from lp.code.model.codeimportevent import CodeImportEvent
86 from lp.code.model.codeimportresult import CodeImportResult
87@@ -64,6 +71,12 @@
88 RevisionCache,
89 )
90 from lp.hardwaredb.model.hwdb import HWSubmission
91+from lp.registry.enums import FREE_INFORMATION_TYPES
92+from lp.registry.interfaces.accesspolicy import (
93+ IAccessPolicyArtifactSource,
94+ IAccessPolicyGrantSource,
95+ IAccessPolicySource,
96+ )
97 from lp.registry.model.commercialsubscription import CommercialSubscription
98 from lp.registry.model.person import Person
99 from lp.registry.model.product import Product
100@@ -1054,6 +1067,50 @@
101 transaction.commit()
102
103
104+class UnusedSharingPolicyPruner(TunableLoop):
105+ """Deletes unused AccessPolicy and AccessPolicyGrants for products."""
106+
107+ maximum_chunk_size = 5000
108+
109+ def __init__(self, log, abort_time=None):
110+ super(UnusedSharingPolicyPruner, self).__init__(log, abort_time)
111+ self.start_at = 1
112+ self.store = IMasterStore(Product)
113+
114+ def findProducts(self):
115+ return self.store.find(
116+ Product, Product.id >= self.start_at).order_by(Product.id)
117+
118+ def isDone(self):
119+ return self.findProducts().is_empty()
120+
121+ def __call__(self, chunk_size):
122+ products = list(self.findProducts()[:chunk_size])
123+ for product in products:
124+ allowed_bug_policies = set(
125+ BUG_POLICY_ALLOWED_TYPES.get(
126+ product.bug_sharing_policy, FREE_INFORMATION_TYPES))
127+ allowed_branch_policies = set(
128+ BRANCH_POLICY_ALLOWED_TYPES.get(
129+ product.branch_sharing_policy, FREE_INFORMATION_TYPES))
130+ # Fetch all APs, and after filtering out ones that are forbidden
131+ # by the bug and branch policies, the APs that have no APAs are
132+ # unused and can be deleted.
133+ access_policies = set(
134+ getUtility(IAccessPolicySource).findByPillar([product]))
135+ candidate_aps = access_policies.difference(
136+ allowed_bug_policies, allowed_branch_policies)
137+ apa_source = getUtility(IAccessPolicyArtifactSource)
138+ unused_aps = [
139+ ap for ap in candidate_aps
140+ if apa_source.findByPolicy([ap]).is_empty()]
141+ getUtility(IAccessPolicyGrantSource).revokeByPolicy(unused_aps)
142+ for ap in unused_aps:
143+ self.store.remove(ap)
144+ self.start_at = products[-1].id + 1
145+ transaction.commit()
146+
147+
148 class BaseDatabaseGarbageCollector(LaunchpadCronScript):
149 """Abstract base class to run a collection of TunableLoops."""
150 script_name = None # Script name for locking and database user. Override.
151@@ -1340,8 +1397,9 @@
152 ScrubPOFileTranslator,
153 SuggestiveTemplatesCacheUpdater,
154 POTranslationPruner,
155+ UnlinkedAccountPruner,
156 UnusedPOTMsgSetPruner,
157- UnlinkedAccountPruner,
158+ UnusedSharingPolicyPruner,
159 ]
160 experimental_tunable_loops = [
161 PersonPruner,
162
163=== modified file 'lib/lp/scripts/tests/test_garbo.py'
164--- lib/lp/scripts/tests/test_garbo.py 2012-08-17 07:18:49 +0000
165+++ lib/lp/scripts/tests/test_garbo.py 2012-08-22 23:30:30 +0000
166@@ -59,6 +59,7 @@
167 BranchSharingPolicy,
168 BugSharingPolicy,
169 )
170+from lp.registry.interfaces.accesspolicy import IAccessPolicySource
171 from lp.registry.interfaces.person import IPersonSet
172 from lp.registry.interfaces.product import IProductSet
173 from lp.registry.model.product import Product
174@@ -1083,6 +1084,21 @@
175 self.assertEqual(
176 BugSharingPolicy.PUBLIC, product.bug_sharing_policy)
177
178+ def test_UnusedSharingPolicyPruner(self):
179+ # UnusedSharingPolicyPruner destroys the unused sharing details.
180+ switch_dbuser('testadmin')
181+ product = self.factory.makeProduct(
182+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
183+ ap = self.factory.makeAccessPolicy(pillar=product)
184+ self.factory.makeAccessPolicyArtifact(policy=ap)
185+ self.factory.makeAccessPolicyGrant(policy=ap)
186+ all_aps = getUtility(IAccessPolicySource).findByPillar([product])
187+ # There are 3 because two are created implicitly when the product is.
188+ self.assertEqual(3, all_aps.count())
189+ self.runDaily()
190+ all_aps = getUtility(IAccessPolicySource).findByPillar([product])
191+ self.assertEqual([ap], list(all_aps))
192+
193
194 class TestGarboTasks(TestCaseWithFactory):
195 layer = LaunchpadZopelessLayer