Merge lp:~julian-edwards/launchpad/suspend-running-jobs-bug-788612 into lp:launchpad

Proposed by Julian Edwards
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 13128
Proposed branch: lp:~julian-edwards/launchpad/suspend-running-jobs-bug-788612
Merge into: lp:launchpad
Diff against target: 113 lines (+28/-2)
5 files modified
lib/lp/services/job/interfaces/job.py (+6/-0)
lib/lp/services/job/model/job.py (+1/-0)
lib/lp/services/job/runner.py (+5/-0)
lib/lp/services/job/tests/test_job.py (+3/-2)
lib/lp/services/job/tests/test_runner.py (+13/-0)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/suspend-running-jobs-bug-788612
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+62494@code.launchpad.net

Commit message

[r=abentley][bug=788612] Allow running Jobs to suspend themselves

Description of the change

= Summary =
Allow running Jobs to suspend themselves.

== Pre-implementation notes ==
Talked to Aaron and we agreed this strategy.

== Implementation details ==
There's a new exception SuspendJobError defined in interfaces/job.py which any job can raise. It is caught in the main runner code which will subsequently change the job's status to SUSPENDED.

== Tests ==
bin/test -cvv test_runner test_runJob_with_SuspendJobError

== Demo and Q/A ==
n/a

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/services/job/tests/test_runner.py
  lib/lp/services/job/runner.py
  lib/lp/services/job/interfaces/job.py
  lib/lp/services/job/model/job.py

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

I think SuspendJobError is one of those exceptions that's not actually an error, but no need to bikeshed on the name.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/job/interfaces/job.py'
--- lib/lp/services/job/interfaces/job.py 2011-05-19 15:15:16 +0000
+++ lib/lp/services/job/interfaces/job.py 2011-05-27 08:19:28 +0000
@@ -14,6 +14,7 @@
14 'ITwistedJobSource',14 'ITwistedJobSource',
15 'JobStatus',15 'JobStatus',
16 'LeaseHeld',16 'LeaseHeld',
17 'SuspendJobException',
17 ]18 ]
1819
1920
@@ -35,6 +36,11 @@
35from canonical.launchpad import _36from canonical.launchpad import _
3637
3738
39class SuspendJobException(Exception):
40 """Raised when a running job wants to suspend itself."""
41 pass
42
43
38class LeaseHeld(Exception):44class LeaseHeld(Exception):
39 """Raised when attempting to acquire a list that is already held."""45 """Raised when attempting to acquire a list that is already held."""
4046
4147
=== modified file 'lib/lp/services/job/model/job.py'
--- lib/lp/services/job/model/job.py 2011-05-19 15:15:16 +0000
+++ lib/lp/services/job/model/job.py 2011-05-27 08:19:28 +0000
@@ -78,6 +78,7 @@
78 JobStatus.RUNNING:78 JobStatus.RUNNING:
79 (JobStatus.COMPLETED,79 (JobStatus.COMPLETED,
80 JobStatus.FAILED,80 JobStatus.FAILED,
81 JobStatus.SUSPENDED,
81 JobStatus.WAITING),82 JobStatus.WAITING),
82 JobStatus.FAILED: (),83 JobStatus.FAILED: (),
83 JobStatus.COMPLETED: (),84 JobStatus.COMPLETED: (),
8485
=== modified file 'lib/lp/services/job/runner.py'
--- lib/lp/services/job/runner.py 2011-05-19 15:15:16 +0000
+++ lib/lp/services/job/runner.py 2011-05-27 08:19:28 +0000
@@ -51,6 +51,7 @@
51 IJob,51 IJob,
52 IRunnableJob,52 IRunnableJob,
53 LeaseHeld,53 LeaseHeld,
54 SuspendJobException,
54 )55 )
55from lp.services.mail.sendmail import MailController56from lp.services.mail.sendmail import MailController
56from lp.services.scripts.base import LaunchpadCronScript57from lp.services.scripts.base import LaunchpadCronScript
@@ -182,6 +183,10 @@
182 self.logger.exception(183 self.logger.exception(
183 "Scheduling retry due to %s.", e.__class__.__name__)184 "Scheduling retry due to %s.", e.__class__.__name__)
184 do_retry = True185 do_retry = True
186 except SuspendJobException:
187 self.logger.debug("Job suspended itself")
188 job.suspend()
189 self.incomplete_jobs.append(job)
185 except Exception:190 except Exception:
186 self.logger.exception("Job execution raised an exception.")191 self.logger.exception("Job execution raised an exception.")
187 transaction.abort()192 transaction.abort()
188193
=== modified file 'lib/lp/services/job/tests/test_job.py'
--- lib/lp/services/job/tests/test_job.py 2011-03-23 15:30:02 +0000
+++ lib/lp/services/job/tests/test_job.py 2011-05-27 08:19:28 +0000
@@ -169,9 +169,10 @@
169 JobStatus.SUSPENDED)169 JobStatus.SUSPENDED)
170170
171 def test_suspend_when_running(self):171 def test_suspend_when_running(self):
172 """When a job is running, attempting to suspend is invalid."""172 """When a job is running, attempting to suspend is valid."""
173 job = Job(_status=JobStatus.RUNNING)173 job = Job(_status=JobStatus.RUNNING)
174 self.assertRaises(InvalidTransition, job.suspend)174 job.suspend()
175 self.assertEqual(JobStatus.SUSPENDED, job.status)
175176
176 def test_suspend_when_completed(self):177 def test_suspend_when_completed(self):
177 """When a job is completed, attempting to suspend is invalid."""178 """When a job is completed, attempting to suspend is invalid."""
178179
=== modified file 'lib/lp/services/job/tests/test_runner.py'
--- lib/lp/services/job/tests/test_runner.py 2011-05-25 14:07:19 +0000
+++ lib/lp/services/job/tests/test_runner.py 2011-05-27 08:19:28 +0000
@@ -22,6 +22,7 @@
22from lp.services.job.interfaces.job import (22from lp.services.job.interfaces.job import (
23 IRunnableJob,23 IRunnableJob,
24 JobStatus,24 JobStatus,
25 SuspendJobException,
25 )26 )
26from lp.services.job.model.job import Job27from lp.services.job.model.job import Job
27from lp.services.job.runner import (28from lp.services.job.runner import (
@@ -38,6 +39,7 @@
38 TestCaseWithFactory,39 TestCaseWithFactory,
39 ZopeTestInSubProcess,40 ZopeTestInSubProcess,
40 )41 )
42from lp.testing.fakemethod import FakeMethod
41from lp.testing.mail_helpers import pop_notifications43from lp.testing.mail_helpers import pop_notifications
4244
4345
@@ -363,6 +365,17 @@
363 runner.runJobHandleError(job)365 runner.runJobHandleError(job)
364 self.assertEqual(1, len(self.oopses))366 self.assertEqual(1, len(self.oopses))
365367
368 def test_runJob_with_SuspendJobException(self):
369 # A job that raises SuspendJobError should end up suspended.
370 job = NullJob('suspended')
371 job.run = FakeMethod(failure=SuspendJobException())
372 runner = JobRunner([job])
373 runner.runJob(job)
374
375 self.assertEqual(JobStatus.SUSPENDED, job.status)
376 self.assertNotIn(job, runner.completed_jobs)
377 self.assertIn(job, runner.incomplete_jobs)
378
366379
367class StuckJob(BaseRunnableJob):380class StuckJob(BaseRunnableJob):
368 """Simulation of a job that stalls."""381 """Simulation of a job that stalls."""