Merge lp:~cjwatson/launchpad/branch-delete-job into lp:launchpad
- branch-delete-job
- Merge into devel
Proposed by
Colin Watson
Status: | Rejected | ||||
---|---|---|---|---|---|
Rejected by: | Colin Watson | ||||
Proposed branch: | lp:~cjwatson/launchpad/branch-delete-job | ||||
Merge into: | lp:launchpad | ||||
Prerequisite: | lp:~cjwatson/launchpad/code-delete-no-recipe-preload | ||||
Diff against target: |
948 lines (+377/-48) 11 files modified
database/schema/security.cfg (+48/-0) lib/lp/code/configure.zcml (+10/-1) lib/lp/code/enums.py (+12/-1) lib/lp/code/interfaces/branch.py (+7/-1) lib/lp/code/interfaces/branchjob.py (+19/-1) lib/lp/code/model/branch.py (+19/-1) lib/lp/code/model/branchjob.py (+84/-3) lib/lp/code/model/tests/test_branch.py (+76/-38) lib/lp/code/model/tests/test_branchjob.py (+95/-2) lib/lp/code/stories/webservice/xx-branch.txt (+1/-0) lib/lp/services/config/schema-lazr.conf (+6/-0) |
||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/branch-delete-job | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+364907@code.launchpad.net |
Commit message
Add a branch deletion job.
Description of the change
Nothing creates these jobs yet; that will come in a follow-up branch.
We'll need to get the branch-delete-job DB user created and .pgpass updated on the relevant machines before landing this.
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote : | # |
Unmerged revisions
- 18912. By Colin Watson
-
Add a branch deletion job.
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 2019-03-11 14:51:16 +0000 | |||
3 | +++ database/schema/security.cfg 2019-03-21 15:55:38 +0000 | |||
4 | @@ -2593,3 +2593,51 @@ | |||
5 | 2593 | public.teamparticipation = SELECT | 2593 | public.teamparticipation = SELECT |
6 | 2594 | public.webhook = SELECT | 2594 | public.webhook = SELECT |
7 | 2595 | public.webhookjob = SELECT, INSERT | 2595 | public.webhookjob = SELECT, INSERT |
8 | 2596 | |||
9 | 2597 | [branch-delete-job] | ||
10 | 2598 | type=user | ||
11 | 2599 | groups=script | ||
12 | 2600 | public.accessartifact = SELECT, DELETE | ||
13 | 2601 | public.accessartifactgrant = SELECT, DELETE | ||
14 | 2602 | public.accesspolicyartifact = SELECT, DELETE | ||
15 | 2603 | public.archive = SELECT | ||
16 | 2604 | public.branch = SELECT, UPDATE, DELETE | ||
17 | 2605 | public.branchjob = SELECT, INSERT, DELETE | ||
18 | 2606 | public.branchmergeproposal = SELECT, UPDATE, DELETE | ||
19 | 2607 | public.branchmergeproposaljob = SELECT, DELETE | ||
20 | 2608 | public.branchsubscription = SELECT, DELETE | ||
21 | 2609 | public.bug = SELECT | ||
22 | 2610 | public.bugbranch = SELECT, DELETE | ||
23 | 2611 | public.bugtask = SELECT | ||
24 | 2612 | public.buildfarmjob = SELECT, DELETE | ||
25 | 2613 | public.buildqueue = SELECT, DELETE | ||
26 | 2614 | public.codeimport = SELECT, DELETE | ||
27 | 2615 | public.codeimportjob = SELECT, DELETE | ||
28 | 2616 | public.codereviewinlinecomment = SELECT, DELETE | ||
29 | 2617 | public.codereviewinlinecommentdraft = SELECT, DELETE | ||
30 | 2618 | public.codereviewmessage = SELECT, DELETE | ||
31 | 2619 | public.codereviewvote = SELECT, DELETE | ||
32 | 2620 | public.distribution = SELECT | ||
33 | 2621 | public.distributionsourcepackage = SELECT, DELETE | ||
34 | 2622 | public.distroseries = SELECT | ||
35 | 2623 | public.emailaddress = SELECT | ||
36 | 2624 | public.job = SELECT, INSERT, UPDATE, DELETE | ||
37 | 2625 | public.person = SELECT | ||
38 | 2626 | public.previewdiff = SELECT, DELETE | ||
39 | 2627 | public.product = SELECT | ||
40 | 2628 | public.productseries = SELECT, UPDATE | ||
41 | 2629 | public.seriessourcepackagebranch = SELECT, DELETE | ||
42 | 2630 | public.snap = SELECT, UPDATE | ||
43 | 2631 | public.sourcepackage = SELECT | ||
44 | 2632 | public.sourcepackagename = SELECT | ||
45 | 2633 | public.sourcepackagepublishinghistory = SELECT | ||
46 | 2634 | public.sourcepackagerecipe = SELECT, DELETE | ||
47 | 2635 | public.sourcepackagerecipebuild = SELECT, UPDATE | ||
48 | 2636 | public.sourcepackagerecipedata = SELECT, DELETE | ||
49 | 2637 | public.sourcepackagerecipedatainstruction = SELECT, DELETE | ||
50 | 2638 | public.sourcepackagerecipedistroseries = SELECT, DELETE | ||
51 | 2639 | public.sourcepackagerelease = SELECT | ||
52 | 2640 | public.specificationbranch = SELECT, DELETE | ||
53 | 2641 | public.translationtemplatesbuild = SELECT, DELETE | ||
54 | 2642 | public.webhook = SELECT, DELETE | ||
55 | 2643 | public.webhookjob = SELECT, DELETE | ||
56 | 2596 | 2644 | ||
57 | === modified file 'lib/lp/code/configure.zcml' | |||
58 | --- lib/lp/code/configure.zcml 2018-10-16 15:52:00 +0000 | |||
59 | +++ lib/lp/code/configure.zcml 2019-03-21 15:55:38 +0000 | |||
60 | @@ -1,4 +1,4 @@ | |||
62 | 1 | <!-- Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | <!-- Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
63 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | 2 | GNU Affero General Public License version 3 (see the file LICENSE). |
64 | 3 | --> | 3 | --> |
65 | 4 | 4 | ||
66 | @@ -602,6 +602,10 @@ | |||
67 | 602 | <allow interface="lp.code.interfaces.branchjob.IBranchModifiedMailJob"/> | 602 | <allow interface="lp.code.interfaces.branchjob.IBranchModifiedMailJob"/> |
68 | 603 | <allow interface="lp.code.interfaces.branchjob.IBranchJob"/> | 603 | <allow interface="lp.code.interfaces.branchjob.IBranchJob"/> |
69 | 604 | </class> | 604 | </class> |
70 | 605 | <class class="lp.code.model.branchjob.BranchDeleteJob"> | ||
71 | 606 | <allow interface="lp.code.interfaces.branchjob.IBranchDeleteJob"/> | ||
72 | 607 | <allow interface="lp.code.interfaces.branchjob.IBranchJob"/> | ||
73 | 608 | </class> | ||
74 | 605 | <securedutility | 609 | <securedutility |
75 | 606 | component="lp.code.model.branchjob.RevisionMailJob" | 610 | component="lp.code.model.branchjob.RevisionMailJob" |
76 | 607 | provides="lp.code.interfaces.branchjob.IRevisionMailJobSource"> | 611 | provides="lp.code.interfaces.branchjob.IRevisionMailJobSource"> |
77 | @@ -627,6 +631,11 @@ | |||
78 | 627 | provides="lp.code.interfaces.branchjob.IBranchModifiedMailJobSource"> | 631 | provides="lp.code.interfaces.branchjob.IBranchModifiedMailJobSource"> |
79 | 628 | <allow interface="lp.code.interfaces.branchjob.IBranchModifiedMailJobSource"/> | 632 | <allow interface="lp.code.interfaces.branchjob.IBranchModifiedMailJobSource"/> |
80 | 629 | </securedutility> | 633 | </securedutility> |
81 | 634 | <securedutility | ||
82 | 635 | component="lp.code.model.branchjob.BranchDeleteJob" | ||
83 | 636 | provides="lp.code.interfaces.branchjob.IBranchDeleteJobSource"> | ||
84 | 637 | <allow interface="lp.code.interfaces.branchjob.IBranchDeleteJobSource"/> | ||
85 | 638 | </securedutility> | ||
86 | 630 | 639 | ||
87 | 631 | <!-- CodeImport --> | 640 | <!-- CodeImport --> |
88 | 632 | 641 | ||
89 | 633 | 642 | ||
90 | === modified file 'lib/lp/code/enums.py' | |||
91 | --- lib/lp/code/enums.py 2018-10-19 23:10:41 +0000 | |||
92 | +++ lib/lp/code/enums.py 2019-03-21 15:55:38 +0000 | |||
93 | @@ -1,10 +1,11 @@ | |||
95 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
96 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
97 | 3 | 3 | ||
98 | 4 | """Enumerations used in the lp/code modules.""" | 4 | """Enumerations used in the lp/code modules.""" |
99 | 5 | 5 | ||
100 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
101 | 7 | __all__ = [ | 7 | __all__ = [ |
102 | 8 | 'BranchDeletionStatus', | ||
103 | 8 | 'BranchLifecycleStatus', | 9 | 'BranchLifecycleStatus', |
104 | 9 | 'BranchLifecycleStatusFilter', | 10 | 'BranchLifecycleStatusFilter', |
105 | 10 | 'BranchListingSort', | 11 | 'BranchListingSort', |
106 | @@ -116,6 +117,16 @@ | |||
107 | 116 | """) | 117 | """) |
108 | 117 | 118 | ||
109 | 118 | 119 | ||
110 | 120 | class BranchDeletionStatus(DBEnumeratedType): | ||
111 | 121 | """Branch Deletion Status | ||
112 | 122 | |||
113 | 123 | The deletion status of a branch is used to track asynchronous deletion. | ||
114 | 124 | """ | ||
115 | 125 | |||
116 | 126 | ACTIVE = DBItem(0, "Active") | ||
117 | 127 | DELETING = DBItem(1, "Deleting") | ||
118 | 128 | |||
119 | 129 | |||
120 | 119 | class GitRepositoryType(DBEnumeratedType): | 130 | class GitRepositoryType(DBEnumeratedType): |
121 | 120 | """Git Repository Type | 131 | """Git Repository Type |
122 | 121 | 132 | ||
123 | 122 | 133 | ||
124 | === modified file 'lib/lp/code/interfaces/branch.py' | |||
125 | --- lib/lp/code/interfaces/branch.py 2019-01-23 17:13:48 +0000 | |||
126 | +++ lib/lp/code/interfaces/branch.py 2019-03-21 15:55:38 +0000 | |||
127 | @@ -1,4 +1,4 @@ | |||
129 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
130 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
131 | 3 | 3 | ||
132 | 4 | """Branch interfaces.""" | 4 | """Branch interfaces.""" |
133 | @@ -75,6 +75,7 @@ | |||
134 | 75 | RepositoryFormat, | 75 | RepositoryFormat, |
135 | 76 | ) | 76 | ) |
136 | 77 | from lp.code.enums import ( | 77 | from lp.code.enums import ( |
137 | 78 | BranchDeletionStatus, | ||
138 | 78 | BranchLifecycleStatus, | 79 | BranchLifecycleStatus, |
139 | 79 | BranchMergeProposalStatus, | 80 | BranchMergeProposalStatus, |
140 | 80 | BranchSubscriptionDiffSize, | 81 | BranchSubscriptionDiffSize, |
141 | @@ -293,6 +294,11 @@ | |||
142 | 293 | accepted). | 294 | accepted). |
143 | 294 | """ | 295 | """ |
144 | 295 | 296 | ||
145 | 297 | deletion_status = exported(Choice( | ||
146 | 298 | title=_("Deletion status"), required=True, readonly=True, | ||
147 | 299 | vocabulary=BranchDeletionStatus, | ||
148 | 300 | description=_("The deletion status of this branch."))) | ||
149 | 301 | |||
150 | 296 | # People attributes | 302 | # People attributes |
151 | 297 | registrant = exported( | 303 | registrant = exported( |
152 | 298 | PublicPersonChoice( | 304 | PublicPersonChoice( |
153 | 299 | 305 | ||
154 | === modified file 'lib/lp/code/interfaces/branchjob.py' | |||
155 | --- lib/lp/code/interfaces/branchjob.py 2015-09-01 17:10:46 +0000 | |||
156 | +++ lib/lp/code/interfaces/branchjob.py 2019-03-21 15:55:38 +0000 | |||
157 | @@ -1,4 +1,4 @@ | |||
159 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
160 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
161 | 3 | 3 | ||
162 | 4 | """BranchJob interfaces.""" | 4 | """BranchJob interfaces.""" |
163 | @@ -8,6 +8,8 @@ | |||
164 | 8 | 8 | ||
165 | 9 | 9 | ||
166 | 10 | __all__ = [ | 10 | __all__ = [ |
167 | 11 | 'IBranchDeleteJob', | ||
168 | 12 | 'IBranchDeleteJobSource', | ||
169 | 11 | 'IBranchJob', | 13 | 'IBranchJob', |
170 | 12 | 'IBranchModifiedMailJob', | 14 | 'IBranchModifiedMailJob', |
171 | 13 | 'IBranchModifiedMailJobSource', | 15 | 'IBranchModifiedMailJobSource', |
172 | @@ -201,3 +203,19 @@ | |||
173 | 201 | :param user: The `IPerson` who modified the branch. | 203 | :param user: The `IPerson` who modified the branch. |
174 | 202 | :param branch_delta: An `IBranchDelta` describing the changes. | 204 | :param branch_delta: An `IBranchDelta` describing the changes. |
175 | 203 | """ | 205 | """ |
176 | 206 | |||
177 | 207 | |||
178 | 208 | class IBranchDeleteJob(IRunnableJob): | ||
179 | 209 | """A Job that deletes a branch from the database.""" | ||
180 | 210 | |||
181 | 211 | branch_id = Int(title=_('The id of the branch to delete.')) | ||
182 | 212 | |||
183 | 213 | |||
184 | 214 | class IBranchDeleteJobSource(IJobSource): | ||
185 | 215 | |||
186 | 216 | def create(branch, requester): | ||
187 | 217 | """Delete a branch from the database. | ||
188 | 218 | |||
189 | 219 | :param branch: The `IBranch` to delete. | ||
190 | 220 | :param requester: The `IPerson` who requested the deletion. | ||
191 | 221 | """ | ||
192 | 204 | 222 | ||
193 | === modified file 'lib/lp/code/model/branch.py' | |||
194 | --- lib/lp/code/model/branch.py 2019-03-21 15:55:38 +0000 | |||
195 | +++ lib/lp/code/model/branch.py 2019-03-21 15:55:38 +0000 | |||
196 | @@ -1,4 +1,4 @@ | |||
198 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
199 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
200 | 3 | 3 | ||
201 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
202 | @@ -82,6 +82,7 @@ | |||
203 | 82 | RepositoryFormat, | 82 | RepositoryFormat, |
204 | 83 | ) | 83 | ) |
205 | 84 | from lp.code.enums import ( | 84 | from lp.code.enums import ( |
206 | 85 | BranchDeletionStatus, | ||
207 | 85 | BranchLifecycleStatus, | 86 | BranchLifecycleStatus, |
208 | 86 | BranchMergeProposalStatus, | 87 | BranchMergeProposalStatus, |
209 | 87 | BranchType, | 88 | BranchType, |
210 | @@ -304,6 +305,23 @@ | |||
211 | 304 | # such subscriptions. | 305 | # such subscriptions. |
212 | 305 | getUtility(IRemoveArtifactSubscriptionsJobSource).create(who, [self]) | 306 | getUtility(IRemoveArtifactSubscriptionsJobSource).create(who, [self]) |
213 | 306 | 307 | ||
214 | 308 | _deletion_status = EnumCol( | ||
215 | 309 | dbName='deletion_status', enum=BranchDeletionStatus, | ||
216 | 310 | default=BranchDeletionStatus.ACTIVE) | ||
217 | 311 | |||
218 | 312 | @property | ||
219 | 313 | def deletion_status(self): | ||
220 | 314 | # XXX cjwatson 2019-03-19: Remove once this column has been | ||
221 | 315 | # backfilled. | ||
222 | 316 | if self._deletion_status is None: | ||
223 | 317 | return BranchDeletionStatus.ACTIVE | ||
224 | 318 | else: | ||
225 | 319 | return self._deletion_status | ||
226 | 320 | |||
227 | 321 | @deletion_status.setter | ||
228 | 322 | def deletion_status(self, value): | ||
229 | 323 | self._deletion_status = value | ||
230 | 324 | |||
231 | 307 | registrant = ForeignKey( | 325 | registrant = ForeignKey( |
232 | 308 | dbName='registrant', foreignKey='Person', | 326 | dbName='registrant', foreignKey='Person', |
233 | 309 | storm_validator=validate_public_person, notNull=True) | 327 | storm_validator=validate_public_person, notNull=True) |
234 | 310 | 328 | ||
235 | === modified file 'lib/lp/code/model/branchjob.py' | |||
236 | --- lib/lp/code/model/branchjob.py 2018-03-30 20:42:14 +0000 | |||
237 | +++ lib/lp/code/model/branchjob.py 2019-03-21 15:55:38 +0000 | |||
238 | @@ -1,7 +1,8 @@ | |||
240 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
241 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
242 | 3 | 3 | ||
243 | 4 | __all__ = [ | 4 | __all__ = [ |
244 | 5 | 'BranchDeleteJob', | ||
245 | 5 | 'BranchJob', | 6 | 'BranchJob', |
246 | 6 | 'BranchModifiedMailJob', | 7 | 'BranchModifiedMailJob', |
247 | 7 | 'BranchScanJob', | 8 | 'BranchScanJob', |
248 | @@ -56,17 +57,22 @@ | |||
249 | 56 | implementer, | 57 | implementer, |
250 | 57 | provider, | 58 | provider, |
251 | 58 | ) | 59 | ) |
252 | 60 | from zope.security.proxy import removeSecurityProxy | ||
253 | 59 | 61 | ||
254 | 60 | from lp.code.bzr import ( | 62 | from lp.code.bzr import ( |
255 | 61 | branch_revision_history, | 63 | branch_revision_history, |
256 | 62 | get_branch_formats, | 64 | get_branch_formats, |
257 | 63 | ) | 65 | ) |
258 | 64 | from lp.code.enums import ( | 66 | from lp.code.enums import ( |
259 | 67 | BranchDeletionStatus, | ||
260 | 65 | BranchMergeProposalStatus, | 68 | BranchMergeProposalStatus, |
261 | 66 | BranchSubscriptionDiffSize, | 69 | BranchSubscriptionDiffSize, |
262 | 67 | BranchSubscriptionNotificationLevel, | 70 | BranchSubscriptionNotificationLevel, |
263 | 68 | ) | 71 | ) |
264 | 72 | from lp.code.errors import CannotDeleteBranch | ||
265 | 69 | from lp.code.interfaces.branchjob import ( | 73 | from lp.code.interfaces.branchjob import ( |
266 | 74 | IBranchDeleteJob, | ||
267 | 75 | IBranchDeleteJobSource, | ||
268 | 70 | IBranchJob, | 76 | IBranchJob, |
269 | 71 | IBranchModifiedMailJob, | 77 | IBranchModifiedMailJob, |
270 | 72 | IBranchModifiedMailJobSource, | 78 | IBranchModifiedMailJobSource, |
271 | @@ -82,6 +88,7 @@ | |||
272 | 82 | IRosettaUploadJob, | 88 | IRosettaUploadJob, |
273 | 83 | IRosettaUploadJobSource, | 89 | IRosettaUploadJobSource, |
274 | 84 | ) | 90 | ) |
275 | 91 | from lp.code.interfaces.branchlookup import IBranchLookup | ||
276 | 85 | from lp.code.mail.branch import BranchMailer | 92 | from lp.code.mail.branch import BranchMailer |
277 | 86 | from lp.code.model.branch import Branch | 93 | from lp.code.model.branch import Branch |
278 | 87 | from lp.code.model.branchmergeproposal import BranchMergeProposal | 94 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
279 | @@ -121,6 +128,7 @@ | |||
280 | 121 | BaseRunnableJobSource, | 128 | BaseRunnableJobSource, |
281 | 122 | ) | 129 | ) |
282 | 123 | from lp.services.mail.sendmail import format_address_for_person | 130 | from lp.services.mail.sendmail import format_address_for_person |
283 | 131 | from lp.services.scripts import log | ||
284 | 124 | from lp.services.utils import text_delta | 132 | from lp.services.utils import text_delta |
285 | 125 | from lp.services.webapp import canonical_url | 133 | from lp.services.webapp import canonical_url |
286 | 126 | from lp.translations.interfaces.translationimportqueue import ( | 134 | from lp.translations.interfaces.translationimportqueue import ( |
287 | @@ -195,6 +203,12 @@ | |||
288 | 195 | This job runs against a branch to send emails about modifications. | 203 | This job runs against a branch to send emails about modifications. |
289 | 196 | """) | 204 | """) |
290 | 197 | 205 | ||
291 | 206 | DELETE_BRANCH = DBItem(9, """ | ||
292 | 207 | Delete branch | ||
293 | 208 | |||
294 | 209 | This job deletes a branch from the database. | ||
295 | 210 | """) | ||
296 | 211 | |||
297 | 198 | 212 | ||
298 | 199 | @implementer(IBranchJob) | 213 | @implementer(IBranchJob) |
299 | 200 | class BranchJob(SQLBase): | 214 | class BranchJob(SQLBase): |
300 | @@ -295,7 +309,9 @@ | |||
301 | 295 | vars.extend([ | 309 | vars.extend([ |
302 | 296 | ('branch_job_id', self.context.id), | 310 | ('branch_job_id', self.context.id), |
303 | 297 | ('branch_job_type', self.context.job_type.title)]) | 311 | ('branch_job_type', self.context.job_type.title)]) |
305 | 298 | if self.context.branch is not None: | 312 | if 'branch_name' in self.metadata: |
306 | 313 | vars.append(('branch_name', self.metadata['branch_name'])) | ||
307 | 314 | elif self.context.branch is not None: | ||
308 | 299 | vars.append(('branch_name', self.context.branch.unique_name)) | 315 | vars.append(('branch_name', self.context.branch.unique_name)) |
309 | 300 | return vars | 316 | return vars |
310 | 301 | 317 | ||
311 | @@ -347,7 +363,6 @@ | |||
312 | 347 | 363 | ||
313 | 348 | def run(self): | 364 | def run(self): |
314 | 349 | """See `IBranchScanJob`.""" | 365 | """See `IBranchScanJob`.""" |
315 | 350 | from lp.services.scripts import log | ||
316 | 351 | with server(get_ro_server(), no_replace=True): | 366 | with server(get_ro_server(), no_replace=True): |
317 | 352 | try: | 367 | try: |
318 | 353 | with try_advisory_lock( | 368 | with try_advisory_lock( |
319 | @@ -1056,3 +1071,69 @@ | |||
320 | 1056 | def run(self): | 1071 | def run(self): |
321 | 1057 | """See `IBranchModifiedMailJob`.""" | 1072 | """See `IBranchModifiedMailJob`.""" |
322 | 1058 | self.getMailer().sendAll() | 1073 | self.getMailer().sendAll() |
323 | 1074 | |||
324 | 1075 | |||
325 | 1076 | @implementer(IBranchDeleteJob) | ||
326 | 1077 | @provider(IBranchDeleteJobSource) | ||
327 | 1078 | class BranchDeleteJob(BranchJobDerived): | ||
328 | 1079 | """A Job that deletes a branch from the database.""" | ||
329 | 1080 | |||
330 | 1081 | class_job_type = BranchJobType.DELETE_BRANCH | ||
331 | 1082 | |||
332 | 1083 | user_error_types = (CannotDeleteBranch,) | ||
333 | 1084 | |||
334 | 1085 | config = config.IBranchDeleteJobSource | ||
335 | 1086 | |||
336 | 1087 | def __repr__(self): | ||
337 | 1088 | return '<DELETE_BRANCH branch job (%(id)s) for %(branch)s>' % { | ||
338 | 1089 | 'id': self.context.id, | ||
339 | 1090 | 'branch': self.branch_name, | ||
340 | 1091 | } | ||
341 | 1092 | |||
342 | 1093 | def getOperationDescription(self): | ||
343 | 1094 | return 'deleting a branch' | ||
344 | 1095 | |||
345 | 1096 | @classmethod | ||
346 | 1097 | def create(cls, branch, requester): | ||
347 | 1098 | """See `IBranchDeleteJobSource`.""" | ||
348 | 1099 | metadata = {'branch_id': branch.id, 'branch_name': branch.unique_name} | ||
349 | 1100 | # The BranchJob has a branch of None, because we don't want to | ||
350 | 1101 | # delete this job while trying to delete the branch. | ||
351 | 1102 | branch_job = BranchJob( | ||
352 | 1103 | None, cls.class_job_type, metadata, requester=requester) | ||
353 | 1104 | job = cls(branch_job) | ||
354 | 1105 | job.celeryRunOnCommit() | ||
355 | 1106 | return job | ||
356 | 1107 | |||
357 | 1108 | @property | ||
358 | 1109 | def branch_id(self): | ||
359 | 1110 | return self.metadata['branch_id'] | ||
360 | 1111 | |||
361 | 1112 | @property | ||
362 | 1113 | def branch_name(self): | ||
363 | 1114 | return self.metadata['branch_name'] | ||
364 | 1115 | |||
365 | 1116 | def run(self): | ||
366 | 1117 | """See `IBranchDeleteJob`.""" | ||
367 | 1118 | branch = getUtility(IBranchLookup).get(self.branch_id) | ||
368 | 1119 | if branch is None: | ||
369 | 1120 | log.info( | ||
370 | 1121 | 'Skipping branch %s because it has already been deleted.' % | ||
371 | 1122 | self.branch_name) | ||
372 | 1123 | elif branch.deletion_status != BranchDeletionStatus.DELETING: | ||
373 | 1124 | log.warning( | ||
374 | 1125 | 'Skipping branch %s because its deletion status is not ' | ||
375 | 1126 | 'DELETING.' % self.branch_name) | ||
376 | 1127 | else: | ||
377 | 1128 | try: | ||
378 | 1129 | branch.destroySelf(break_references=True) | ||
379 | 1130 | except CannotDeleteBranch: | ||
380 | 1131 | # Set the deletion status back to ACTIVE so that it's | ||
381 | 1132 | # possible to try again. We don't attempt to undo the | ||
382 | 1133 | # renaming at the moment. Do this in its own transaction | ||
383 | 1134 | # since the job runner will abort the transaction. | ||
384 | 1135 | transaction.abort() | ||
385 | 1136 | removeSecurityProxy(branch).deletion_status = ( | ||
386 | 1137 | BranchDeletionStatus.ACTIVE) | ||
387 | 1138 | transaction.commit() | ||
388 | 1139 | raise | ||
389 | 1059 | 1140 | ||
390 | === modified file 'lib/lp/code/model/tests/test_branch.py' | |||
391 | --- lib/lp/code/model/tests/test_branch.py 2019-01-28 18:09:21 +0000 | |||
392 | +++ lib/lp/code/model/tests/test_branch.py 2019-03-21 15:55:38 +0000 | |||
393 | @@ -1,4 +1,4 @@ | |||
395 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
396 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
397 | 3 | 3 | ||
398 | 4 | """Tests for Branches.""" | 4 | """Tests for Branches.""" |
399 | @@ -1247,7 +1247,8 @@ | |||
400 | 1247 | "deleted.") | 1247 | "deleted.") |
401 | 1248 | branch_id = self.branch.id | 1248 | branch_id = self.branch.id |
402 | 1249 | branch_set = getUtility(IBranchLookup) | 1249 | branch_set = getUtility(IBranchLookup) |
404 | 1250 | self.branch.destroySelf() | 1250 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
405 | 1251 | self.branch.destroySelf() | ||
406 | 1251 | self.assertIsNone( | 1252 | self.assertIsNone( |
407 | 1252 | branch_set.get(branch_id), "The branch has not been deleted.") | 1253 | branch_set.get(branch_id), "The branch has not been deleted.") |
408 | 1253 | 1254 | ||
409 | @@ -1280,7 +1281,8 @@ | |||
410 | 1280 | bug.linkBranch(self.branch, self.user) | 1281 | bug.linkBranch(self.branch, self.user) |
411 | 1281 | self.assertEqual(self.branch.canBeDeleted(), False, | 1282 | self.assertEqual(self.branch.canBeDeleted(), False, |
412 | 1282 | "A branch linked to a bug is not deletable.") | 1283 | "A branch linked to a bug is not deletable.") |
414 | 1283 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1284 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
415 | 1285 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
416 | 1284 | 1286 | ||
417 | 1285 | def test_specBranchLinkDisablesDeletion(self): | 1287 | def test_specBranchLinkDisablesDeletion(self): |
418 | 1286 | """A branch linked to a spec cannot be deleted.""" | 1288 | """A branch linked to a spec cannot be deleted.""" |
419 | @@ -1291,7 +1293,8 @@ | |||
420 | 1291 | spec.linkBranch(self.branch, self.user) | 1293 | spec.linkBranch(self.branch, self.user) |
421 | 1292 | self.assertEqual(self.branch.canBeDeleted(), False, | 1294 | self.assertEqual(self.branch.canBeDeleted(), False, |
422 | 1293 | "A branch linked to a spec is not deletable.") | 1295 | "A branch linked to a spec is not deletable.") |
424 | 1294 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1296 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
425 | 1297 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
426 | 1295 | 1298 | ||
427 | 1296 | def test_associatedProductSeriesBranchDisablesDeletion(self): | 1299 | def test_associatedProductSeriesBranchDisablesDeletion(self): |
428 | 1297 | """A branch linked as a branch to a product series cannot be | 1300 | """A branch linked as a branch to a product series cannot be |
429 | @@ -1301,14 +1304,16 @@ | |||
430 | 1301 | self.assertEqual(self.branch.canBeDeleted(), False, | 1304 | self.assertEqual(self.branch.canBeDeleted(), False, |
431 | 1302 | "A branch that is a user branch for a product series" | 1305 | "A branch that is a user branch for a product series" |
432 | 1303 | " is not deletable.") | 1306 | " is not deletable.") |
434 | 1304 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1307 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
435 | 1308 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
436 | 1305 | 1309 | ||
437 | 1306 | def test_productSeriesTranslationsBranchDisablesDeletion(self): | 1310 | def test_productSeriesTranslationsBranchDisablesDeletion(self): |
438 | 1307 | self.product.development_focus.translations_branch = self.branch | 1311 | self.product.development_focus.translations_branch = self.branch |
439 | 1308 | self.assertEqual(self.branch.canBeDeleted(), False, | 1312 | self.assertEqual(self.branch.canBeDeleted(), False, |
440 | 1309 | "A branch that is a translations branch for a " | 1313 | "A branch that is a translations branch for a " |
441 | 1310 | "product series is not deletable.") | 1314 | "product series is not deletable.") |
443 | 1311 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1315 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
444 | 1316 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
445 | 1312 | 1317 | ||
446 | 1313 | def test_revisionsDeletable(self): | 1318 | def test_revisionsDeletable(self): |
447 | 1314 | """A branch that has some revisions can be deleted.""" | 1319 | """A branch that has some revisions can be deleted.""" |
448 | @@ -1321,7 +1326,8 @@ | |||
449 | 1321 | self.assertEqual(self.branch.canBeDeleted(), True, | 1326 | self.assertEqual(self.branch.canBeDeleted(), True, |
450 | 1322 | "A branch that has a revision is deletable.") | 1327 | "A branch that has a revision is deletable.") |
451 | 1323 | unique_name = self.branch.unique_name | 1328 | unique_name = self.branch.unique_name |
453 | 1324 | self.branch.destroySelf() | 1329 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
454 | 1330 | self.branch.destroySelf() | ||
455 | 1325 | # Commit again to trigger the deferred indices. | 1331 | # Commit again to trigger the deferred indices. |
456 | 1326 | transaction.commit() | 1332 | transaction.commit() |
457 | 1327 | branch_lookup = getUtility(IBranchLookup) | 1333 | branch_lookup = getUtility(IBranchLookup) |
458 | @@ -1335,7 +1341,8 @@ | |||
459 | 1335 | self.branch.addLandingTarget(self.user, target_branch) | 1341 | self.branch.addLandingTarget(self.user, target_branch) |
460 | 1336 | self.assertEqual(self.branch.canBeDeleted(), False, | 1342 | self.assertEqual(self.branch.canBeDeleted(), False, |
461 | 1337 | "A branch with a landing target is not deletable.") | 1343 | "A branch with a landing target is not deletable.") |
463 | 1338 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1344 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
464 | 1345 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
465 | 1339 | 1346 | ||
466 | 1340 | def test_landingCandidateDisablesDeletion(self): | 1347 | def test_landingCandidateDisablesDeletion(self): |
467 | 1341 | """A branch with a landing candidate cannot be deleted.""" | 1348 | """A branch with a landing candidate cannot be deleted.""" |
468 | @@ -1345,7 +1352,8 @@ | |||
469 | 1345 | self.assertEqual(self.branch.canBeDeleted(), False, | 1352 | self.assertEqual(self.branch.canBeDeleted(), False, |
470 | 1346 | "A branch with a landing candidate is not" | 1353 | "A branch with a landing candidate is not" |
471 | 1347 | " deletable.") | 1354 | " deletable.") |
473 | 1348 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1355 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
474 | 1356 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
475 | 1349 | 1357 | ||
476 | 1350 | def test_prerequisiteBranchDisablesDeletion(self): | 1358 | def test_prerequisiteBranchDisablesDeletion(self): |
477 | 1351 | """A branch that is a prerequisite branch cannot be deleted.""" | 1359 | """A branch that is a prerequisite branch cannot be deleted.""" |
478 | @@ -1357,14 +1365,16 @@ | |||
479 | 1357 | self.assertEqual(self.branch.canBeDeleted(), False, | 1365 | self.assertEqual(self.branch.canBeDeleted(), False, |
480 | 1358 | "A branch with a prerequisite target is not " | 1366 | "A branch with a prerequisite target is not " |
481 | 1359 | "deletable.") | 1367 | "deletable.") |
483 | 1360 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | 1368 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
484 | 1369 | self.assertRaises(CannotDeleteBranch, self.branch.destroySelf) | ||
485 | 1361 | 1370 | ||
486 | 1362 | def test_relatedBranchJobsDeleted(self): | 1371 | def test_relatedBranchJobsDeleted(self): |
487 | 1363 | # A branch with an associated branch job will delete those jobs. | 1372 | # A branch with an associated branch job will delete those jobs. |
488 | 1364 | branch = self.factory.makeBranch( | 1373 | branch = self.factory.makeBranch( |
489 | 1365 | branch_format=BranchFormat.BZR_BRANCH_6) | 1374 | branch_format=BranchFormat.BZR_BRANCH_6) |
490 | 1366 | removeSecurityProxy(branch).requestUpgrade(branch.owner) | 1375 | removeSecurityProxy(branch).requestUpgrade(branch.owner) |
492 | 1367 | branch.destroySelf() | 1376 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
493 | 1377 | branch.destroySelf() | ||
494 | 1368 | # Need to commit the transaction to fire off the constraint checks. | 1378 | # Need to commit the transaction to fire off the constraint checks. |
495 | 1369 | transaction.commit() | 1379 | transaction.commit() |
496 | 1370 | 1380 | ||
497 | @@ -1373,7 +1383,8 @@ | |||
498 | 1373 | # should be cleared. | 1383 | # should be cleared. |
499 | 1374 | dev_focus = self.branch.product.development_focus | 1384 | dev_focus = self.branch.product.development_focus |
500 | 1375 | dev_focus.translations_branch = self.branch | 1385 | dev_focus.translations_branch = self.branch |
502 | 1376 | self.branch.destroySelf(break_references=True) | 1386 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
503 | 1387 | self.branch.destroySelf(break_references=True) | ||
504 | 1377 | 1388 | ||
505 | 1378 | def test_related_TranslationTemplatesBuild_cleaned_out(self): | 1389 | def test_related_TranslationTemplatesBuild_cleaned_out(self): |
506 | 1379 | # A TranslationTemplatesBuild may come with a BuildQueue entry. | 1390 | # A TranslationTemplatesBuild may come with a BuildQueue entry. |
507 | @@ -1381,7 +1392,8 @@ | |||
508 | 1381 | # remove the TTB. | 1392 | # remove the TTB. |
509 | 1382 | build = self.factory.makeTranslationTemplatesBuild() | 1393 | build = self.factory.makeTranslationTemplatesBuild() |
510 | 1383 | build.queueBuild() | 1394 | build.queueBuild() |
512 | 1384 | build.branch.destroySelf(break_references=True) | 1395 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
513 | 1396 | build.branch.destroySelf(break_references=True) | ||
514 | 1385 | 1397 | ||
515 | 1386 | def test_unrelated_TranslationTemplatesBuild_intact(self): | 1398 | def test_unrelated_TranslationTemplatesBuild_intact(self): |
516 | 1387 | # No innocent BuildQueue entries are harmed in deleting a | 1399 | # No innocent BuildQueue entries are harmed in deleting a |
517 | @@ -1391,7 +1403,8 @@ | |||
518 | 1391 | other_build = self.factory.makeTranslationTemplatesBuild() | 1403 | other_build = self.factory.makeTranslationTemplatesBuild() |
519 | 1392 | other_bq = other_build.queueBuild() | 1404 | other_bq = other_build.queueBuild() |
520 | 1393 | 1405 | ||
522 | 1394 | build.branch.destroySelf(break_references=True) | 1406 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
523 | 1407 | build.branch.destroySelf(break_references=True) | ||
524 | 1395 | 1408 | ||
525 | 1396 | store = Store.of(build) | 1409 | store = Store.of(build) |
526 | 1397 | # The BuildQueue for the job whose branch we deleted is gone. | 1410 | # The BuildQueue for the job whose branch we deleted is gone. |
527 | @@ -1406,7 +1419,8 @@ | |||
528 | 1406 | branch = self.factory.makeAnyBranch() | 1419 | branch = self.factory.makeAnyBranch() |
529 | 1407 | branch_id = branch.id | 1420 | branch_id = branch.id |
530 | 1408 | store = Store.of(branch) | 1421 | store = Store.of(branch) |
532 | 1409 | branch.destroySelf() | 1422 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
533 | 1423 | branch.destroySelf() | ||
534 | 1410 | jobs = store.find( | 1424 | jobs = store.find( |
535 | 1411 | BranchJob, | 1425 | BranchJob, |
536 | 1412 | BranchJob.job_type == BranchJobType.RECLAIM_BRANCH_SPACE) | 1426 | BranchJob.job_type == BranchJobType.RECLAIM_BRANCH_SPACE) |
537 | @@ -1417,7 +1431,8 @@ | |||
538 | 1417 | def test_destroySelf_with_SourcePackageRecipe(self): | 1431 | def test_destroySelf_with_SourcePackageRecipe(self): |
539 | 1418 | """If branch is a base_branch in a recipe, it is deleted.""" | 1432 | """If branch is a base_branch in a recipe, it is deleted.""" |
540 | 1419 | recipe = self.factory.makeSourcePackageRecipe() | 1433 | recipe = self.factory.makeSourcePackageRecipe() |
542 | 1420 | recipe.base_branch.destroySelf(break_references=True) | 1434 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
543 | 1435 | recipe.base_branch.destroySelf(break_references=True) | ||
544 | 1421 | 1436 | ||
545 | 1422 | def test_destroySelf_with_SourcePackageRecipe_as_non_base(self): | 1437 | def test_destroySelf_with_SourcePackageRecipe_as_non_base(self): |
546 | 1423 | """If branch is referred to by a recipe, it is deleted.""" | 1438 | """If branch is referred to by a recipe, it is deleted.""" |
547 | @@ -1425,7 +1440,8 @@ | |||
548 | 1425 | branch2 = self.factory.makeAnyBranch() | 1440 | branch2 = self.factory.makeAnyBranch() |
549 | 1426 | self.factory.makeSourcePackageRecipe( | 1441 | self.factory.makeSourcePackageRecipe( |
550 | 1427 | branches=[branch1, branch2]) | 1442 | branches=[branch1, branch2]) |
552 | 1428 | branch2.destroySelf(break_references=True) | 1443 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
553 | 1444 | branch2.destroySelf(break_references=True) | ||
554 | 1429 | 1445 | ||
555 | 1430 | def test_destroySelf_with_inline_comments_draft(self): | 1446 | def test_destroySelf_with_inline_comments_draft(self): |
556 | 1431 | # Draft inline comments related to a deleted branch (source | 1447 | # Draft inline comments related to a deleted branch (source |
557 | @@ -1439,7 +1455,8 @@ | |||
558 | 1439 | previewdiff_id=preview_diff.id, | 1455 | previewdiff_id=preview_diff.id, |
559 | 1440 | person=self.user, | 1456 | person=self.user, |
560 | 1441 | comments={'1': 'Should vanish.'}) | 1457 | comments={'1': 'Should vanish.'}) |
562 | 1442 | self.branch.destroySelf(break_references=True) | 1458 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
563 | 1459 | self.branch.destroySelf(break_references=True) | ||
564 | 1443 | 1460 | ||
565 | 1444 | def test_destroySelf_with_inline_comments_published(self): | 1461 | def test_destroySelf_with_inline_comments_published(self): |
566 | 1445 | # Published inline comments related to a deleted branch (source | 1462 | # Published inline comments related to a deleted branch (source |
567 | @@ -1455,12 +1472,14 @@ | |||
568 | 1455 | previewdiff_id=preview_diff.id, | 1472 | previewdiff_id=preview_diff.id, |
569 | 1456 | inline_comments={'1': 'Must disappear.'}, | 1473 | inline_comments={'1': 'Must disappear.'}, |
570 | 1457 | ) | 1474 | ) |
572 | 1458 | self.branch.destroySelf(break_references=True) | 1475 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
573 | 1476 | self.branch.destroySelf(break_references=True) | ||
574 | 1459 | 1477 | ||
575 | 1460 | def test_related_webhooks_deleted(self): | 1478 | def test_related_webhooks_deleted(self): |
576 | 1461 | webhook = self.factory.makeWebhook(target=self.branch) | 1479 | webhook = self.factory.makeWebhook(target=self.branch) |
577 | 1462 | webhook.ping() | 1480 | webhook.ping() |
579 | 1463 | self.branch.destroySelf() | 1481 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
580 | 1482 | self.branch.destroySelf() | ||
581 | 1464 | transaction.commit() | 1483 | transaction.commit() |
582 | 1465 | self.assertRaises(LostObjectError, getattr, webhook, 'target') | 1484 | self.assertRaises(LostObjectError, getattr, webhook, 'target') |
583 | 1466 | 1485 | ||
584 | @@ -1541,7 +1560,8 @@ | |||
585 | 1541 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 1560 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
586 | 1542 | merge_proposal1_id = merge_proposal1.id | 1561 | merge_proposal1_id = merge_proposal1.id |
587 | 1543 | BranchMergeProposal.get(merge_proposal1_id) | 1562 | BranchMergeProposal.get(merge_proposal1_id) |
589 | 1544 | self.branch.destroySelf(break_references=True) | 1563 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
590 | 1564 | self.branch.destroySelf(break_references=True) | ||
591 | 1545 | self.assertRaises(SQLObjectNotFound, | 1565 | self.assertRaises(SQLObjectNotFound, |
592 | 1546 | BranchMergeProposal.get, merge_proposal1_id) | 1566 | BranchMergeProposal.get, merge_proposal1_id) |
593 | 1547 | 1567 | ||
594 | @@ -1550,14 +1570,17 @@ | |||
595 | 1550 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 1570 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
596 | 1551 | merge_proposal1_id = merge_proposal1.id | 1571 | merge_proposal1_id = merge_proposal1.id |
597 | 1552 | BranchMergeProposal.get(merge_proposal1_id) | 1572 | BranchMergeProposal.get(merge_proposal1_id) |
599 | 1553 | merge_proposal1.target_branch.destroySelf(break_references=True) | 1573 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
600 | 1574 | merge_proposal1.target_branch.destroySelf(break_references=True) | ||
601 | 1554 | self.assertRaises(SQLObjectNotFound, | 1575 | self.assertRaises(SQLObjectNotFound, |
602 | 1555 | BranchMergeProposal.get, merge_proposal1_id) | 1576 | BranchMergeProposal.get, merge_proposal1_id) |
603 | 1556 | 1577 | ||
604 | 1557 | def test_deleteMergeProposalDependent(self): | 1578 | def test_deleteMergeProposalDependent(self): |
605 | 1558 | """break_links enables deleting merge proposal dependant branches.""" | 1579 | """break_links enables deleting merge proposal dependant branches.""" |
606 | 1559 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() | 1580 | merge_proposal1, merge_proposal2 = self.makeMergeProposals() |
608 | 1560 | merge_proposal1.prerequisite_branch.destroySelf(break_references=True) | 1581 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
609 | 1582 | merge_proposal1.prerequisite_branch.destroySelf( | ||
610 | 1583 | break_references=True) | ||
611 | 1561 | self.assertEqual(None, merge_proposal1.prerequisite_branch) | 1584 | self.assertEqual(None, merge_proposal1.prerequisite_branch) |
612 | 1562 | 1585 | ||
613 | 1563 | def test_deleteSourceCodeReviewComment(self): | 1586 | def test_deleteSourceCodeReviewComment(self): |
614 | @@ -1565,7 +1588,8 @@ | |||
615 | 1565 | comment = self.factory.makeCodeReviewComment() | 1588 | comment = self.factory.makeCodeReviewComment() |
616 | 1566 | comment_id = comment.id | 1589 | comment_id = comment.id |
617 | 1567 | branch = comment.branch_merge_proposal.source_branch | 1590 | branch = comment.branch_merge_proposal.source_branch |
619 | 1568 | branch.destroySelf(break_references=True) | 1591 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
620 | 1592 | branch.destroySelf(break_references=True) | ||
621 | 1569 | self.assertRaises( | 1593 | self.assertRaises( |
622 | 1570 | SQLObjectNotFound, CodeReviewComment.get, comment_id) | 1594 | SQLObjectNotFound, CodeReviewComment.get, comment_id) |
623 | 1571 | 1595 | ||
624 | @@ -1574,7 +1598,8 @@ | |||
625 | 1574 | comment = self.factory.makeCodeReviewComment() | 1598 | comment = self.factory.makeCodeReviewComment() |
626 | 1575 | comment_id = comment.id | 1599 | comment_id = comment.id |
627 | 1576 | branch = comment.branch_merge_proposal.target_branch | 1600 | branch = comment.branch_merge_proposal.target_branch |
629 | 1577 | branch.destroySelf(break_references=True) | 1601 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
630 | 1602 | branch.destroySelf(break_references=True) | ||
631 | 1578 | self.assertRaises( | 1603 | self.assertRaises( |
632 | 1579 | SQLObjectNotFound, CodeReviewComment.get, comment_id) | 1604 | SQLObjectNotFound, CodeReviewComment.get, comment_id) |
633 | 1580 | 1605 | ||
634 | @@ -1592,7 +1617,8 @@ | |||
635 | 1592 | bug1.linkBranch(self.branch, self.branch.owner) | 1617 | bug1.linkBranch(self.branch, self.branch.owner) |
636 | 1593 | bug_branch1 = bug1.linked_bugbranches[0] | 1618 | bug_branch1 = bug1.linked_bugbranches[0] |
637 | 1594 | bug_branch1_id = removeSecurityProxy(bug_branch1).id | 1619 | bug_branch1_id = removeSecurityProxy(bug_branch1).id |
639 | 1595 | self.branch.destroySelf(break_references=True) | 1620 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
640 | 1621 | self.branch.destroySelf(break_references=True) | ||
641 | 1596 | self.assertRaises(SQLObjectNotFound, BugBranch.get, bug_branch1_id) | 1622 | self.assertRaises(SQLObjectNotFound, BugBranch.get, bug_branch1_id) |
642 | 1597 | 1623 | ||
643 | 1598 | def test_branchWithSpecRequirements(self): | 1624 | def test_branchWithSpecRequirements(self): |
644 | @@ -1612,7 +1638,8 @@ | |||
645 | 1612 | spec2 = self.factory.makeSpecification() | 1638 | spec2 = self.factory.makeSpecification() |
646 | 1613 | spec2.linkBranch(self.branch, self.branch.owner) | 1639 | spec2.linkBranch(self.branch, self.branch.owner) |
647 | 1614 | spec2_branch_id = self.branch.spec_links[1].id | 1640 | spec2_branch_id = self.branch.spec_links[1].id |
649 | 1615 | self.branch.destroySelf(break_references=True) | 1641 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
650 | 1642 | self.branch.destroySelf(break_references=True) | ||
651 | 1616 | self.assertRaises( | 1643 | self.assertRaises( |
652 | 1617 | SQLObjectNotFound, SpecificationBranch.get, spec1_branch_id) | 1644 | SQLObjectNotFound, SpecificationBranch.get, spec1_branch_id) |
653 | 1618 | self.assertRaises( | 1645 | self.assertRaises( |
654 | @@ -1630,7 +1657,8 @@ | |||
655 | 1630 | """break_links allows deleting a series' branch.""" | 1657 | """break_links allows deleting a series' branch.""" |
656 | 1631 | series1 = self.factory.makeProductSeries(branch=self.branch) | 1658 | series1 = self.factory.makeProductSeries(branch=self.branch) |
657 | 1632 | series2 = self.factory.makeProductSeries(branch=self.branch) | 1659 | series2 = self.factory.makeProductSeries(branch=self.branch) |
659 | 1633 | self.branch.destroySelf(break_references=True) | 1660 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
660 | 1661 | self.branch.destroySelf(break_references=True) | ||
661 | 1634 | self.assertEqual(None, series1.branch) | 1662 | self.assertEqual(None, series1.branch) |
662 | 1635 | self.assertEqual(None, series2.branch) | 1663 | self.assertEqual(None, series2.branch) |
663 | 1636 | 1664 | ||
664 | @@ -1661,7 +1689,8 @@ | |||
665 | 1661 | package.development_version.setBranch, | 1689 | package.development_version.setBranch, |
666 | 1662 | pocket, branch, package.distribution.owner) | 1690 | pocket, branch, package.distribution.owner) |
667 | 1663 | self.assertEqual(False, branch.canBeDeleted()) | 1691 | self.assertEqual(False, branch.canBeDeleted()) |
669 | 1664 | branch.destroySelf(break_references=True) | 1692 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
670 | 1693 | branch.destroySelf(break_references=True) | ||
671 | 1665 | self.assertIs(None, package.getBranch(pocket)) | 1694 | self.assertIs(None, package.getBranch(pocket)) |
672 | 1666 | 1695 | ||
673 | 1667 | def test_branchWithCodeImportRequirements(self): | 1696 | def test_branchWithCodeImportRequirements(self): |
674 | @@ -1676,7 +1705,8 @@ | |||
675 | 1676 | """break_references allows deleting a code import branch.""" | 1705 | """break_references allows deleting a code import branch.""" |
676 | 1677 | code_import = self.factory.makeCodeImport() | 1706 | code_import = self.factory.makeCodeImport() |
677 | 1678 | code_import_id = code_import.id | 1707 | code_import_id = code_import.id |
679 | 1679 | code_import.branch.destroySelf(break_references=True) | 1708 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
680 | 1709 | code_import.branch.destroySelf(break_references=True) | ||
681 | 1680 | self.assertRaises( | 1710 | self.assertRaises( |
682 | 1681 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) | 1711 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) |
683 | 1682 | 1712 | ||
684 | @@ -1685,14 +1715,16 @@ | |||
685 | 1685 | merge_proposal = self.factory.makeBranchMergeProposal() | 1715 | merge_proposal = self.factory.makeBranchMergeProposal() |
686 | 1686 | merge_proposal.nominateReviewer(self.factory.makePerson(), | 1716 | merge_proposal.nominateReviewer(self.factory.makePerson(), |
687 | 1687 | self.factory.makePerson()) | 1717 | self.factory.makePerson()) |
689 | 1688 | merge_proposal.source_branch.destroySelf(break_references=True) | 1718 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
690 | 1719 | merge_proposal.source_branch.destroySelf(break_references=True) | ||
691 | 1689 | 1720 | ||
692 | 1690 | def test_targetBranchWithCodeReviewVoteReference(self): | 1721 | def test_targetBranchWithCodeReviewVoteReference(self): |
693 | 1691 | """Break_references handles CodeReviewVoteReference target branch.""" | 1722 | """Break_references handles CodeReviewVoteReference target branch.""" |
694 | 1692 | merge_proposal = self.factory.makeBranchMergeProposal() | 1723 | merge_proposal = self.factory.makeBranchMergeProposal() |
695 | 1693 | merge_proposal.nominateReviewer(self.factory.makePerson(), | 1724 | merge_proposal.nominateReviewer(self.factory.makePerson(), |
696 | 1694 | self.factory.makePerson()) | 1725 | self.factory.makePerson()) |
698 | 1695 | merge_proposal.target_branch.destroySelf(break_references=True) | 1726 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
699 | 1727 | merge_proposal.target_branch.destroySelf(break_references=True) | ||
700 | 1696 | 1728 | ||
701 | 1697 | def test_snap_requirements(self): | 1729 | def test_snap_requirements(self): |
702 | 1698 | # If a branch is used by a snap package, the deletion requirements | 1730 | # If a branch is used by a snap package, the deletion requirements |
703 | @@ -1706,7 +1738,8 @@ | |||
704 | 1706 | # break_references allows deleting a branch used by a snap package. | 1738 | # break_references allows deleting a branch used by a snap package. |
705 | 1707 | snap1 = self.factory.makeSnap(branch=self.branch) | 1739 | snap1 = self.factory.makeSnap(branch=self.branch) |
706 | 1708 | snap2 = self.factory.makeSnap(branch=self.branch) | 1740 | snap2 = self.factory.makeSnap(branch=self.branch) |
708 | 1709 | self.branch.destroySelf(break_references=True) | 1741 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
709 | 1742 | self.branch.destroySelf(break_references=True) | ||
710 | 1710 | transaction.commit() | 1743 | transaction.commit() |
711 | 1711 | self.assertIsNone(snap1.branch) | 1744 | self.assertIsNone(snap1.branch) |
712 | 1712 | self.assertIsNone(snap2.branch) | 1745 | self.assertIsNone(snap2.branch) |
713 | @@ -1715,7 +1748,8 @@ | |||
714 | 1715 | """ClearDependent.__call__ must clear the prerequisite branch.""" | 1748 | """ClearDependent.__call__ must clear the prerequisite branch.""" |
715 | 1716 | merge_proposal = removeSecurityProxy(self.makeMergeProposals()[0]) | 1749 | merge_proposal = removeSecurityProxy(self.makeMergeProposals()[0]) |
716 | 1717 | with person_logged_in(merge_proposal.prerequisite_branch.owner): | 1750 | with person_logged_in(merge_proposal.prerequisite_branch.owner): |
718 | 1718 | ClearDependentBranch(merge_proposal)() | 1751 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
719 | 1752 | ClearDependentBranch(merge_proposal)() | ||
720 | 1719 | self.assertEqual(None, merge_proposal.prerequisite_branch) | 1753 | self.assertEqual(None, merge_proposal.prerequisite_branch) |
721 | 1720 | 1754 | ||
722 | 1721 | def test_ClearOfficialPackageBranch(self): | 1755 | def test_ClearOfficialPackageBranch(self): |
723 | @@ -1730,14 +1764,16 @@ | |||
724 | 1730 | pocket, branch, package.distribution.owner) | 1764 | pocket, branch, package.distribution.owner) |
725 | 1731 | series_set = getUtility(IFindOfficialBranchLinks) | 1765 | series_set = getUtility(IFindOfficialBranchLinks) |
726 | 1732 | [link] = list(series_set.findForBranch(branch)) | 1766 | [link] = list(series_set.findForBranch(branch)) |
728 | 1733 | ClearOfficialPackageBranch(link)() | 1767 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
729 | 1768 | ClearOfficialPackageBranch(link)() | ||
730 | 1734 | self.assertIs(None, package.getBranch(pocket)) | 1769 | self.assertIs(None, package.getBranch(pocket)) |
731 | 1735 | 1770 | ||
732 | 1736 | def test_ClearSeriesBranch(self): | 1771 | def test_ClearSeriesBranch(self): |
733 | 1737 | """ClearSeriesBranch.__call__ must clear the user branch.""" | 1772 | """ClearSeriesBranch.__call__ must clear the user branch.""" |
734 | 1738 | series = removeSecurityProxy(self.factory.makeProductSeries( | 1773 | series = removeSecurityProxy(self.factory.makeProductSeries( |
735 | 1739 | branch=self.branch)) | 1774 | branch=self.branch)) |
737 | 1740 | ClearSeriesBranch(series, self.branch)() | 1775 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
738 | 1776 | ClearSeriesBranch(series, self.branch)() | ||
739 | 1741 | self.assertEqual(None, series.branch) | 1777 | self.assertEqual(None, series.branch) |
740 | 1742 | 1778 | ||
741 | 1743 | def test_DeletionOperation(self): | 1779 | def test_DeletionOperation(self): |
742 | @@ -1749,7 +1785,8 @@ | |||
743 | 1749 | spec = self.factory.makeSpecification() | 1785 | spec = self.factory.makeSpecification() |
744 | 1750 | spec_link = spec.linkBranch(self.branch, self.branch.owner) | 1786 | spec_link = spec.linkBranch(self.branch, self.branch.owner) |
745 | 1751 | spec_link_id = spec_link.id | 1787 | spec_link_id = spec_link.id |
747 | 1752 | DeletionCallable(spec, 'blah', spec_link.destroySelf)() | 1788 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
748 | 1789 | DeletionCallable(spec, 'blah', spec_link.destroySelf)() | ||
749 | 1753 | self.assertRaises(SQLObjectNotFound, SpecificationBranch.get, | 1790 | self.assertRaises(SQLObjectNotFound, SpecificationBranch.get, |
750 | 1754 | spec_link_id) | 1791 | spec_link_id) |
751 | 1755 | 1792 | ||
752 | @@ -1757,7 +1794,8 @@ | |||
753 | 1757 | """DeleteCodeImport.__call__ must delete the CodeImport.""" | 1794 | """DeleteCodeImport.__call__ must delete the CodeImport.""" |
754 | 1758 | code_import = self.factory.makeCodeImport() | 1795 | code_import = self.factory.makeCodeImport() |
755 | 1759 | code_import_id = code_import.id | 1796 | code_import_id = code_import.id |
757 | 1760 | DeleteCodeImport(code_import)() | 1797 | with dbuser(config.IBranchDeleteJobSource.dbuser): |
758 | 1798 | DeleteCodeImport(code_import)() | ||
759 | 1761 | self.assertRaises( | 1799 | self.assertRaises( |
760 | 1762 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) | 1800 | NotFoundError, getUtility(ICodeImportSet).get, code_import_id) |
761 | 1763 | 1801 | ||
762 | 1764 | 1802 | ||
763 | === modified file 'lib/lp/code/model/tests/test_branchjob.py' | |||
764 | --- lib/lp/code/model/tests/test_branchjob.py 2018-03-30 20:42:14 +0000 | |||
765 | +++ lib/lp/code/model/tests/test_branchjob.py 2019-03-21 15:55:38 +0000 | |||
766 | @@ -1,4 +1,4 @@ | |||
768 | 1 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
769 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
770 | 3 | 3 | ||
771 | 4 | """Tests for BranchJobs.""" | 4 | """Tests for BranchJobs.""" |
772 | @@ -16,7 +16,10 @@ | |||
773 | 16 | from bzrlib.bzrdir import BzrDir | 16 | from bzrlib.bzrdir import BzrDir |
774 | 17 | from bzrlib.revision import NULL_REVISION | 17 | from bzrlib.revision import NULL_REVISION |
775 | 18 | from bzrlib.transport import get_transport | 18 | from bzrlib.transport import get_transport |
777 | 19 | from fixtures import MockPatch | 19 | from fixtures import ( |
778 | 20 | FakeLogger, | ||
779 | 21 | MockPatch, | ||
780 | 22 | ) | ||
781 | 20 | import pytz | 23 | import pytz |
782 | 21 | from sqlobject import SQLObjectNotFound | 24 | from sqlobject import SQLObjectNotFound |
783 | 22 | from storm.locals import Store | 25 | from storm.locals import Store |
784 | @@ -31,6 +34,7 @@ | |||
785 | 31 | RepositoryFormat, | 34 | RepositoryFormat, |
786 | 32 | ) | 35 | ) |
787 | 33 | from lp.code.enums import ( | 36 | from lp.code.enums import ( |
788 | 37 | BranchDeletionStatus, | ||
789 | 34 | BranchMergeProposalStatus, | 38 | BranchMergeProposalStatus, |
790 | 35 | BranchSubscriptionDiffSize, | 39 | BranchSubscriptionDiffSize, |
791 | 36 | BranchSubscriptionNotificationLevel, | 40 | BranchSubscriptionNotificationLevel, |
792 | @@ -38,6 +42,8 @@ | |||
793 | 38 | ) | 42 | ) |
794 | 39 | from lp.code.errors import AlreadyLatestFormat | 43 | from lp.code.errors import AlreadyLatestFormat |
795 | 40 | from lp.code.interfaces.branchjob import ( | 44 | from lp.code.interfaces.branchjob import ( |
796 | 45 | IBranchDeleteJob, | ||
797 | 46 | IBranchDeleteJobSource, | ||
798 | 41 | IBranchJob, | 47 | IBranchJob, |
799 | 42 | IBranchScanJob, | 48 | IBranchScanJob, |
800 | 43 | IBranchUpgradeJob, | 49 | IBranchUpgradeJob, |
801 | @@ -46,6 +52,7 @@ | |||
802 | 46 | IRevisionMailJob, | 52 | IRevisionMailJob, |
803 | 47 | IRosettaUploadJob, | 53 | IRosettaUploadJob, |
804 | 48 | ) | 54 | ) |
805 | 55 | from lp.code.interfaces.branchlookup import IBranchLookup | ||
806 | 49 | from lp.code.model.branchjob import ( | 56 | from lp.code.model.branchjob import ( |
807 | 50 | BranchJob, | 57 | BranchJob, |
808 | 51 | BranchJobDerived, | 58 | BranchJobDerived, |
809 | @@ -72,6 +79,7 @@ | |||
810 | 72 | from lp.services.job.model.job import Job | 79 | from lp.services.job.model.job import Job |
811 | 73 | from lp.services.job.runner import JobRunner | 80 | from lp.services.job.runner import JobRunner |
812 | 74 | from lp.services.job.tests import block_on_job | 81 | from lp.services.job.tests import block_on_job |
813 | 82 | from lp.services.mail.sendmail import format_address_for_person | ||
814 | 75 | from lp.services.osutils import override_environ | 83 | from lp.services.osutils import override_environ |
815 | 76 | from lp.services.webapp import canonical_url | 84 | from lp.services.webapp import canonical_url |
816 | 77 | from lp.testing import ( | 85 | from lp.testing import ( |
817 | @@ -86,6 +94,7 @@ | |||
818 | 86 | CeleryBzrsyncdJobLayer, | 94 | CeleryBzrsyncdJobLayer, |
819 | 87 | DatabaseFunctionalLayer, | 95 | DatabaseFunctionalLayer, |
820 | 88 | LaunchpadZopelessLayer, | 96 | LaunchpadZopelessLayer, |
821 | 97 | ZopelessDatabaseLayer, | ||
822 | 89 | ) | 98 | ) |
823 | 90 | from lp.testing.librarianhelpers import get_newest_librarian_file | 99 | from lp.testing.librarianhelpers import get_newest_librarian_file |
824 | 91 | from lp.testing.mail_helpers import pop_notifications | 100 | from lp.testing.mail_helpers import pop_notifications |
825 | @@ -1390,3 +1399,87 @@ | |||
826 | 1390 | os.makedirs(branch_path) | 1399 | os.makedirs(branch_path) |
827 | 1391 | self.runReadyJobs() | 1400 | self.runReadyJobs() |
828 | 1392 | self.assertFalse(os.path.exists(branch_path)) | 1401 | self.assertFalse(os.path.exists(branch_path)) |
829 | 1402 | |||
830 | 1403 | |||
831 | 1404 | class TestBranchDeleteJob(TestCaseWithFactory): | ||
832 | 1405 | |||
833 | 1406 | layer = ZopelessDatabaseLayer | ||
834 | 1407 | |||
835 | 1408 | def test_providesInterface(self): | ||
836 | 1409 | branch = self.factory.makeAnyBranch() | ||
837 | 1410 | requester = branch.registrant | ||
838 | 1411 | job = getUtility(IBranchDeleteJobSource).create(branch, requester) | ||
839 | 1412 | self.assertProvides(job, IBranchDeleteJob) | ||
840 | 1413 | |||
841 | 1414 | def test_run(self): | ||
842 | 1415 | branch = self.factory.makeAnyBranch() | ||
843 | 1416 | branch_id = branch.id | ||
844 | 1417 | requester = branch.registrant | ||
845 | 1418 | job = getUtility(IBranchDeleteJobSource).create(branch, requester) | ||
846 | 1419 | removeSecurityProxy(branch).deletion_status = ( | ||
847 | 1420 | BranchDeletionStatus.DELETING) | ||
848 | 1421 | logger = self.useFixture(FakeLogger()) | ||
849 | 1422 | with dbuser(config.IBranchDeleteJobSource.dbuser): | ||
850 | 1423 | job.run() | ||
851 | 1424 | self.assertEqual('', logger.output) | ||
852 | 1425 | self.assertIsNone(getUtility(IBranchLookup).get(branch_id)) | ||
853 | 1426 | |||
854 | 1427 | def test_already_deleted(self): | ||
855 | 1428 | branch = self.factory.makeAnyBranch() | ||
856 | 1429 | branch_name = branch.unique_name | ||
857 | 1430 | requester = branch.registrant | ||
858 | 1431 | job = getUtility(IBranchDeleteJobSource).create(branch, requester) | ||
859 | 1432 | branch.destroySelf() | ||
860 | 1433 | logger = self.useFixture(FakeLogger()) | ||
861 | 1434 | with dbuser(config.IBranchDeleteJobSource.dbuser): | ||
862 | 1435 | job.run() | ||
863 | 1436 | self.assertEqual( | ||
864 | 1437 | 'Skipping branch %s because it has already been ' | ||
865 | 1438 | 'deleted.\n' % branch_name, | ||
866 | 1439 | logger.output) | ||
867 | 1440 | |||
868 | 1441 | def test_not_deleting(self): | ||
869 | 1442 | # The job skips branches that aren't DELETING. This shouldn't be | ||
870 | 1443 | # possible in practice, but is a guard against accidents. | ||
871 | 1444 | branch = self.factory.makeAnyBranch() | ||
872 | 1445 | branch_id = branch.id | ||
873 | 1446 | branch_name = branch.unique_name | ||
874 | 1447 | self.assertNotEqual( | ||
875 | 1448 | BranchDeletionStatus.DELETING, branch.deletion_status) | ||
876 | 1449 | requester = branch.registrant | ||
877 | 1450 | job = getUtility(IBranchDeleteJobSource).create(branch, requester) | ||
878 | 1451 | logger = self.useFixture(FakeLogger()) | ||
879 | 1452 | with dbuser(config.IBranchDeleteJobSource.dbuser): | ||
880 | 1453 | job.run() | ||
881 | 1454 | self.assertEqual( | ||
882 | 1455 | 'Skipping branch %s because its deletion status is not ' | ||
883 | 1456 | 'DELETING.\n' % branch_name, | ||
884 | 1457 | logger.output) | ||
885 | 1458 | self.assertEqual(branch, getUtility(IBranchLookup).get(branch_id)) | ||
886 | 1459 | |||
887 | 1460 | def test_error(self): | ||
888 | 1461 | # If deleting the branch fails, an error message is sent to the | ||
889 | 1462 | # requester and the deletion status is set back to ACTIVE. Most | ||
890 | 1463 | # cases where this can happen are caught earlier, but this can still | ||
891 | 1464 | # happen if a branch stacked on the to-be-deleted branch is created | ||
892 | 1465 | # between the deletion job being created and being run. | ||
893 | 1466 | branch = self.factory.makeAnyBranch() | ||
894 | 1467 | branch_id = branch.id | ||
895 | 1468 | requester = branch.registrant | ||
896 | 1469 | job = getUtility(IBranchDeleteJobSource).create(branch, requester) | ||
897 | 1470 | removeSecurityProxy(branch).deletion_status = ( | ||
898 | 1471 | BranchDeletionStatus.DELETING) | ||
899 | 1472 | self.factory.makeAnyBranch(stacked_on=branch) | ||
900 | 1473 | logger = self.useFixture(FakeLogger()) | ||
901 | 1474 | with dbuser(config.IBranchDeleteJobSource.dbuser): | ||
902 | 1475 | JobRunner([job]).runJobHandleError(job) | ||
903 | 1476 | self.assertIn( | ||
904 | 1477 | 'failed with user error CannotDeleteBranch', logger.output) | ||
905 | 1478 | self.assertEqual(branch, getUtility(IBranchLookup).get(branch_id)) | ||
906 | 1479 | self.assertEqual(BranchDeletionStatus.ACTIVE, branch.deletion_status) | ||
907 | 1480 | self.assertEqual([], self.oopses) | ||
908 | 1481 | [mail] = pop_notifications() | ||
909 | 1482 | self.assertEqual(format_address_for_person(requester), mail['to']) | ||
910 | 1483 | self.assertEqual( | ||
911 | 1484 | 'Launchpad error while deleting a branch', mail['subject']) | ||
912 | 1485 | self.assertIn('Cannot delete branch', mail.get_payload(decode=True)) | ||
913 | 1393 | 1486 | ||
914 | === modified file 'lib/lp/code/stories/webservice/xx-branch.txt' | |||
915 | --- lib/lp/code/stories/webservice/xx-branch.txt 2018-05-13 10:35:52 +0000 | |||
916 | +++ lib/lp/code/stories/webservice/xx-branch.txt 2019-03-21 15:55:38 +0000 | |||
917 | @@ -112,6 +112,7 @@ | |||
918 | 112 | control_format: None | 112 | control_format: None |
919 | 113 | date_created: u'2009-01-01T00:00:00+00:00' | 113 | date_created: u'2009-01-01T00:00:00+00:00' |
920 | 114 | date_last_modified: u'2009-01-01T00:00:00+00:00' | 114 | date_last_modified: u'2009-01-01T00:00:00+00:00' |
921 | 115 | deletion_status: u'Active' | ||
922 | 115 | dependent_branches_collection_link: u'.../~eric/fooix/trunk/dependent_branches' | 116 | dependent_branches_collection_link: u'.../~eric/fooix/trunk/dependent_branches' |
923 | 116 | description: None | 117 | description: None |
924 | 117 | display_name: u'lp://dev/~eric/fooix/trunk' | 118 | display_name: u'lp://dev/~eric/fooix/trunk' |
925 | 118 | 119 | ||
926 | === modified file 'lib/lp/services/config/schema-lazr.conf' | |||
927 | --- lib/lp/services/config/schema-lazr.conf 2019-03-18 12:22:55 +0000 | |||
928 | +++ lib/lp/services/config/schema-lazr.conf 2019-03-21 15:55:38 +0000 | |||
929 | @@ -1867,6 +1867,7 @@ | |||
930 | 1867 | # dbuser, the crontab_group, and the module that the job source class | 1867 | # dbuser, the crontab_group, and the module that the job source class |
931 | 1868 | # can be loaded from. | 1868 | # can be loaded from. |
932 | 1869 | job_sources: | 1869 | job_sources: |
933 | 1870 | IBranchDeleteJobSource, | ||
934 | 1870 | IBranchModifiedMailJobSource, | 1871 | IBranchModifiedMailJobSource, |
935 | 1871 | ICommercialExpiredJobSource, | 1872 | ICommercialExpiredJobSource, |
936 | 1872 | IExpiringMembershipNotificationJobSource, | 1873 | IExpiringMembershipNotificationJobSource, |
937 | @@ -1888,6 +1889,11 @@ | |||
938 | 1888 | ITeamJoinNotificationJobSource, | 1889 | ITeamJoinNotificationJobSource, |
939 | 1889 | IThirtyDayCommercialExpirationJobSource | 1890 | IThirtyDayCommercialExpirationJobSource |
940 | 1890 | 1891 | ||
941 | 1892 | [IBranchDeleteJobSource] | ||
942 | 1893 | module: lp.code.interfaces.branchjob | ||
943 | 1894 | dbuser: branch-delete-job | ||
944 | 1895 | crontab_group: MAIN | ||
945 | 1896 | |||
946 | 1891 | [IBranchMergeProposalJobSource] | 1897 | [IBranchMergeProposalJobSource] |
947 | 1892 | module: lp.code.interfaces.branchmergeproposal | 1898 | module: lp.code.interfaces.branchmergeproposal |
948 | 1893 | dbuser: merge-proposal-jobs | 1899 | dbuser: merge-proposal-jobs |
This seems unnecessary since adding some more indexes has made deletion fast enough, so withdrawing this until we have a clear need.