launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05354
[Merge] lp:~julian-edwards/launchpad/cancel-build-bug-173018-ui-part3 into lp:launchpad
Julian Edwards has proposed merging lp:~julian-edwards/launchpad/cancel-build-bug-173018-ui-part3 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~julian-edwards/launchpad/cancel-build-bug-173018-ui-part3/+merge/80888
Add BinaryPackageBuild.cancel() and can_be_cancelled property, and export both to the webservice.
--
https://code.launchpad.net/~julian-edwards/launchpad/cancel-build-bug-173018-ui-part3/+merge/80888
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~julian-edwards/launchpad/cancel-build-bug-173018-ui-part3 into lp:launchpad.
=== modified file 'lib/lp/soyuz/interfaces/binarypackagebuild.py'
--- lib/lp/soyuz/interfaces/binarypackagebuild.py 2011-06-16 20:12:00 +0000
+++ lib/lp/soyuz/interfaces/binarypackagebuild.py 2011-11-01 11:25:38 +0000
@@ -27,6 +27,7 @@
export_as_webservice_entry,
export_write_operation,
exported,
+ operation_for_version,
operation_parameters,
)
from lazr.restful.fields import Reference
@@ -119,6 +120,12 @@
description=_(
"Whether or not this build record can be retried.")))
+ can_be_cancelled = exported(
+ Bool(
+ title=_("Can Be Cancelled"), required=False, readonly=True,
+ description=_(
+ "Whether or not this build record can be cancelled.")))
+
is_virtualized = Attribute(
"Whether or not this build requires a virtual build host or not.")
@@ -223,6 +230,22 @@
non-scored BuildQueue entry is created for it.
"""
+ @export_write_operation()
+ @operation_for_version("devel")
+ def cancel():
+ """Cancel the build if it is either pending or in progress.
+
+ Call the can_be_cancelled() method prior to this one to find out if
+ cancelling the build is possible.
+
+ If the build is in progress, it is marked as CANCELLING until the
+ buildd manager terminates the build and marks it CANCELLED. If the
+ build is not in progress, it is marked CANCELLED immediately and is
+ removed from the build queue.
+
+ If the build is not in a cancellable state, this method is a no-op.
+ """
+
class IBinaryPackageBuildAdmin(Interface):
"""A Build interface for items requiring launchpad.Admin."""
=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py 2011-09-23 15:46:11 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py 2011-11-01 11:25:38 +0000
@@ -349,6 +349,20 @@
"""See `IBuild`."""
return self.status is BuildStatus.NEEDSBUILD
+ @property
+ def can_be_cancelled(self):
+ """See `IBuild`."""
+ if not self.buildqueue_record:
+ return False
+ if self.buildqueue_record.virtualized is False:
+ return False
+
+ cancellable_statuses = [
+ BuildStatus.BUILDING,
+ BuildStatus.NEEDSBUILD,
+ ]
+ return self.status in cancellable_statuses
+
def retry(self):
"""See `IBuild`."""
assert self.can_be_retried, "Build %s cannot be retried" % self.id
@@ -377,6 +391,20 @@
else:
return self.buildqueue_record.lastscore
+ def cancel(self):
+ """See `IBinaryPackageBuild`."""
+ if not self.can_be_cancelled:
+ return
+
+ # If the build is currently building we need to tell the
+ # buildd-manager to terminate it.
+ if self.status == BuildStatus.BUILDING:
+ self.status = BuildStatus.CANCELLING
+ return
+
+ # Otherwise we can cancel it here.
+ self.buildqueue_record.cancel()
+
def makeJob(self):
"""See `IBuildFarmJob`."""
store = Store.of(self)
=== modified file 'lib/lp/soyuz/stories/webservice/xx-builds.txt'
--- lib/lp/soyuz/stories/webservice/xx-builds.txt 2011-05-04 02:45:25 +0000
+++ lib/lp/soyuz/stories/webservice/xx-builds.txt 2011-11-01 11:25:38 +0000
@@ -42,6 +42,7 @@
>>> pprint_entry(builds['entries'][0])
arch_tag: u'i386'
archive_link: u'http://.../beta/~cprov/+archive/ppa'
+ can_be_cancelled: False
can_be_rescored: False
can_be_retried: True
changesfile_url: None
@@ -71,6 +72,7 @@
archive_link: u'http://.../~cprov/+archive/ppa'
build_log_url: u'http://.../~cprov/+archive/ppa/+build/26/+files/netapplet-1.0.0.tar.gz'
buildstate: u'Failed to build'
+ can_be_cancelled: False
can_be_rescored: False
can_be_retried: True
changesfile_url: None
=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-05-04 06:37:50 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py 2011-11-01 11:25:38 +0000
@@ -15,7 +15,15 @@
from twisted.trial.unittest import TestCase as TrialTestCase
-from canonical.testing.layers import LaunchpadZopelessLayer
+from canonical.launchpad.testing.pages import (
+ webservice_for_person,
+ )
+from canonical.launchpad.webapp.interaction import ANONYMOUS
+from canonical.launchpad.webapp.interfaces import OAuthPermission
+from canonical.testing.layers import (
+ DatabaseFunctionalLayer,
+ LaunchpadZopelessLayer,
+ )
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.builder import IBuilderSet
from lp.buildmaster.interfaces.buildqueue import IBuildQueue
@@ -27,7 +35,10 @@
TestHandleStatusMixin,
)
from lp.services.job.model.job import Job
-from lp.soyuz.enums import PackagePublishingStatus
+from lp.soyuz.enums import (
+ ArchivePurpose,
+ PackagePublishingStatus,
+ )
from lp.soyuz.interfaces.binarypackagebuild import (
IBinaryPackageBuild,
IBinaryPackageBuildSet,
@@ -39,7 +50,12 @@
from lp.soyuz.model.buildpackagejob import BuildPackageJob
from lp.soyuz.model.processor import ProcessorFamilySet
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
-from lp.testing import TestCaseWithFactory
+from lp.testing import (
+ api_url,
+ login,
+ logout,
+ TestCaseWithFactory,
+ )
class TestBinaryPackageBuild(TestCaseWithFactory):
@@ -170,6 +186,46 @@
self.assertEquals("Somebody <somebody@xxxxxxxxxx>",
self.build.getUploader(MockChanges()))
+ def test_can_be_cancelled(self):
+ # For all states that can be cancelled, assert can_be_cancelled
+ # returns True.
+ ok_cases = [
+ BuildStatus.BUILDING,
+ BuildStatus.NEEDSBUILD,
+ ]
+ for status in BuildStatus:
+ if status in ok_cases:
+ self.assertTrue(self.build.can_be_cancelled)
+ else:
+ self.assertFalse(self.build.can_be_cancelled)
+
+ def test_can_be_cancelled_virtuality(self):
+ # Only virtual builds can be cancelled.
+ bq = removeSecurityProxy(self.build.queueBuild())
+ bq.virtualized = True
+ self.assertTrue(self.build.can_be_cancelled)
+ bq.virtualized = False
+ self.assertFalse(self.build.can_be_cancelled)
+
+ def test_cancel_not_in_progress(self):
+ # Testing the cancel() method for a pending build.
+ ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
+ build = self.factory.makeBinaryPackageBuild(archive=ppa)
+ build.queueBuild()
+ build.cancel()
+ self.assertEqual(BuildStatus.CANCELLED, build.status)
+ self.assertIs(None, build.buildqueue_record)
+
+ def test_cancel_in_progress(self):
+ # Testing the cancel() method for a building build.
+ ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
+ build = self.factory.makeBinaryPackageBuild(archive=ppa)
+ bq = build.queueBuild()
+ build.status = BuildStatus.BUILDING
+ build.cancel()
+ self.assertEqual(BuildStatus.CANCELLING, build.status)
+ self.assertEqual(bq, build.buildqueue_record)
+
class TestBuildUpdateDependencies(TestCaseWithFactory):
@@ -503,3 +559,44 @@
class TestHandleStatusForBinaryPackageBuild(
MakeBinaryPackageBuildMixin, TestHandleStatusMixin, TrialTestCase):
"""IPackageBuild.handleStatus works with binary builds."""
+
+
+class TestBinaryPackageBuildWebservice(TestCaseWithFactory):
+ """Test cases for BinaryPackageBuild on the webservice.
+
+ NB. Note that most tests are currently in
+ lib/lp/soyuz/stories/webservice/xx-builds.txt but unit tests really
+ ought to be here instead.
+ """
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestBinaryPackageBuildWebservice, self).setUp()
+ self.ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
+ self.build = self.factory.makeBinaryPackageBuild(archive=self.ppa)
+ self.webservice = webservice_for_person(
+ self.ppa.owner, permission=OAuthPermission.WRITE_PUBLIC)
+ login(ANONYMOUS)
+
+ def test_can_be_cancelled_is_exported(self):
+ expected = self.build.can_be_cancelled
+ entry_url = api_url(self.build)
+ logout()
+ entry = self.webservice.get(
+ entry_url, api_version='devel').jsonBody()
+ self.assertEqual(expected, entry['can_be_cancelled'])
+
+ def test_cancel_is_exported(self):
+ build_url = api_url(self.build)
+ self.build.queueBuild()
+ logout()
+ entry = self.webservice.get(
+ build_url, api_version='devel').jsonBody()
+ response = self.webservice.named_post(
+ entry['self_link'], 'cancel', api_version='devel')
+ self.assertEqual(200, response.status)
+ entry = self.webservice.get(
+ build_url, api_version='devel').jsonBody()
+ self.assertEqual(BuildStatus.CANCELLED.title, entry['buildstate'])
+