Merge ~cjwatson/launchpad:refactor-build-retry-cancel-rescore into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 618b6d0313a33166db0d82bb11bb52ececfee337
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:refactor-build-retry-cancel-rescore
Merge into: launchpad:master
Diff against target: 1627 lines (+316/-627)
24 files modified
lib/lp/_schema_circular_imports.py (+0/-2)
lib/lp/buildmaster/interfaces/buildfarmjob.py (+93/-3)
lib/lp/buildmaster/interfaces/packagebuild.py (+11/-3)
lib/lp/buildmaster/interfaces/webservice.py (+7/-1)
lib/lp/buildmaster/model/buildfarmjob.py (+77/-0)
lib/lp/buildmaster/tests/test_buildfarmjob.py (+3/-1)
lib/lp/buildmaster/tests/test_packagebuild.py (+1/-0)
lib/lp/charms/interfaces/charmrecipebuild.py (+14/-53)
lib/lp/charms/model/charmrecipebuild.py (+2/-60)
lib/lp/code/configure.zcml (+3/-0)
lib/lp/code/interfaces/sourcepackagerecipebuild.py (+21/-39)
lib/lp/code/model/sourcepackagerecipebuild.py (+1/-25)
lib/lp/oci/interfaces/ocirecipebuild.py (+13/-53)
lib/lp/oci/model/ocirecipebuild.py (+2/-60)
lib/lp/security.py (+0/-12)
lib/lp/snappy/interfaces/snapbuild.py (+14/-53)
lib/lp/snappy/model/snapbuild.py (+2/-60)
lib/lp/soyuz/interfaces/binarypackagebuild.py (+26/-57)
lib/lp/soyuz/interfaces/livefsbuild.py (+14/-40)
lib/lp/soyuz/interfaces/webservice.py (+1/-5)
lib/lp/soyuz/model/binarypackagebuild.py (+5/-60)
lib/lp/soyuz/model/livefsbuild.py (+1/-32)
lib/lp/soyuz/stories/webservice/xx-builds.txt (+3/-4)
lib/lp/soyuz/tests/test_build.py (+2/-4)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+413831@code.launchpad.net

Commit message

Push retry/cancel/rescore down to IBuildFarmJob

Description of the change

Previously, each build type implemented retry/cancel/rescore logic itself, even though the implementations were almost identical. Push this down to `IBuildFarmJob` and `BuildFarmJobMixin` so that creating a new build type involves a bit less copy-and-paste.

I had to split `IBuildFarmJob` into `IBuildFarmJob{View,Edit,Admin}`, since the retry/cancel/rescore operations require varying amounts of privilege.

Source package recipe builds gain a `rescore` API operation, and live filesystem and source package recipe builds both gain `can_be_retried` and `retry` methods (although `can_be_retried` is currently always False for those build types, and `retry` will always fail, but that might change in future).

Attempting to retry or rescore a build that cannot be retried or rescored respectively now consistently returns HTTP 400 rather than returning HTTP 500 in some cases.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

LGTM + tests pass

review: Approve
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py
2index 09fe6e4..af12d9a 100644
3--- a/lib/lp/_schema_circular_imports.py
4+++ b/lib/lp/_schema_circular_imports.py
5@@ -742,8 +742,6 @@ patch_entry_explicit_version(IArchiveSubscriber, 'beta')
6
7 # IBinaryPackageBuild
8 patch_entry_explicit_version(IBinaryPackageBuild, 'beta')
9-patch_operations_explicit_version(
10- IBinaryPackageBuild, 'beta', "rescore", "retry")
11
12 # IBinaryPackagePublishingHistory
13 patch_entry_explicit_version(IBinaryPackagePublishingHistory, 'beta')
14diff --git a/lib/lp/buildmaster/interfaces/buildfarmjob.py b/lib/lp/buildmaster/interfaces/buildfarmjob.py
15index 86d62e5..9e8d461 100644
16--- a/lib/lp/buildmaster/interfaces/buildfarmjob.py
17+++ b/lib/lp/buildmaster/interfaces/buildfarmjob.py
18@@ -4,17 +4,28 @@
19 """Interface for Soyuz build farm jobs."""
20
21 __all__ = [
22+ 'CannotBeRescored',
23+ 'CannotBeRetried',
24 'IBuildFarmJob',
25+ 'IBuildFarmJobAdmin',
26 'IBuildFarmJobDB',
27+ 'IBuildFarmJobEdit',
28 'IBuildFarmJobSet',
29 'IBuildFarmJobSource',
30+ 'IBuildFarmJobView',
31 'InconsistentBuildFarmJobError',
32 'ISpecificBuildFarmJobSource',
33 ]
34
35+import http.client
36+
37 from lazr.restful.declarations import (
38+ error_status,
39+ export_write_operation,
40 exported,
41 exported_as_webservice_entry,
42+ operation_for_version,
43+ operation_parameters,
44 )
45 from lazr.restful.fields import Reference
46 from zope.interface import (
47@@ -49,6 +60,22 @@ class InconsistentBuildFarmJobError(Exception):
48 """
49
50
51+@error_status(http.client.BAD_REQUEST)
52+class CannotBeRetried(Exception):
53+ """Raised when retrying a build that cannot be retried."""
54+
55+ def __init__(self, build_id):
56+ super().__init__("Build %s cannot be retried." % build_id)
57+
58+
59+@error_status(http.client.BAD_REQUEST)
60+class CannotBeRescored(Exception):
61+ """Raised when rescoring a build that cannot be rescored."""
62+
63+ def __init__(self, build_id):
64+ super().__init__("Build %s cannot be rescored." % build_id)
65+
66+
67 class IBuildFarmJobDB(Interface):
68 """Operations on a `BuildFarmJob` DB row.
69
70@@ -63,9 +90,8 @@ class IBuildFarmJobDB(Interface):
71 description=_("The specific type of job."))
72
73
74-@exported_as_webservice_entry(as_of='beta')
75-class IBuildFarmJob(Interface):
76- """Operations that jobs for the build farm must implement."""
77+class IBuildFarmJobView(Interface):
78+ """`IBuildFarmJob` attributes that require launchpad.View."""
79
80 id = Attribute('The build farm job ID.')
81
82@@ -238,6 +264,70 @@ class IBuildFarmJob(Interface):
83 "Newline-separated list of repositories to be used to retrieve any "
84 "external build-dependencies when performing this build.")
85
86+ can_be_rescored = exported(Bool(
87+ title=_("Can be rescored"), required=True, readonly=True,
88+ description=_(
89+ "Whether this build record can be rescored manually.")))
90+
91+ can_be_retried = exported(Bool(
92+ title=_("Can be retried"), required=True, readonly=True,
93+ description=_("Whether this build record can be retried.")))
94+
95+ can_be_cancelled = exported(Bool(
96+ title=_("Can be cancelled"), required=True, readonly=True,
97+ description=_("Whether this build record can be cancelled.")))
98+
99+
100+class IBuildFarmJobEdit(Interface):
101+ """`IBuildFarmJob` methods that require launchpad.Edit."""
102+
103+ def resetBuild():
104+ """Reset this build record to a clean state.
105+
106+ This method should only be called by `BuildFarmJobMixin.retry`, but
107+ subclasses may override it to reset additional state.
108+ """
109+
110+ @export_write_operation()
111+ @operation_for_version("devel")
112+ def retry():
113+ """Restore the build record to its initial state.
114+
115+ Build record loses its history, is moved to NEEDSBUILD and a new
116+ non-scored BuildQueue entry is created for it.
117+ """
118+
119+ @export_write_operation()
120+ @operation_for_version("devel")
121+ def cancel():
122+ """Cancel the build if it is either pending or in progress.
123+
124+ Check the can_be_cancelled property prior to calling this method to
125+ find out if cancelling the build is possible.
126+
127+ If the build is in progress, it is marked as CANCELLING until the
128+ buildd manager terminates the build and marks it CANCELLED. If the
129+ build is not in progress, it is marked CANCELLED immediately and is
130+ removed from the build queue.
131+
132+ If the build is not in a cancellable state, this method is a no-op.
133+ """
134+
135+
136+class IBuildFarmJobAdmin(Interface):
137+ """`IBuildFarmJob` methods that require launchpad.Admin."""
138+
139+ @operation_parameters(score=Int(title=_("Score"), required=True))
140+ @export_write_operation()
141+ @operation_for_version("devel")
142+ def rescore(score):
143+ """Change the build's score."""
144+
145+
146+@exported_as_webservice_entry(as_of='beta')
147+class IBuildFarmJob(IBuildFarmJobView, IBuildFarmJobEdit, IBuildFarmJobAdmin):
148+ """Operations that jobs for the build farm must implement."""
149+
150
151 class ISpecificBuildFarmJobSource(Interface):
152 """A utility for retrieving objects of a specific IBuildFarmJob type.
153diff --git a/lib/lp/buildmaster/interfaces/packagebuild.py b/lib/lp/buildmaster/interfaces/packagebuild.py
154index 8d6b836..3af81f7 100644
155--- a/lib/lp/buildmaster/interfaces/packagebuild.py
156+++ b/lib/lp/buildmaster/interfaces/packagebuild.py
157@@ -5,6 +5,7 @@
158
159 __all__ = [
160 'IPackageBuild',
161+ 'IPackageBuildView',
162 ]
163
164
165@@ -18,7 +19,10 @@ from zope.schema import (
166 )
167
168 from lp import _
169-from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
170+from lp.buildmaster.interfaces.buildfarmjob import (
171+ IBuildFarmJob,
172+ IBuildFarmJobView,
173+ )
174 from lp.registry.interfaces.distribution import IDistribution
175 from lp.registry.interfaces.distroseries import IDistroSeries
176 from lp.registry.interfaces.pocket import PackagePublishingPocket
177@@ -26,8 +30,8 @@ from lp.services.librarian.interfaces import ILibraryFileAlias
178 from lp.soyuz.interfaces.archive import IArchive
179
180
181-class IPackageBuild(IBuildFarmJob):
182- """Attributes and operations specific to package build jobs."""
183+class IPackageBuildView(IBuildFarmJobView):
184+ """`IPackageBuild` methods that require launchpad.View."""
185
186 archive = exported(
187 Reference(
188@@ -95,3 +99,7 @@ class IPackageBuild(IBuildFarmJob):
189
190 :param changes: Changes file from the upload.
191 """
192+
193+
194+class IPackageBuild(IPackageBuildView, IBuildFarmJob):
195+ """Attributes and operations specific to package build jobs."""
196diff --git a/lib/lp/buildmaster/interfaces/webservice.py b/lib/lp/buildmaster/interfaces/webservice.py
197index b899a8f..e6ca23e 100644
198--- a/lib/lp/buildmaster/interfaces/webservice.py
199+++ b/lib/lp/buildmaster/interfaces/webservice.py
200@@ -10,6 +10,8 @@ which tells `lazr.restful` that it should look for webservice exports here.
201 """
202
203 __all__ = [
204+ 'CannotBeRescored',
205+ 'CannotBeRetried',
206 'IBuilder',
207 'IBuilderSet',
208 'IBuildFarmJob',
209@@ -21,7 +23,11 @@ from lp.buildmaster.interfaces.builder import (
210 IBuilder,
211 IBuilderSet,
212 )
213-from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
214+from lp.buildmaster.interfaces.buildfarmjob import (
215+ CannotBeRescored,
216+ CannotBeRetried,
217+ IBuildFarmJob,
218+ )
219 from lp.buildmaster.interfaces.processor import (
220 IProcessor,
221 IProcessorSet,
222diff --git a/lib/lp/buildmaster/model/buildfarmjob.py b/lib/lp/buildmaster/model/buildfarmjob.py
223index 876546d..181be99 100644
224--- a/lib/lp/buildmaster/model/buildfarmjob.py
225+++ b/lib/lp/buildmaster/model/buildfarmjob.py
226@@ -34,6 +34,8 @@ from lp.buildmaster.enums import (
227 BuildStatus,
228 )
229 from lp.buildmaster.interfaces.buildfarmjob import (
230+ CannotBeRescored,
231+ CannotBeRetried,
232 IBuildFarmJob,
233 IBuildFarmJobDB,
234 IBuildFarmJobSet,
235@@ -262,6 +264,81 @@ class BuildFarmJobMixin:
236 del get_property_cache(self).buildqueue_record
237 return queue_entry
238
239+ @property
240+ def can_be_retried(self):
241+ """See `IBuildFarmJob`.
242+
243+ Implementations should override this method to first check whether
244+ their associated build behaviour would accept the build if it
245+ succeeded.
246+ """
247+ failed_statuses = [
248+ BuildStatus.FAILEDTOBUILD,
249+ BuildStatus.MANUALDEPWAIT,
250+ BuildStatus.CHROOTWAIT,
251+ BuildStatus.FAILEDTOUPLOAD,
252+ BuildStatus.CANCELLED,
253+ BuildStatus.SUPERSEDED,
254+ ]
255+
256+ # If the build is currently in any of the failed states,
257+ # it may be retried.
258+ return self.status in failed_statuses
259+
260+ @property
261+ def can_be_rescored(self):
262+ """See `IBuildFarmJob`."""
263+ return (
264+ self.buildqueue_record is not None and
265+ self.status is BuildStatus.NEEDSBUILD)
266+
267+ @property
268+ def can_be_cancelled(self):
269+ """See `IBuildFarmJob`."""
270+ if not self.buildqueue_record:
271+ return False
272+
273+ cancellable_statuses = [
274+ BuildStatus.BUILDING,
275+ BuildStatus.NEEDSBUILD,
276+ ]
277+ return self.status in cancellable_statuses
278+
279+ def resetBuild(self):
280+ """See `IBuildFarmJob`."""
281+ self.build_farm_job.status = self.status = BuildStatus.NEEDSBUILD
282+ self.build_farm_job.date_finished = self.date_finished = None
283+ self.date_started = None
284+ self.build_farm_job.builder = self.builder = None
285+ self.log = None
286+ self.upload_log = None
287+ self.dependencies = None
288+ self.failure_count = 0
289+
290+ def retry(self):
291+ """See `IBuildFarmJob`."""
292+ if not self.can_be_retried:
293+ raise CannotBeRetried(self.id)
294+
295+ self.resetBuild()
296+ self.queueBuild()
297+
298+ def rescore(self, score):
299+ """See `IBuildFarmJob`."""
300+ if not self.can_be_rescored:
301+ raise CannotBeRescored(self.id)
302+
303+ self.buildqueue_record.manualScore(score)
304+
305+ def cancel(self):
306+ """See `IBuildFarmJob`."""
307+ if not self.can_be_cancelled:
308+ return
309+ # BuildQueue.cancel() will decide whether to go straight to
310+ # CANCELLED, or go through CANCELLING to let buildd-manager clean up
311+ # the builder.
312+ self.buildqueue_record.cancel()
313+
314
315 class SpecificBuildFarmJobSourceMixin:
316
317diff --git a/lib/lp/buildmaster/tests/test_buildfarmjob.py b/lib/lp/buildmaster/tests/test_buildfarmjob.py
318index a10dfb0..911582f 100644
319--- a/lib/lp/buildmaster/tests/test_buildfarmjob.py
320+++ b/lib/lp/buildmaster/tests/test_buildfarmjob.py
321@@ -21,6 +21,7 @@ from lp.buildmaster.enums import (
322 BuildStatus,
323 )
324 from lp.buildmaster.interfaces.buildfarmjob import (
325+ CannotBeRetried,
326 IBuildFarmJob,
327 IBuildFarmJobSet,
328 IBuildFarmJobSource,
329@@ -119,6 +120,7 @@ class TestBuildFarmJobMixin(TestCaseWithFactory):
330
331 def test_providesInterface(self):
332 # BuildFarmJobMixin derivatives provide IBuildFarmJob
333+ login('admin@canonical.com')
334 self.assertProvides(self.build_farm_job, IBuildFarmJob)
335
336 def test_duration_none(self):
337@@ -149,7 +151,7 @@ class TestBuildFarmJobMixin(TestCaseWithFactory):
338 def test_edit_build_farm_job(self):
339 # Users with edit access can update attributes.
340 login('admin@canonical.com')
341- self.assertRaises(AssertionError, self.build_farm_job.retry)
342+ self.assertRaises(CannotBeRetried, self.build_farm_job.retry)
343
344 def test_updateStatus_sets_status(self):
345 # updateStatus always sets status.
346diff --git a/lib/lp/buildmaster/tests/test_packagebuild.py b/lib/lp/buildmaster/tests/test_packagebuild.py
347index 90d5adb..8a1b51c 100644
348--- a/lib/lp/buildmaster/tests/test_packagebuild.py
349+++ b/lib/lp/buildmaster/tests/test_packagebuild.py
350@@ -35,6 +35,7 @@ class TestPackageBuildMixin(TestCaseWithFactory):
351
352 def test_providesInterface(self):
353 # PackageBuild provides IPackageBuild
354+ login('admin@canonical.com')
355 self.assertProvides(self.package_build, IPackageBuild)
356
357 def test_updateStatus_MANUALDEPWAIT_sets_dependencies(self):
358diff --git a/lib/lp/charms/interfaces/charmrecipebuild.py b/lib/lp/charms/interfaces/charmrecipebuild.py
359index 4a33e19..2bbfe04 100644
360--- a/lib/lp/charms/interfaces/charmrecipebuild.py
361+++ b/lib/lp/charms/interfaces/charmrecipebuild.py
362@@ -23,7 +23,6 @@ from lazr.restful.declarations import (
363 exported,
364 exported_as_webservice_entry,
365 operation_for_version,
366- operation_parameters,
367 )
368 from lazr.restful.fields import (
369 CollectionField,
370@@ -43,8 +42,15 @@ from zope.schema import (
371 )
372
373 from lp import _
374-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
375-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
376+from lp.buildmaster.interfaces.buildfarmjob import (
377+ IBuildFarmJobAdmin,
378+ IBuildFarmJobEdit,
379+ ISpecificBuildFarmJobSource,
380+ )
381+from lp.buildmaster.interfaces.packagebuild import (
382+ IPackageBuild,
383+ IPackageBuildView,
384+ )
385 from lp.charms.interfaces.charmrecipe import (
386 ICharmRecipe,
387 ICharmRecipeBuildRequest,
388@@ -100,7 +106,7 @@ class CharmRecipeBuildStoreUploadStatus(EnumeratedType):
389 """)
390
391
392-class ICharmRecipeBuildView(IPackageBuild):
393+class ICharmRecipeBuildView(IPackageBuildView):
394 """`ICharmRecipeBuild` attributes that require launchpad.View."""
395
396 build_request = Reference(
397@@ -141,21 +147,6 @@ class ICharmRecipeBuildView(IPackageBuild):
398 title=_("Score of the related build farm job (if any)."),
399 required=False, readonly=True))
400
401- can_be_rescored = exported(Bool(
402- title=_("Can be rescored"),
403- required=True, readonly=True,
404- description=_("Whether this build record can be rescored manually.")))
405-
406- can_be_retried = exported(Bool(
407- title=_("Can be retried"),
408- required=False, readonly=True,
409- description=_("Whether this build record can be retried.")))
410-
411- can_be_cancelled = exported(Bool(
412- title=_("Can be cancelled"),
413- required=True, readonly=True,
414- description=_("Whether this build record can be cancelled.")))
415-
416 eta = Datetime(
417 title=_("The datetime when the build job is estimated to complete."),
418 readonly=True)
419@@ -229,7 +220,7 @@ class ICharmRecipeBuildView(IPackageBuild):
420 """
421
422
423-class ICharmRecipeBuildEdit(Interface):
424+class ICharmRecipeBuildEdit(IBuildFarmJobEdit):
425 """`ICharmRecipeBuild` methods that require launchpad.Edit."""
426
427 def addFile(lfa):
428@@ -248,48 +239,18 @@ class ICharmRecipeBuildEdit(Interface):
429 where an upload can be scheduled.
430 """
431
432- @export_write_operation()
433- @operation_for_version("devel")
434- def retry():
435- """Restore the build record to its initial state.
436-
437- Build record loses its history, is moved to NEEDSBUILD and a new
438- non-scored BuildQueue entry is created for it.
439- """
440-
441- @export_write_operation()
442- @operation_for_version("devel")
443- def cancel():
444- """Cancel the build if it is either pending or in progress.
445-
446- Check the can_be_cancelled property prior to calling this method to
447- find out if cancelling the build is possible.
448-
449- If the build is in progress, it is marked as CANCELLING until the
450- buildd manager terminates the build and marks it CANCELLED. If the
451- build is not in progress, it is marked CANCELLED immediately and is
452- removed from the build queue.
453-
454- If the build is not in a cancellable state, this method is a no-op.
455- """
456-
457
458-class ICharmRecipeBuildAdmin(Interface):
459+class ICharmRecipeBuildAdmin(IBuildFarmJobAdmin):
460 """`ICharmRecipeBuild` methods that require launchpad.Admin."""
461
462- @operation_parameters(score=Int(title=_("Score"), required=True))
463- @export_write_operation()
464- @operation_for_version("devel")
465- def rescore(score):
466- """Change the build's score."""
467-
468
469 # XXX cjwatson 2021-09-15 bug=760849: "beta" is a lie to get WADL
470 # generation working. Individual attributes must set their version to
471 # "devel".
472 @exported_as_webservice_entry(as_of="beta")
473 class ICharmRecipeBuild(
474- ICharmRecipeBuildView, ICharmRecipeBuildEdit, ICharmRecipeBuildAdmin):
475+ ICharmRecipeBuildView, ICharmRecipeBuildEdit, ICharmRecipeBuildAdmin,
476+ IPackageBuild):
477 """A build record for a charm recipe."""
478
479
480diff --git a/lib/lp/charms/model/charmrecipebuild.py b/lib/lp/charms/model/charmrecipebuild.py
481index cbcdd9e..2374924 100644
482--- a/lib/lp/charms/model/charmrecipebuild.py
483+++ b/lib/lp/charms/model/charmrecipebuild.py
484@@ -220,70 +220,12 @@ class CharmRecipeBuild(PackageBuildMixin, StormBase):
485
486 @property
487 def can_be_retried(self):
488- """See `ICharmRecipeBuild`."""
489+ """See `IBuildFarmJob`."""
490 # First check that the behaviour would accept the build if it
491 # succeeded.
492 if self.distro_series.status == SeriesStatus.OBSOLETE:
493 return False
494-
495- failed_statuses = [
496- BuildStatus.FAILEDTOBUILD,
497- BuildStatus.MANUALDEPWAIT,
498- BuildStatus.CHROOTWAIT,
499- BuildStatus.FAILEDTOUPLOAD,
500- BuildStatus.CANCELLED,
501- BuildStatus.SUPERSEDED,
502- ]
503-
504- # If the build is currently in any of the failed states,
505- # it may be retried.
506- return self.status in failed_statuses
507-
508- @property
509- def can_be_rescored(self):
510- """See `ICharmRecipeBuild`."""
511- return (
512- self.buildqueue_record is not None and
513- self.status is BuildStatus.NEEDSBUILD)
514-
515- @property
516- def can_be_cancelled(self):
517- """See `ICharmRecipeBuild`."""
518- if not self.buildqueue_record:
519- return False
520-
521- cancellable_statuses = [
522- BuildStatus.BUILDING,
523- BuildStatus.NEEDSBUILD,
524- ]
525- return self.status in cancellable_statuses
526-
527- def retry(self):
528- """See `ICharmRecipeBuild`."""
529- assert self.can_be_retried, "Build %s cannot be retried" % self.id
530- self.build_farm_job.status = self.status = BuildStatus.NEEDSBUILD
531- self.build_farm_job.date_finished = self.date_finished = None
532- self.date_started = None
533- self.build_farm_job.builder = self.builder = None
534- self.log = None
535- self.upload_log = None
536- self.dependencies = None
537- self.failure_count = 0
538- self.queueBuild()
539-
540- def rescore(self, score):
541- """See `ICharmRecipeBuild`."""
542- assert self.can_be_rescored, "Build %s cannot be rescored" % self.id
543- self.buildqueue_record.manualScore(score)
544-
545- def cancel(self):
546- """See `ICharmRecipeBuild`."""
547- if not self.can_be_cancelled:
548- return
549- # BuildQueue.cancel() will decide whether to go straight to
550- # CANCELLED, or go through CANCELLING to let buildd-manager clean up
551- # the slave.
552- self.buildqueue_record.cancel()
553+ return super().can_be_retried
554
555 def calculateScore(self):
556 """See `IBuildFarmJob`."""
557diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
558index fb8e0d5..b5399af 100644
559--- a/lib/lp/code/configure.zcml
560+++ b/lib/lp/code/configure.zcml
561@@ -1196,6 +1196,9 @@
562 <require
563 permission="launchpad.Edit"
564 interface="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuildEdit"/>
565+ <require
566+ permission="launchpad.Admin"
567+ interface="lp.code.interfaces.sourcepackagerecipebuild.ISourcePackageRecipeBuildAdmin"/>
568 </class>
569
570 <securedutility
571diff --git a/lib/lp/code/interfaces/sourcepackagerecipebuild.py b/lib/lp/code/interfaces/sourcepackagerecipebuild.py
572index 82375f7..12fe659 100644
573--- a/lib/lp/code/interfaces/sourcepackagerecipebuild.py
574+++ b/lib/lp/code/interfaces/sourcepackagerecipebuild.py
575@@ -8,26 +8,26 @@ __all__ = [
576 'ISourcePackageRecipeBuildSource',
577 ]
578
579-from lazr.restful.declarations import (
580- export_write_operation,
581- exported,
582- exported_as_webservice_entry,
583- operation_for_version,
584- )
585+from lazr.restful.declarations import exported_as_webservice_entry
586 from lazr.restful.fields import (
587 CollectionField,
588 Reference,
589 )
590-from zope.interface import Interface
591 from zope.schema import (
592- Bool,
593 Int,
594 Object,
595 )
596
597 from lp import _
598-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
599-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
600+from lp.buildmaster.interfaces.buildfarmjob import (
601+ IBuildFarmJobAdmin,
602+ IBuildFarmJobEdit,
603+ ISpecificBuildFarmJobSource,
604+ )
605+from lp.buildmaster.interfaces.packagebuild import (
606+ IPackageBuild,
607+ IPackageBuildView,
608+ )
609 from lp.code.interfaces.sourcepackagerecipe import (
610 ISourcePackageRecipe,
611 ISourcePackageRecipeData,
612@@ -38,7 +38,8 @@ from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
613 from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
614
615
616-class ISourcePackageRecipeBuildView(IPackageBuild):
617+class ISourcePackageRecipeBuildView(IPackageBuildView):
618+ """`ISourcePackageRecipeBuild` attributes that require launchpad.View."""
619
620 id = Int(title=_("Identifier for this build."))
621
622@@ -57,16 +58,6 @@ class ISourcePackageRecipeBuildView(IPackageBuild):
623 recipe = Object(
624 schema=ISourcePackageRecipe, title=_("The recipe being built."))
625
626- can_be_rescored = exported(Bool(
627- title=_("Can be rescored"),
628- required=True, readonly=True,
629- description=_("Whether this build record can be rescored manually.")))
630-
631- can_be_cancelled = exported(Bool(
632- title=_("Can be cancelled"),
633- required=True, readonly=True,
634- description=_("Whether this build record can be cancelled.")))
635-
636 manifest = Object(
637 schema=ISourcePackageRecipeData, title=_(
638 'A snapshot of the recipe for this build.'))
639@@ -82,31 +73,22 @@ class ISourcePackageRecipeBuildView(IPackageBuild):
640 """Return the file under +files with specified name."""
641
642
643-class ISourcePackageRecipeBuildEdit(Interface):
644-
645- @export_write_operation()
646- @operation_for_version("devel")
647- def cancel():
648- """Cancel the build if it is either pending or in progress.
649-
650- Check the can_be_cancelled property prior to calling this method to
651- find out if cancelling the build is possible.
652-
653- If the build is in progress, it is marked as CANCELLING until the
654- buildd manager terminates the build and marks it CANCELLED. If the
655- build is not in progress, it is marked CANCELLED immediately and is
656- removed from the build queue.
657-
658- If the build is not in a cancellable state, this method is a no-op.
659- """
660+class ISourcePackageRecipeBuildEdit(IBuildFarmJobEdit):
661+ """`ISourcePackageRecipeBuild` attributes that require launchpad.Edit."""
662
663 def destroySelf():
664 """Delete the build itself."""
665
666
667+class ISourcePackageRecipeBuildAdmin(IBuildFarmJobAdmin):
668+ """`ISourcePackageRecipeBuild` attributes that require launchpad.Admin."""
669+
670+
671 @exported_as_webservice_entry()
672 class ISourcePackageRecipeBuild(ISourcePackageRecipeBuildView,
673- ISourcePackageRecipeBuildEdit):
674+ ISourcePackageRecipeBuildEdit,
675+ ISourcePackageRecipeBuildAdmin,
676+ IPackageBuild):
677 """A build of a source package."""
678
679
680diff --git a/lib/lp/code/model/sourcepackagerecipebuild.py b/lib/lp/code/model/sourcepackagerecipebuild.py
681index 7be41ee..273d606 100644
682--- a/lib/lp/code/model/sourcepackagerecipebuild.py
683+++ b/lib/lp/code/model/sourcepackagerecipebuild.py
684@@ -263,31 +263,7 @@ class SourcePackageRecipeBuild(SpecificBuildFarmJobSourceMixin,
685 builds.append(build)
686 return builds
687
688- @property
689- def can_be_rescored(self):
690- """See `IBuild`."""
691- return self.status is BuildStatus.NEEDSBUILD
692-
693- @property
694- def can_be_cancelled(self):
695- """See `ISourcePackageRecipeBuild`."""
696- if not self.buildqueue_record:
697- return False
698-
699- cancellable_statuses = [
700- BuildStatus.BUILDING,
701- BuildStatus.NEEDSBUILD,
702- ]
703- return self.status in cancellable_statuses
704-
705- def cancel(self):
706- """See `ISourcePackageRecipeBuild`."""
707- if not self.can_be_cancelled:
708- return
709- # BuildQueue.cancel() will decide whether to go straight to
710- # CANCELLED, or go through CANCELLING to let buildd-manager
711- # clean up the slave.
712- self.buildqueue_record.cancel()
713+ can_be_retried = False
714
715 def destroySelf(self):
716 if self.buildqueue_record is not None:
717diff --git a/lib/lp/oci/interfaces/ocirecipebuild.py b/lib/lp/oci/interfaces/ocirecipebuild.py
718index 32aabe0..b366f87 100644
719--- a/lib/lp/oci/interfaces/ocirecipebuild.py
720+++ b/lib/lp/oci/interfaces/ocirecipebuild.py
721@@ -25,7 +25,6 @@ from lazr.restful.declarations import (
722 exported,
723 exported_as_webservice_entry,
724 operation_for_version,
725- operation_parameters,
726 )
727 from lazr.restful.fields import (
728 CollectionField,
729@@ -47,8 +46,15 @@ from zope.schema import (
730
731 from lp import _
732 from lp.app.interfaces.launchpad import IPrivacy
733-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
734-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
735+from lp.buildmaster.interfaces.buildfarmjob import (
736+ IBuildFarmJobAdmin,
737+ IBuildFarmJobEdit,
738+ ISpecificBuildFarmJobSource,
739+ )
740+from lp.buildmaster.interfaces.packagebuild import (
741+ IPackageBuild,
742+ IPackageBuildView,
743+ )
744 from lp.oci.interfaces.ocirecipe import (
745 IOCIRecipe,
746 IOCIRecipeBuildRequest,
747@@ -141,7 +147,7 @@ class OCIRecipeBuildSetRegistryUploadStatus(EnumeratedType):
748 """)
749
750
751-class IOCIRecipeBuildView(IPackageBuild, IPrivacy):
752+class IOCIRecipeBuildView(IPackageBuildView, IPrivacy):
753 """`IOCIRecipeBuild` attributes that require launchpad.View permission."""
754
755 build_request = Reference(
756@@ -207,21 +213,6 @@ class IOCIRecipeBuildView(IPackageBuild, IPrivacy):
757 title=_("Score of the related build farm job (if any)."),
758 required=False, readonly=True))
759
760- can_be_rescored = exported(Bool(
761- title=_("Can be rescored"),
762- required=True, readonly=True,
763- description=_("Whether this build record can be rescored manually.")))
764-
765- can_be_retried = exported(Bool(
766- title=_("Can be retried"),
767- required=True, readonly=True,
768- description=_("Whether this build record can be retried.")))
769-
770- can_be_cancelled = exported(Bool(
771- title=_("Can be cancelled"),
772- required=True, readonly=True,
773- description=_("Whether this build record can be cancelled.")))
774-
775 manifest = Attribute(_("The manifest of the image."))
776
777 digests = Attribute(_("File containing the image digests."))
778@@ -266,7 +257,7 @@ class IOCIRecipeBuildView(IPackageBuild, IPrivacy):
779 """
780
781
782-class IOCIRecipeBuildEdit(Interface):
783+class IOCIRecipeBuildEdit(IBuildFarmJobEdit):
784 """`IOCIRecipeBuild` attributes that require launchpad.Edit permission."""
785
786 def addFile(lfa, layer_file_digest):
787@@ -286,46 +277,15 @@ class IOCIRecipeBuildEdit(Interface):
788 where an upload can be scheduled.
789 """
790
791- @export_write_operation()
792- @operation_for_version("devel")
793- def retry():
794- """Restore the build record to its initial state.
795-
796- Build record loses its history, is moved to NEEDSBUILD and a new
797- non-scored BuildQueue entry is created for it.
798- """
799-
800- @export_write_operation()
801- @operation_for_version("devel")
802- def cancel():
803- """Cancel the build if it is either pending or in progress.
804-
805- Check the can_be_cancelled property prior to calling this method to
806- find out if cancelling the build is possible.
807-
808- If the build is in progress, it is marked as CANCELLING until the
809- buildd manager terminates the build and marks it CANCELLED. If the
810- build is not in progress, it is marked CANCELLED immediately and is
811- removed from the build queue.
812-
813- If the build is not in a cancellable state, this method is a no-op.
814- """
815-
816
817-class IOCIRecipeBuildAdmin(Interface):
818+class IOCIRecipeBuildAdmin(IBuildFarmJobAdmin):
819 """`IOCIRecipeBuild` attributes that require launchpad.Admin permission."""
820
821- @operation_parameters(score=Int(title=_("Score"), required=True))
822- @export_write_operation()
823- @operation_for_version("devel")
824- def rescore(score):
825- """Change the build's score."""
826-
827
828 @exported_as_webservice_entry(
829 publish_web_link=True, as_of="devel", singular_name="oci_recipe_build")
830 class IOCIRecipeBuild(IOCIRecipeBuildAdmin, IOCIRecipeBuildEdit,
831- IOCIRecipeBuildView):
832+ IOCIRecipeBuildView, IPackageBuild):
833 """A build record for an OCI recipe."""
834
835
836diff --git a/lib/lp/oci/model/ocirecipebuild.py b/lib/lp/oci/model/ocirecipebuild.py
837index fc4e3e2..13cc08a 100644
838--- a/lib/lp/oci/model/ocirecipebuild.py
839+++ b/lib/lp/oci/model/ocirecipebuild.py
840@@ -214,43 +214,12 @@ class OCIRecipeBuild(PackageBuildMixin, StormBase):
841
842 @property
843 def can_be_retried(self):
844- """See `IOCIRecipeBuild`."""
845+ """See `IBuildFarmJob`."""
846 # First check that the behaviour would accept the build if it
847 # succeeded.
848 if self.distro_series.status == SeriesStatus.OBSOLETE:
849 return False
850-
851- failed_statuses = [
852- BuildStatus.FAILEDTOBUILD,
853- BuildStatus.MANUALDEPWAIT,
854- BuildStatus.CHROOTWAIT,
855- BuildStatus.FAILEDTOUPLOAD,
856- BuildStatus.CANCELLED,
857- BuildStatus.SUPERSEDED,
858- ]
859-
860- # If the build is currently in any of the failed states,
861- # it may be retried.
862- return self.status in failed_statuses
863-
864- @property
865- def can_be_rescored(self):
866- """See `IOCIRecipeBuild`."""
867- return (
868- self.buildqueue_record is not None and
869- self.status is BuildStatus.NEEDSBUILD)
870-
871- @property
872- def can_be_cancelled(self):
873- """See `IOCIRecipeBuild`."""
874- if not self.buildqueue_record:
875- return False
876-
877- cancellable_statuses = [
878- BuildStatus.BUILDING,
879- BuildStatus.NEEDSBUILD,
880- ]
881- return self.status in cancellable_statuses
882+ return super().can_be_retried
883
884 @property
885 def is_private(self):
886@@ -271,33 +240,6 @@ class OCIRecipeBuild(PackageBuildMixin, StormBase):
887
888 private = is_private
889
890- def retry(self):
891- """See `IOCIRecipeBuild`."""
892- assert self.can_be_retried, "Build %s cannot be retried" % self.id
893- self.build_farm_job.status = self.status = BuildStatus.NEEDSBUILD
894- self.build_farm_job.date_finished = self.date_finished = None
895- self.date_started = None
896- self.build_farm_job.builder = self.builder = None
897- self.log = None
898- self.upload_log = None
899- self.dependencies = None
900- self.failure_count = 0
901- self.queueBuild()
902-
903- def rescore(self, score):
904- """See `IOCIRecipeBuild`."""
905- assert self.can_be_rescored, "Build %s cannot be rescored" % self.id
906- self.buildqueue_record.manualScore(score)
907-
908- def cancel(self):
909- """See `IOCIRecipeBuild`."""
910- if not self.can_be_cancelled:
911- return
912- # BuildQueue.cancel() will decide whether to go straight to
913- # CANCELLED, or go through CANCELLING to let buildd-manager clean up
914- # the slave.
915- self.buildqueue_record.cancel()
916-
917 def calculateScore(self):
918 # XXX twom 2020-02-11 - This might need an addition?
919 return 2510
920diff --git a/lib/lp/security.py b/lib/lp/security.py
921index 9f6690e..03c580f 100644
922--- a/lib/lp/security.py
923+++ b/lib/lp/security.py
924@@ -1666,18 +1666,6 @@ class EditCodeImportMachine(OnlyBazaarExpertsAndAdmins):
925 usedfor = ICodeImportMachine
926
927
928-class AdminSourcePackageRecipeBuilds(AuthorizationBase):
929- """Control who can edit SourcePackageRecipeBuilds.
930-
931- Access is restricted to Buildd Admins.
932- """
933- permission = 'launchpad.Admin'
934- usedfor = ISourcePackageRecipeBuild
935-
936- def checkAuthenticated(self, user):
937- return user.in_buildd_admin
938-
939-
940 class AdminDistributionTranslations(AuthorizationBase):
941 """Class for deciding who can administer distribution translations.
942
943diff --git a/lib/lp/snappy/interfaces/snapbuild.py b/lib/lp/snappy/interfaces/snapbuild.py
944index 6d41fbe..4012391 100644
945--- a/lib/lp/snappy/interfaces/snapbuild.py
946+++ b/lib/lp/snappy/interfaces/snapbuild.py
947@@ -25,7 +25,6 @@ from lazr.restful.declarations import (
948 exported,
949 exported_as_webservice_entry,
950 operation_for_version,
951- operation_parameters,
952 )
953 from lazr.restful.fields import (
954 CollectionField,
955@@ -48,8 +47,15 @@ from zope.schema import (
956
957 from lp import _
958 from lp.app.interfaces.launchpad import IPrivacy
959-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
960-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
961+from lp.buildmaster.interfaces.buildfarmjob import (
962+ IBuildFarmJobAdmin,
963+ IBuildFarmJobEdit,
964+ ISpecificBuildFarmJobSource,
965+ )
966+from lp.buildmaster.interfaces.packagebuild import (
967+ IPackageBuild,
968+ IPackageBuildView,
969+ )
970 from lp.registry.interfaces.person import IPerson
971 from lp.registry.interfaces.pocket import PackagePublishingPocket
972 from lp.services.database.constants import DEFAULT
973@@ -128,7 +134,7 @@ class SnapBuildStoreUploadStatus(EnumeratedType):
974 """)
975
976
977-class ISnapBuildView(IPackageBuild, IPrivacy):
978+class ISnapBuildView(IPackageBuildView, IPrivacy):
979 """`ISnapBuild` attributes that require launchpad.View permission."""
980
981 build_request = Reference(
982@@ -189,21 +195,6 @@ class ISnapBuildView(IPackageBuild, IPrivacy):
983 title=_("Score of the related build farm job (if any)."),
984 required=False, readonly=True))
985
986- can_be_rescored = exported(Bool(
987- title=_("Can be rescored"),
988- required=True, readonly=True,
989- description=_("Whether this build record can be rescored manually.")))
990-
991- can_be_retried = exported(Bool(
992- title=_("Can be retried"),
993- required=False, readonly=True,
994- description=_("Whether this build record can be retried.")))
995-
996- can_be_cancelled = exported(Bool(
997- title=_("Can be cancelled"),
998- required=True, readonly=True,
999- description=_("Whether this build record can be cancelled.")))
1000-
1001 eta = Datetime(
1002 title=_("The datetime when the build job is estimated to complete."),
1003 readonly=True)
1004@@ -298,7 +289,7 @@ class ISnapBuildView(IPackageBuild, IPrivacy):
1005 :return: A collection of URLs for this build."""
1006
1007
1008-class ISnapBuildEdit(Interface):
1009+class ISnapBuildEdit(IBuildFarmJobEdit):
1010 """`ISnapBuild` attributes that require launchpad.Edit."""
1011
1012 def addFile(lfa):
1013@@ -317,47 +308,17 @@ class ISnapBuildEdit(Interface):
1014 where an upload can be scheduled.
1015 """
1016
1017- @export_write_operation()
1018- @operation_for_version("devel")
1019- def retry():
1020- """Restore the build record to its initial state.
1021-
1022- Build record loses its history, is moved to NEEDSBUILD and a new
1023- non-scored BuildQueue entry is created for it.
1024- """
1025-
1026- @export_write_operation()
1027- @operation_for_version("devel")
1028- def cancel():
1029- """Cancel the build if it is either pending or in progress.
1030-
1031- Check the can_be_cancelled property prior to calling this method to
1032- find out if cancelling the build is possible.
1033-
1034- If the build is in progress, it is marked as CANCELLING until the
1035- buildd manager terminates the build and marks it CANCELLED. If the
1036- build is not in progress, it is marked CANCELLED immediately and is
1037- removed from the build queue.
1038-
1039- If the build is not in a cancellable state, this method is a no-op.
1040- """
1041-
1042
1043-class ISnapBuildAdmin(Interface):
1044+class ISnapBuildAdmin(IBuildFarmJobAdmin):
1045 """`ISnapBuild` attributes that require launchpad.Admin."""
1046
1047- @operation_parameters(score=Int(title=_("Score"), required=True))
1048- @export_write_operation()
1049- @operation_for_version("devel")
1050- def rescore(score):
1051- """Change the build's score."""
1052-
1053
1054 # XXX cjwatson 2014-05-06 bug=760849: "beta" is a lie to get WADL
1055 # generation working. Individual attributes must set their version to
1056 # "devel".
1057 @exported_as_webservice_entry(as_of="beta")
1058-class ISnapBuild(ISnapBuildView, ISnapBuildEdit, ISnapBuildAdmin):
1059+class ISnapBuild(
1060+ ISnapBuildView, ISnapBuildEdit, ISnapBuildAdmin, IPackageBuild):
1061 """Build information for snap package builds."""
1062
1063
1064diff --git a/lib/lp/snappy/model/snapbuild.py b/lib/lp/snappy/model/snapbuild.py
1065index a4405de..24fa63f 100644
1066--- a/lib/lp/snappy/model/snapbuild.py
1067+++ b/lib/lp/snappy/model/snapbuild.py
1068@@ -284,70 +284,12 @@ class SnapBuild(PackageBuildMixin, Storm):
1069
1070 @property
1071 def can_be_retried(self):
1072- """See `ISnapBuild`."""
1073+ """See `IBuildFarmJob`."""
1074 # First check that the behaviour would accept the build if it
1075 # succeeded.
1076 if self.distro_series.status == SeriesStatus.OBSOLETE:
1077 return False
1078-
1079- failed_statuses = [
1080- BuildStatus.FAILEDTOBUILD,
1081- BuildStatus.MANUALDEPWAIT,
1082- BuildStatus.CHROOTWAIT,
1083- BuildStatus.FAILEDTOUPLOAD,
1084- BuildStatus.CANCELLED,
1085- BuildStatus.SUPERSEDED,
1086- ]
1087-
1088- # If the build is currently in any of the failed states,
1089- # it may be retried.
1090- return self.status in failed_statuses
1091-
1092- @property
1093- def can_be_rescored(self):
1094- """See `ISnapBuild`."""
1095- return (
1096- self.buildqueue_record is not None and
1097- self.status is BuildStatus.NEEDSBUILD)
1098-
1099- @property
1100- def can_be_cancelled(self):
1101- """See `ISnapBuild`."""
1102- if not self.buildqueue_record:
1103- return False
1104-
1105- cancellable_statuses = [
1106- BuildStatus.BUILDING,
1107- BuildStatus.NEEDSBUILD,
1108- ]
1109- return self.status in cancellable_statuses
1110-
1111- def retry(self):
1112- """See `ISnapBuild`."""
1113- assert self.can_be_retried, "Build %s cannot be retried" % self.id
1114- self.build_farm_job.status = self.status = BuildStatus.NEEDSBUILD
1115- self.build_farm_job.date_finished = self.date_finished = None
1116- self.date_started = None
1117- self.build_farm_job.builder = self.builder = None
1118- self.log = None
1119- self.upload_log = None
1120- self.dependencies = None
1121- self.failure_count = 0
1122- self.queueBuild()
1123-
1124- def rescore(self, score):
1125- """See `ISnapBuild`."""
1126- assert self.can_be_rescored, "Build %s cannot be rescored" % self.id
1127- self.buildqueue_record.manualScore(score)
1128-
1129- def cancel(self):
1130- """See `ISnapBuild`."""
1131- if not self.can_be_cancelled:
1132- return
1133- # BuildQueue.cancel() will decide whether to go straight to
1134- # CANCELLED, or go through CANCELLING to let buildd-manager clean up
1135- # the slave.
1136- self.buildqueue_record.cancel()
1137+ return super().can_be_retried
1138
1139 def calculateScore(self):
1140 return 2510 + self.archive.relative_build_score
1141diff --git a/lib/lp/soyuz/interfaces/binarypackagebuild.py b/lib/lp/soyuz/interfaces/binarypackagebuild.py
1142index 3771af8..edc245f 100644
1143--- a/lib/lp/soyuz/interfaces/binarypackagebuild.py
1144+++ b/lib/lp/soyuz/interfaces/binarypackagebuild.py
1145@@ -5,21 +5,17 @@
1146
1147 __all__ = [
1148 'BuildSetStatus',
1149- 'CannotBeRescored',
1150 'IBinaryPackageBuild',
1151 'IBuildRescoreForm',
1152 'IBinaryPackageBuildSet',
1153 'UnparsableDependencies',
1154 ]
1155
1156-import http.client
1157-
1158 from lazr.enum import (
1159 EnumeratedType,
1160 Item,
1161 )
1162 from lazr.restful.declarations import (
1163- error_status,
1164 export_read_operation,
1165 export_write_operation,
1166 exported,
1167@@ -42,24 +38,25 @@ from zope.schema import (
1168
1169 from lp import _
1170 from lp.buildmaster.enums import BuildStatus
1171-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
1172-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
1173+from lp.buildmaster.interfaces.buildfarmjob import (
1174+ IBuildFarmJobAdmin,
1175+ IBuildFarmJobEdit,
1176+ ISpecificBuildFarmJobSource,
1177+ )
1178+from lp.buildmaster.interfaces.packagebuild import (
1179+ IPackageBuild,
1180+ IPackageBuildView,
1181+ )
1182 from lp.buildmaster.interfaces.processor import IProcessor
1183 from lp.soyuz.interfaces.publishing import ISourcePackagePublishingHistory
1184 from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
1185
1186
1187-@error_status(http.client.BAD_REQUEST)
1188-class CannotBeRescored(Exception):
1189- """Raised when rescoring a build that cannot be rescored."""
1190- _message_prefix = "Cannot rescore build"
1191-
1192-
1193 class UnparsableDependencies(Exception):
1194 """Raised when parsing invalid dependencies on a binary package."""
1195
1196
1197-class IBinaryPackageBuildView(IPackageBuild):
1198+class IBinaryPackageBuildView(IPackageBuildView):
1199 """A Build interface for items requiring launchpad.View."""
1200 id = Int(title=_('ID'), required=True, readonly=True)
1201
1202@@ -116,25 +113,6 @@ class IBinaryPackageBuildView(IPackageBuild):
1203 "A list of distroarchseriesbinarypackages that resulted from this"
1204 "build, ordered by name.")
1205
1206- can_be_rescored = exported(
1207- Bool(
1208- title=_("Can Be Rescored"), required=False, readonly=True,
1209- description=_(
1210- "Whether or not this build record can be rescored "
1211- "manually.")))
1212-
1213- can_be_retried = exported(
1214- Bool(
1215- title=_("Can Be Retried"), required=False, readonly=True,
1216- description=_(
1217- "Whether or not this build record can be retried.")))
1218-
1219- can_be_cancelled = exported(
1220- Bool(
1221- title=_("Can Be Cancelled"), required=False, readonly=True,
1222- description=_(
1223- "Whether or not this build record can be cancelled.")))
1224-
1225 upload_changesfile = Attribute(
1226 "The `LibraryFileAlias` object containing the changes file which "
1227 "was originally uploaded with the results of this build. It's "
1228@@ -243,10 +221,19 @@ class IBinaryPackageBuildView(IPackageBuild):
1229 """
1230
1231
1232-class IBinaryPackageBuildEdit(Interface):
1233+class IBinaryPackageBuildEdit(IBuildFarmJobEdit):
1234 """A Build interface for items requiring launchpad.Edit."""
1235
1236+ def addBuildInfo(buildinfo):
1237+ """Add a buildinfo file to this build.
1238+
1239+ :param buildinfo: An `ILibraryFileAlias`.
1240+ """
1241+
1242+ # Redeclaring from IBuildFarmJobEdit.retry since this was available in
1243+ # the beta version.
1244 @export_write_operation()
1245+ @operation_for_version("beta")
1246 def retry():
1247 """Restore the build record to its initial state.
1248
1249@@ -254,28 +241,6 @@ class IBinaryPackageBuildEdit(Interface):
1250 non-scored BuildQueue entry is created for it.
1251 """
1252
1253- @export_write_operation()
1254- @operation_for_version("devel")
1255- def cancel():
1256- """Cancel the build if it is either pending or in progress.
1257-
1258- Check the can_be_cancelled property prior to calling this method to
1259- find out if cancelling the build is possible.
1260-
1261- If the build is in progress, it is marked as CANCELLING until the
1262- buildd manager terminates the build and marks it CANCELLED. If the
1263- build is not in progress, it is marked CANCELLED immediately and is
1264- removed from the build queue.
1265-
1266- If the build is not in a cancellable state, this method is a no-op.
1267- """
1268-
1269- def addBuildInfo(buildinfo):
1270- """Add a buildinfo file to this build.
1271-
1272- :param buildinfo: An `ILibraryFileAlias`.
1273- """
1274-
1275
1276 class IBinaryPackageBuildRestricted(Interface):
1277 """Restricted `IBinaryPackageBuild` attributes.
1278@@ -297,11 +262,14 @@ class IBinaryPackageBuildRestricted(Interface):
1279 exported_as="external_dependencies")
1280
1281
1282-class IBinaryPackageBuildAdmin(Interface):
1283+class IBinaryPackageBuildAdmin(IBuildFarmJobAdmin):
1284 """A Build interface for items requiring launchpad.Admin."""
1285
1286+ # Redeclaring from IBuildFarmJobEdit.rescore since this was available in
1287+ # the beta version.
1288 @operation_parameters(score=Int(title=_("Score"), required=True))
1289 @export_write_operation()
1290+ @operation_for_version("beta")
1291 def rescore(score):
1292 """Change the build's score."""
1293
1294@@ -309,7 +277,8 @@ class IBinaryPackageBuildAdmin(Interface):
1295 @exported_as_webservice_entry(singular_name='build', plural_name='builds')
1296 class IBinaryPackageBuild(
1297 IBinaryPackageBuildView, IBinaryPackageBuildEdit,
1298- IBinaryPackageBuildRestricted, IBinaryPackageBuildAdmin):
1299+ IBinaryPackageBuildRestricted, IBinaryPackageBuildAdmin,
1300+ IPackageBuild):
1301 """A Build interface"""
1302
1303
1304diff --git a/lib/lp/soyuz/interfaces/livefsbuild.py b/lib/lp/soyuz/interfaces/livefsbuild.py
1305index cbba840..5ed7098 100644
1306--- a/lib/lp/soyuz/interfaces/livefsbuild.py
1307+++ b/lib/lp/soyuz/interfaces/livefsbuild.py
1308@@ -11,11 +11,9 @@ __all__ = [
1309
1310 from lazr.restful.declarations import (
1311 export_read_operation,
1312- export_write_operation,
1313 exported,
1314 exported_as_webservice_entry,
1315 operation_for_version,
1316- operation_parameters,
1317 )
1318 from lazr.restful.fields import Reference
1319 from zope.interface import Interface
1320@@ -29,8 +27,15 @@ from zope.schema import (
1321
1322 from lp import _
1323 from lp.app.interfaces.launchpad import IPrivacy
1324-from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
1325-from lp.buildmaster.interfaces.packagebuild import IPackageBuild
1326+from lp.buildmaster.interfaces.buildfarmjob import (
1327+ IBuildFarmJobAdmin,
1328+ IBuildFarmJobEdit,
1329+ ISpecificBuildFarmJobSource,
1330+ )
1331+from lp.buildmaster.interfaces.packagebuild import (
1332+ IPackageBuild,
1333+ IPackageBuildView,
1334+ )
1335 from lp.registry.interfaces.person import IPerson
1336 from lp.registry.interfaces.pocket import PackagePublishingPocket
1337 from lp.services.database.constants import DEFAULT
1338@@ -53,7 +58,7 @@ class ILiveFSFile(Interface):
1339 required=True, readonly=True)
1340
1341
1342-class ILiveFSBuildView(IPackageBuild, IPrivacy):
1343+class ILiveFSBuildView(IPackageBuildView, IPrivacy):
1344 """`ILiveFSBuild` attributes that require launchpad.View permission."""
1345
1346 requester = exported(Reference(
1347@@ -103,16 +108,6 @@ class ILiveFSBuildView(IPackageBuild, IPrivacy):
1348 title=_("Score of the related build farm job (if any)."),
1349 required=False, readonly=True))
1350
1351- can_be_rescored = exported(Bool(
1352- title=_("Can be rescored"),
1353- required=True, readonly=True,
1354- description=_("Whether this build record can be rescored manually.")))
1355-
1356- can_be_cancelled = exported(Bool(
1357- title=_("Can be cancelled"),
1358- required=True, readonly=True,
1359- description=_("Whether this build record can be cancelled.")))
1360-
1361 def getFiles():
1362 """Retrieve the build's `ILiveFSFile` records.
1363
1364@@ -144,7 +139,7 @@ class ILiveFSBuildView(IPackageBuild, IPrivacy):
1365 :return: A collection of URLs for this build."""
1366
1367
1368-class ILiveFSBuildEdit(Interface):
1369+class ILiveFSBuildEdit(IBuildFarmJobEdit):
1370 """`ILiveFSBuild` attributes that require launchpad.Edit."""
1371
1372 def addFile(lfa):
1373@@ -154,38 +149,17 @@ class ILiveFSBuildEdit(Interface):
1374 :return: An `ILiveFSFile`.
1375 """
1376
1377- @export_write_operation()
1378- @operation_for_version("devel")
1379- def cancel():
1380- """Cancel the build if it is either pending or in progress.
1381-
1382- Check the can_be_cancelled property prior to calling this method to
1383- find out if cancelling the build is possible.
1384-
1385- If the build is in progress, it is marked as CANCELLING until the
1386- buildd manager terminates the build and marks it CANCELLED. If the
1387- build is not in progress, it is marked CANCELLED immediately and is
1388- removed from the build queue.
1389-
1390- If the build is not in a cancellable state, this method is a no-op.
1391- """
1392-
1393
1394-class ILiveFSBuildAdmin(Interface):
1395+class ILiveFSBuildAdmin(IBuildFarmJobAdmin):
1396 """`ILiveFSBuild` attributes that require launchpad.Admin."""
1397
1398- @operation_parameters(score=Int(title=_("Score"), required=True))
1399- @export_write_operation()
1400- @operation_for_version("devel")
1401- def rescore(score):
1402- """Change the build's score."""
1403-
1404
1405 # XXX cjwatson 2014-05-06 bug=760849: "beta" is a lie to get WADL
1406 # generation working. Individual attributes must set their version to
1407 # "devel".
1408 @exported_as_webservice_entry(singular_name="livefs_build", as_of="beta")
1409-class ILiveFSBuild(ILiveFSBuildView, ILiveFSBuildEdit, ILiveFSBuildAdmin):
1410+class ILiveFSBuild(
1411+ ILiveFSBuildView, ILiveFSBuildEdit, ILiveFSBuildAdmin, IPackageBuild):
1412 """Build information for live filesystem builds."""
1413
1414
1415diff --git a/lib/lp/soyuz/interfaces/webservice.py b/lib/lp/soyuz/interfaces/webservice.py
1416index 90ef0e1..b8ecec2 100644
1417--- a/lib/lp/soyuz/interfaces/webservice.py
1418+++ b/lib/lp/soyuz/interfaces/webservice.py
1419@@ -13,7 +13,6 @@ __all__ = [
1420 'AlreadySubscribed',
1421 'ArchiveDisabled',
1422 'ArchiveNotPrivate',
1423- 'CannotBeRescored',
1424 'CannotCopy',
1425 'CannotSwitchPrivacy',
1426 'CannotUploadToArchive',
1427@@ -81,10 +80,7 @@ from lp.soyuz.interfaces.archive import (
1428 from lp.soyuz.interfaces.archivedependency import IArchiveDependency
1429 from lp.soyuz.interfaces.archivepermission import IArchivePermission
1430 from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriber
1431-from lp.soyuz.interfaces.binarypackagebuild import (
1432- CannotBeRescored,
1433- IBinaryPackageBuild,
1434- )
1435+from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
1436 from lp.soyuz.interfaces.binarypackagerelease import (
1437 IBinaryPackageReleaseDownloadCount,
1438 )
1439diff --git a/lib/lp/soyuz/model/binarypackagebuild.py b/lib/lp/soyuz/model/binarypackagebuild.py
1440index 511c975..f51f565 100644
1441--- a/lib/lp/soyuz/model/binarypackagebuild.py
1442+++ b/lib/lp/soyuz/model/binarypackagebuild.py
1443@@ -101,7 +101,6 @@ from lp.soyuz.interfaces.archive import (
1444 )
1445 from lp.soyuz.interfaces.binarypackagebuild import (
1446 BuildSetStatus,
1447- CannotBeRescored,
1448 IBinaryPackageBuild,
1449 IBinaryPackageBuildSet,
1450 UnparsableDependencies,
1451@@ -475,64 +474,19 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
1452
1453 @property
1454 def can_be_retried(self):
1455- """See `IBuild`."""
1456+ """See `IBuildFarmJob`."""
1457 # First check that the slave scanner would pick up the build record
1458 # if we reset it.
1459 if not self.archive.canModifySuite(self.distro_series, self.pocket):
1460 # The slave scanner would not pick this up, so it cannot be
1461 # re-tried.
1462 return False
1463+ return super().can_be_retried
1464
1465- failed_statuses = [
1466- BuildStatus.FAILEDTOBUILD,
1467- BuildStatus.MANUALDEPWAIT,
1468- BuildStatus.CHROOTWAIT,
1469- BuildStatus.FAILEDTOUPLOAD,
1470- BuildStatus.CANCELLED,
1471- BuildStatus.SUPERSEDED,
1472- ]
1473-
1474- # If the build is currently in any of the failed states,
1475- # it may be retried.
1476- return self.status in failed_statuses
1477-
1478- @property
1479- def can_be_rescored(self):
1480- """See `IBuild`."""
1481- return self.status is BuildStatus.NEEDSBUILD
1482-
1483- @property
1484- def can_be_cancelled(self):
1485- """See `IBuild`."""
1486- if not self.buildqueue_record:
1487- return False
1488-
1489- cancellable_statuses = [
1490- BuildStatus.BUILDING,
1491- BuildStatus.NEEDSBUILD,
1492- ]
1493- return self.status in cancellable_statuses
1494-
1495- def retry(self):
1496- """See `IBuild`."""
1497- assert self.can_be_retried, "Build %s cannot be retried" % self.id
1498- self.build_farm_job.status = self.status = BuildStatus.NEEDSBUILD
1499- self.build_farm_job.date_finished = self.date_finished = None
1500- self.date_started = None
1501- self.build_farm_job.builder = self.builder = None
1502- self.log = None
1503- self.upload_log = None
1504- self.dependencies = None
1505- self.failure_count = 0
1506+ def resetBuild(self):
1507+ """See `IBuildFarmJob`."""
1508+ super().resetBuild()
1509 self.virtualized = is_build_virtualized(self.archive, self.processor)
1510- self.queueBuild()
1511-
1512- def rescore(self, score):
1513- """See `IBuild`."""
1514- if not self.can_be_rescored:
1515- raise CannotBeRescored("Build cannot be rescored.")
1516-
1517- self.buildqueue_record.manualScore(score)
1518
1519 @property
1520 def api_score(self):
1521@@ -543,15 +497,6 @@ class BinaryPackageBuild(PackageBuildMixin, SQLBase):
1522 else:
1523 return self.buildqueue_record.lastscore
1524
1525- def cancel(self):
1526- """See `IBinaryPackageBuild`."""
1527- if not self.can_be_cancelled:
1528- return
1529- # BuildQueue.cancel() will decide whether to go straight to
1530- # CANCELLED, or go through CANCELLING to let buildd-manager
1531- # clean up the slave.
1532- self.buildqueue_record.cancel()
1533-
1534 def _parseDependencyToken(self, token):
1535 """Parse the given token.
1536
1537diff --git a/lib/lp/soyuz/model/livefsbuild.py b/lib/lp/soyuz/model/livefsbuild.py
1538index f8312c2..7ec3e87 100644
1539--- a/lib/lp/soyuz/model/livefsbuild.py
1540+++ b/lib/lp/soyuz/model/livefsbuild.py
1541@@ -230,38 +230,7 @@ class LiveFSBuild(PackageBuildMixin, Storm):
1542 else:
1543 return self.buildqueue_record.lastscore
1544
1545- @property
1546- def can_be_rescored(self):
1547- """See `ILiveFSBuild`."""
1548- return (
1549- self.buildqueue_record is not None and
1550- self.status is BuildStatus.NEEDSBUILD)
1551-
1552- @property
1553- def can_be_cancelled(self):
1554- """See `ILiveFSBuild`."""
1555- if not self.buildqueue_record:
1556- return False
1557-
1558- cancellable_statuses = [
1559- BuildStatus.BUILDING,
1560- BuildStatus.NEEDSBUILD,
1561- ]
1562- return self.status in cancellable_statuses
1563-
1564- def rescore(self, score):
1565- """See `ILiveFSBuild`."""
1566- assert self.can_be_rescored, "Build %s cannot be rescored" % self.id
1567- self.buildqueue_record.manualScore(score)
1568-
1569- def cancel(self):
1570- """See `ILiveFSBuild`."""
1571- if not self.can_be_cancelled:
1572- return
1573- # BuildQueue.cancel() will decide whether to go straight to
1574- # CANCELLED, or go through CANCELLING to let buildd-manager clean up
1575- # the slave.
1576- self.buildqueue_record.cancel()
1577+ can_be_retried = False
1578
1579 def calculateScore(self):
1580 return (
1581diff --git a/lib/lp/soyuz/stories/webservice/xx-builds.txt b/lib/lp/soyuz/stories/webservice/xx-builds.txt
1582index 1e7a44a..5148042 100644
1583--- a/lib/lp/soyuz/stories/webservice/xx-builds.txt
1584+++ b/lib/lp/soyuz/stories/webservice/xx-builds.txt
1585@@ -205,10 +205,9 @@ As can cprov who owns the PPA for the build:
1586 ... cprov, permission=OAuthPermission.WRITE_PUBLIC)
1587 >>> print(cprov_webservice.named_post(
1588 ... a_build['self_link'], 'retry'))
1589- HTTP/1.1 500 Internal Server Error
1590+ HTTP/1.1 400 Bad Request
1591 ...
1592- AssertionError: Build ... cannot be retried
1593- <BLANKLINE>
1594+ Build ... cannot be retried.
1595
1596 but in this case, although he has permission to retry the build, it
1597 failed because it was already retried by an admin. This is reflected in the
1598@@ -265,4 +264,4 @@ alter the buildstate to one that cannot be retried:
1599 ... a_build['self_link'], 'rescore', score=1000))
1600 HTTP/1.1 400 Bad Request
1601 ...
1602- Build cannot be rescored.
1603+ Build ... cannot be rescored.
1604diff --git a/lib/lp/soyuz/tests/test_build.py b/lib/lp/soyuz/tests/test_build.py
1605index 6e83cde..8181a2d 100644
1606--- a/lib/lp/soyuz/tests/test_build.py
1607+++ b/lib/lp/soyuz/tests/test_build.py
1608@@ -11,6 +11,7 @@ import transaction
1609 from zope.component import getUtility
1610
1611 from lp.buildmaster.enums import BuildStatus
1612+from lp.buildmaster.interfaces.buildfarmjob import CannotBeRescored
1613 from lp.registry.interfaces.person import IPersonSet
1614 from lp.registry.interfaces.pocket import PackagePublishingPocket
1615 from lp.registry.interfaces.series import SeriesStatus
1616@@ -20,10 +21,7 @@ from lp.soyuz.enums import (
1617 PackagePublishingPriority,
1618 PackageUploadStatus,
1619 )
1620-from lp.soyuz.interfaces.binarypackagebuild import (
1621- CannotBeRescored,
1622- IBinaryPackageBuildSet,
1623- )
1624+from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
1625 from lp.soyuz.interfaces.component import IComponentSet
1626 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
1627 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher

Subscribers

People subscribed via source and target branches

to status/vote changes: