Merge lp:~allenap/launchpad/job-status-validator into lp:launchpad
- job-status-validator
- Merge into devel
Status: | Work in progress |
---|---|
Proposed branch: | lp:~allenap/launchpad/job-status-validator |
Merge into: | lp:launchpad |
Diff against target: |
694 lines (+182/-86) 18 files modified
lib/lp/buildmaster/model/builder.py (+2/-2) lib/lp/buildmaster/model/buildqueue.py (+1/-1) lib/lp/code/model/branch.py (+2/-2) lib/lp/code/model/branchjob.py (+2/-2) lib/lp/code/model/branchmergeproposal.py (+1/-1) lib/lp/code/model/branchmergeproposaljob.py (+2/-2) lib/lp/code/model/tests/test_branchjob.py (+1/-1) lib/lp/code/scripts/tests/test_scan_branches.py (+1/-1) lib/lp/registry/model/persontransferjob.py (+1/-1) lib/lp/registry/tests/test_person_merge_job.py (+1/-1) lib/lp/services/job/model/job.py (+38/-27) lib/lp/services/job/testing/__init__.py (+37/-0) lib/lp/services/job/tests/test_job.py (+76/-26) lib/lp/soyuz/model/distroseriesdifferencejob.py (+1/-1) lib/lp/soyuz/model/packagecopyjob.py (+2/-2) lib/lp/soyuz/tests/test_distroseriesdifferencejob.py (+2/-1) lib/lp/soyuz/tests/test_packagecopyjob.py (+11/-14) lib/lp/translations/model/translationsharingjob.py (+1/-1) |
To merge this branch: | bzr merge lp:~allenap/launchpad/job-status-validator |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Disapprove | ||
Jeroen T. Vermeulen (community) | code | Needs Information | |
Ian Booth (community) | code* | Approve | |
Review via email: mp+62509@code.launchpad.net |
Commit message
Consolidate the purpose of Job's status/
Description of the change
This removes the status/
is a gettable property, _status represents the database column, and
_set_status is what Job's state transition methods call).
Why?
- I've lost time before wondering why queries aren't working before
realising I need to use Job._status, so this clears that up.
- It simplifies the code because everything goes via the status
property, and all transitions are validated.
- Even code within Job can't (accidentally) cheat on status
transitions, to the extent that a helper, prepare_job(), was
introduced in the tests in order to get a Job in a desired starting
state.
Jeroen T. Vermeulen (jtv) wrote : | # |
This one's bitten me too, so I'll be very glad to see it go.
One thing I missed was a pre-imp. Gavin, did you verify that there isn't some good but invisible reason for the current design? I believe Aaron wrote this, and he seems to prefer explaining his code in person over leaving comments.
Jeroen T. Vermeulen (jtv) wrote : | # |
Ahem. Something went wrong with my vote there.
Gavin Panella (allenap) wrote : | # |
Hi Aaron,
I forgot to ping you today about this, so for now this will have to
do. When you have a chance can you have a gander at this, especially
with respect to Jeroen's comment:
> One thing I missed was a pre-imp. Gavin, did you verify that there
> isn't some good but invisible reason for the current design? I
> believe Aaron wrote this, and he seems to prefer explaining his code
> in person over leaving comments.
I'm very bad at doing pre-implementation calls, so I apologise in
advance if I've done something stupid because of the lack of that.
I'm back on Tuesday, so if you'd like to talk about it let's try to
talk then.
Thanks, Gavin.
Aaron Bentley (abentley) wrote : | # |
I didn't make _status private because only some transitions are valid. That would be silly, because you can just make a read/write property for that. And the solution you've given would be even better in that case.
I made status private because status is part of a more complex state that includes the date_started and date_finished. It should not be set in isolation, so I made it private and provided methods for setting it to the desired state. By providing a writable status, you encourage people to set it directly, which will produce inconsistent states (e.g. Job.status == JobStatus.RUNNING, but Job.date_started is None).
For example, prepare_job will apparently produce inconsistent states. IIUC, prepare_
Julian Edwards (julian-edwards) wrote : | # |
Why not have a public status that you can read and use in queries, and make the validator reject everything because you're not using the mutator methods.
This is what happens on PackageUpload - see lib/lp/
The current situation is plainly suboptimal and I agree with the intention of this branch.
Aaron Bentley (abentley) wrote : | # |
If we can have a readonly "status" attribute and still write to the database column via mutators, that certainly works for me.
Julian Edwards (julian-edwards) wrote : | # |
That's exactly what the PackageUpload code is doing.
- 13127. By Gavin Panella
-
Test to demonstrate Job's attribute access.
- 13128. By Gavin Panella
-
Test access of all attributes at once, so that we get a complete picture on failure.
- 13129. By Gavin Panella
-
Merge devel, resolving several conflicts.
- 13130. By Gavin Panella
-
Change prepare_job() to automatically figure out how to transition.
- 13131. By Gavin Panella
-
Switch to a single argument version of prepare_job().
- 13132. By Gavin Panella
-
Move 'invariants' code into validate_
status_ transition( ). - 13133. By Gavin Panella
-
Use super(); Exception is now a new-style class.
- 13134. By Gavin Panella
-
Merge devel.
- 13135. By Gavin Panella
-
Move prepare_job into a testing package.
- 13136. By Gavin Panella
-
Merge devel.
- 13137. By Gavin Panella
-
Adjust some more parts of Launchpad to the Job._status -> .status change.
- 13138. By Gavin Panella
-
Merge devel.
Unmerged revisions
- 13138. By Gavin Panella
-
Merge devel.
- 13137. By Gavin Panella
-
Adjust some more parts of Launchpad to the Job._status -> .status change.
- 13136. By Gavin Panella
-
Merge devel.
- 13135. By Gavin Panella
-
Move prepare_job into a testing package.
- 13134. By Gavin Panella
-
Merge devel.
- 13133. By Gavin Panella
-
Use super(); Exception is now a new-style class.
- 13132. By Gavin Panella
-
Move 'invariants' code into validate_
status_ transition( ). - 13131. By Gavin Panella
-
Switch to a single argument version of prepare_job().
- 13130. By Gavin Panella
-
Change prepare_job() to automatically figure out how to transition.
- 13129. By Gavin Panella
-
Merge devel, resolving several conflicts.
Preview Diff
1 | === modified file 'lib/lp/buildmaster/model/builder.py' | |||
2 | --- lib/lp/buildmaster/model/builder.py 2011-09-13 05:23:16 +0000 | |||
3 | +++ lib/lp/buildmaster/model/builder.py 2011-10-07 13:40:29 +0000 | |||
4 | @@ -854,7 +854,7 @@ | |||
5 | 854 | BuildQueue, | 854 | BuildQueue, |
6 | 855 | BuildQueue.job == Job.id, | 855 | BuildQueue.job == Job.id, |
7 | 856 | BuildQueue.builder == self.id, | 856 | BuildQueue.builder == self.id, |
9 | 857 | Job._status == JobStatus.RUNNING, | 857 | Job.status == JobStatus.RUNNING, |
10 | 858 | Job.date_started != None).one() | 858 | Job.date_started != None).one() |
11 | 859 | 859 | ||
12 | 860 | def getCurrentBuildFarmJob(self): | 860 | def getCurrentBuildFarmJob(self): |
13 | @@ -914,7 +914,7 @@ | |||
14 | 914 | Coalesce(BuildQueue.virtualized, True)), | 914 | Coalesce(BuildQueue.virtualized, True)), |
15 | 915 | Processor.id == BuildQueue.processorID, | 915 | Processor.id == BuildQueue.processorID, |
16 | 916 | Job.id == BuildQueue.jobID, | 916 | Job.id == BuildQueue.jobID, |
18 | 917 | Job._status == JobStatus.WAITING).group_by( | 917 | Job.status == JobStatus.WAITING).group_by( |
19 | 918 | Processor, Coalesce(BuildQueue.virtualized, True)) | 918 | Processor, Coalesce(BuildQueue.virtualized, True)) |
20 | 919 | 919 | ||
21 | 920 | result_dict = {'virt': {}, 'nonvirt': {}} | 920 | result_dict = {'virt': {}, 'nonvirt': {}} |
22 | 921 | 921 | ||
23 | === modified file 'lib/lp/buildmaster/model/buildqueue.py' | |||
24 | --- lib/lp/buildmaster/model/buildqueue.py 2010-08-30 15:00:23 +0000 | |||
25 | +++ lib/lp/buildmaster/model/buildqueue.py 2011-10-07 13:40:29 +0000 | |||
26 | @@ -553,6 +553,6 @@ | |||
27 | 553 | # Avoid corrupt build jobs where the builder is None. | 553 | # Avoid corrupt build jobs where the builder is None. |
28 | 554 | BuildQueue.builder != None, | 554 | BuildQueue.builder != None, |
29 | 555 | # status is a property. Let's use _status. | 555 | # status is a property. Let's use _status. |
31 | 556 | Job._status == JobStatus.RUNNING, | 556 | Job.status == JobStatus.RUNNING, |
32 | 557 | Job.date_started != None) | 557 | Job.date_started != None) |
33 | 558 | return result_set | 558 | return result_set |
34 | 559 | 559 | ||
35 | === modified file 'lib/lp/code/model/branch.py' | |||
36 | --- lib/lp/code/model/branch.py 2011-09-27 00:57:13 +0000 | |||
37 | +++ lib/lp/code/model/branch.py 2011-10-07 13:40:29 +0000 | |||
38 | @@ -1176,8 +1176,8 @@ | |||
39 | 1176 | BranchJob, | 1176 | BranchJob, |
40 | 1177 | BranchJob.branch == self, | 1177 | BranchJob.branch == self, |
41 | 1178 | Job.id == BranchJob.jobID, | 1178 | Job.id == BranchJob.jobID, |
44 | 1179 | Job._status != JobStatus.COMPLETED, | 1179 | Job.status != JobStatus.COMPLETED, |
45 | 1180 | Job._status != JobStatus.FAILED, | 1180 | Job.status != JobStatus.FAILED, |
46 | 1181 | BranchJob.job_type == BranchJobType.UPGRADE_BRANCH) | 1181 | BranchJob.job_type == BranchJobType.UPGRADE_BRANCH) |
47 | 1182 | return jobs.count() > 0 | 1182 | return jobs.count() > 0 |
48 | 1183 | 1183 | ||
49 | 1184 | 1184 | ||
50 | === modified file 'lib/lp/code/model/branchjob.py' | |||
51 | --- lib/lp/code/model/branchjob.py 2011-08-26 00:43:31 +0000 | |||
52 | +++ lib/lp/code/model/branchjob.py 2011-10-07 13:40:29 +0000 | |||
53 | @@ -926,8 +926,8 @@ | |||
54 | 926 | Job.id == BranchJob.jobID, | 926 | Job.id == BranchJob.jobID, |
55 | 927 | BranchJob.branch == branch, | 927 | BranchJob.branch == branch, |
56 | 928 | BranchJob.job_type == BranchJobType.ROSETTA_UPLOAD, | 928 | BranchJob.job_type == BranchJobType.ROSETTA_UPLOAD, |
59 | 929 | Job._status != JobStatus.COMPLETED, | 929 | Job.status != JobStatus.COMPLETED, |
60 | 930 | Job._status != JobStatus.FAILED) | 930 | Job.status != JobStatus.FAILED) |
61 | 931 | if since is not None: | 931 | if since is not None: |
62 | 932 | match = And(match, Job.date_created > since) | 932 | match = And(match, Job.date_created > since) |
63 | 933 | jobs = store.using(BranchJob, Job).find((BranchJob), match) | 933 | jobs = store.using(BranchJob, Job).find((BranchJob), match) |
64 | 934 | 934 | ||
65 | === modified file 'lib/lp/code/model/branchmergeproposal.py' | |||
66 | --- lib/lp/code/model/branchmergeproposal.py 2011-09-14 10:19:43 +0000 | |||
67 | +++ lib/lp/code/model/branchmergeproposal.py 2011-10-07 13:40:29 +0000 | |||
68 | @@ -206,7 +206,7 @@ | |||
69 | 206 | BranchMergeProposalJob.job_type == | 206 | BranchMergeProposalJob.job_type == |
70 | 207 | BranchMergeProposalJobType.UPDATE_PREVIEW_DIFF, | 207 | BranchMergeProposalJobType.UPDATE_PREVIEW_DIFF, |
71 | 208 | BranchMergeProposalJob.job == Job.id, | 208 | BranchMergeProposalJob.job == Job.id, |
73 | 209 | Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) | 209 | Job.status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) |
74 | 210 | job = jobs.order_by(Job.scheduled_start, Job.date_created).first() | 210 | job = jobs.order_by(Job.scheduled_start, Job.date_created).first() |
75 | 211 | if job is not None: | 211 | if job is not None: |
76 | 212 | return BranchMergeProposalJobFactory.create(job) | 212 | return BranchMergeProposalJobFactory.create(job) |
77 | 213 | 213 | ||
78 | === modified file 'lib/lp/code/model/branchmergeproposaljob.py' | |||
79 | --- lib/lp/code/model/branchmergeproposaljob.py 2011-09-26 20:00:50 +0000 | |||
80 | +++ lib/lp/code/model/branchmergeproposaljob.py 2011-10-07 13:40:29 +0000 | |||
81 | @@ -779,7 +779,7 @@ | |||
82 | 779 | TargetBranch = ClassAlias(Branch) | 779 | TargetBranch = ClassAlias(Branch) |
83 | 780 | clauses = [ | 780 | clauses = [ |
84 | 781 | BranchMergeProposalJob.job == Job.id, | 781 | BranchMergeProposalJob.job == Job.id, |
86 | 782 | Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING]), | 782 | Job.status.is_in([JobStatus.WAITING, JobStatus.RUNNING]), |
87 | 783 | BranchMergeProposalJob.branch_merge_proposal == | 783 | BranchMergeProposalJob.branch_merge_proposal == |
88 | 784 | BranchMergeProposal.id, BranchMergeProposal.source_branch == | 784 | BranchMergeProposal.id, BranchMergeProposal.source_branch == |
89 | 785 | SourceBranch.id, BranchMergeProposal.target_branch == | 785 | SourceBranch.id, BranchMergeProposal.target_branch == |
90 | @@ -794,7 +794,7 @@ | |||
91 | 794 | # the date_created, then job type. This should give us all creation | 794 | # the date_created, then job type. This should give us all creation |
92 | 795 | # jobs before comment jobs. | 795 | # jobs before comment jobs. |
93 | 796 | jobs = jobs.order_by( | 796 | jobs = jobs.order_by( |
95 | 797 | Desc(Job._status), Job.date_created, | 797 | Desc(Job.status), Job.date_created, |
96 | 798 | Desc(BranchMergeProposalJob.job_type)) | 798 | Desc(BranchMergeProposalJob.job_type)) |
97 | 799 | # Now only return one job for any given merge proposal. | 799 | # Now only return one job for any given merge proposal. |
98 | 800 | ready_jobs = [] | 800 | ready_jobs = [] |
99 | 801 | 801 | ||
100 | === modified file 'lib/lp/code/model/tests/test_branchjob.py' | |||
101 | --- lib/lp/code/model/tests/test_branchjob.py 2011-10-06 16:26:36 +0000 | |||
102 | +++ lib/lp/code/model/tests/test_branchjob.py 2011-10-07 13:40:29 +0000 | |||
103 | @@ -1227,7 +1227,7 @@ | |||
104 | 1227 | job = self._makeRosettaUploadJob() | 1227 | job = self._makeRosettaUploadJob() |
105 | 1228 | job.job.start() | 1228 | job.job.start() |
106 | 1229 | job.job.complete() | 1229 | job.job.complete() |
108 | 1230 | job.job._status = JobStatus.FAILED | 1230 | job.job.status = JobStatus.FAILED |
109 | 1231 | unfinished_jobs = list(RosettaUploadJob.findUnfinishedJobs( | 1231 | unfinished_jobs = list(RosettaUploadJob.findUnfinishedJobs( |
110 | 1232 | self.branch)) | 1232 | self.branch)) |
111 | 1233 | self.assertEqual([], unfinished_jobs) | 1233 | self.assertEqual([], unfinished_jobs) |
112 | 1234 | 1234 | ||
113 | === modified file 'lib/lp/code/scripts/tests/test_scan_branches.py' | |||
114 | --- lib/lp/code/scripts/tests/test_scan_branches.py 2010-10-26 15:47:24 +0000 | |||
115 | +++ lib/lp/code/scripts/tests/test_scan_branches.py 2011-10-07 13:40:29 +0000 | |||
116 | @@ -76,7 +76,7 @@ | |||
117 | 76 | result = store.find( | 76 | result = store.find( |
118 | 77 | BranchJob, | 77 | BranchJob, |
119 | 78 | BranchJob.jobID == Job.id, | 78 | BranchJob.jobID == Job.id, |
121 | 79 | Job._status == JobStatus.WAITING, | 79 | Job.status == JobStatus.WAITING, |
122 | 80 | BranchJob.job_type == BranchJobType.REVISION_MAIL, | 80 | BranchJob.job_type == BranchJobType.REVISION_MAIL, |
123 | 81 | BranchJob.branch == db_branch) | 81 | BranchJob.branch == db_branch) |
124 | 82 | self.assertEqual(result.count(), 1) | 82 | self.assertEqual(result.count(), 1) |
125 | 83 | 83 | ||
126 | === modified file 'lib/lp/registry/model/persontransferjob.py' | |||
127 | --- lib/lp/registry/model/persontransferjob.py 2011-08-12 14:39:51 +0000 | |||
128 | +++ lib/lp/registry/model/persontransferjob.py 2011-10-07 13:40:29 +0000 | |||
129 | @@ -370,7 +370,7 @@ | |||
130 | 370 | conditions = [ | 370 | conditions = [ |
131 | 371 | PersonTransferJob.job_type == cls.class_job_type, | 371 | PersonTransferJob.job_type == cls.class_job_type, |
132 | 372 | PersonTransferJob.job_id == Job.id, | 372 | PersonTransferJob.job_id == Job.id, |
134 | 373 | Job._status.is_in(Job.PENDING_STATUSES)] | 373 | Job.status.is_in(Job.PENDING_STATUSES)] |
135 | 374 | arg_conditions = [] | 374 | arg_conditions = [] |
136 | 375 | if from_person is not None: | 375 | if from_person is not None: |
137 | 376 | arg_conditions.append( | 376 | arg_conditions.append( |
138 | 377 | 377 | ||
139 | === modified file 'lib/lp/registry/tests/test_person_merge_job.py' | |||
140 | --- lib/lp/registry/tests/test_person_merge_job.py 2011-07-30 09:14:44 +0000 | |||
141 | +++ lib/lp/registry/tests/test_person_merge_job.py 2011-10-07 13:40:29 +0000 | |||
142 | @@ -174,7 +174,7 @@ | |||
143 | 174 | def test_find_only_pending_or_running(self): | 174 | def test_find_only_pending_or_running(self): |
144 | 175 | # find() only returns jobs that are pending. | 175 | # find() only returns jobs that are pending. |
145 | 176 | for status in JobStatus.items: | 176 | for status in JobStatus.items: |
147 | 177 | removeSecurityProxy(self.job.job)._status = status | 177 | removeSecurityProxy(self.job.job).status = status |
148 | 178 | if status in Job.PENDING_STATUSES: | 178 | if status in Job.PENDING_STATUSES: |
149 | 179 | self.assertEqual([self.job], self.find()) | 179 | self.assertEqual([self.job], self.find()) |
150 | 180 | else: | 180 | else: |
151 | 181 | 181 | ||
152 | === modified file 'lib/lp/services/job/model/job.py' | |||
153 | --- lib/lp/services/job/model/job.py 2011-09-21 13:06:53 +0000 | |||
154 | +++ lib/lp/services/job/model/job.py 2011-10-07 13:40:29 +0000 | |||
155 | @@ -48,9 +48,33 @@ | |||
156 | 48 | """Invalid transition from one job status to another attempted.""" | 48 | """Invalid transition from one job status to another attempted.""" |
157 | 49 | 49 | ||
158 | 50 | def __init__(self, current_status, requested_status): | 50 | def __init__(self, current_status, requested_status): |
162 | 51 | Exception.__init__( | 51 | super(InvalidTransition, self).__init__( |
163 | 52 | self, 'Transition from %s to %s is invalid.' % | 52 | 'Transition from %s to %s is invalid.' % ( |
164 | 53 | (current_status, requested_status)) | 53 | current_status, requested_status)) |
165 | 54 | |||
166 | 55 | |||
167 | 56 | def validate_status_transition(job, attr, status): | ||
168 | 57 | """Validate the status transition and maintain "invariants".""" | ||
169 | 58 | assert attr == "status" | ||
170 | 59 | if status not in job._valid_transitions[job.status]: | ||
171 | 60 | raise InvalidTransition(job.status, status) | ||
172 | 61 | |||
173 | 62 | # General invariants. | ||
174 | 63 | if status == JobStatus.RUNNING: | ||
175 | 64 | job.date_started = datetime.datetime.now(UTC) | ||
176 | 65 | job.date_finished = None | ||
177 | 66 | job.attempt_count += 1 | ||
178 | 67 | if status in (JobStatus.COMPLETED, JobStatus.FAILED): | ||
179 | 68 | job.date_finished = datetime.datetime.now(UTC) | ||
180 | 69 | |||
181 | 70 | # Transition-specific invariants. | ||
182 | 71 | transition = job.status, status | ||
183 | 72 | if transition == (JobStatus.RUNNING, JobStatus.WAITING): | ||
184 | 73 | job.date_finished = datetime.datetime.now(UTC) | ||
185 | 74 | if transition == (JobStatus.SUSPENDED, JobStatus.WAITING): | ||
186 | 75 | job.lease_expires = None | ||
187 | 76 | |||
188 | 77 | return status | ||
189 | 54 | 78 | ||
190 | 55 | 79 | ||
191 | 56 | class Job(SQLBase): | 80 | class Job(SQLBase): |
192 | @@ -70,8 +94,9 @@ | |||
193 | 70 | 94 | ||
194 | 71 | log = StringCol() | 95 | log = StringCol() |
195 | 72 | 96 | ||
198 | 73 | _status = EnumCol(enum=JobStatus, notNull=True, default=JobStatus.WAITING, | 97 | status = EnumCol( |
199 | 74 | dbName='status') | 98 | enum=JobStatus, notNull=True, default=JobStatus.WAITING, |
200 | 99 | dbName='status', storm_validator=validate_status_transition) | ||
201 | 75 | 100 | ||
202 | 76 | attempt_count = IntCol(default=0) | 101 | attempt_count = IntCol(default=0) |
203 | 77 | 102 | ||
204 | @@ -102,13 +127,6 @@ | |||
205 | 102 | JobStatus.RUNNING, | 127 | JobStatus.RUNNING, |
206 | 103 | JobStatus.SUSPENDED)) | 128 | JobStatus.SUSPENDED)) |
207 | 104 | 129 | ||
208 | 105 | def _set_status(self, status): | ||
209 | 106 | if status not in self._valid_transitions[self._status]: | ||
210 | 107 | raise InvalidTransition(self._status, status) | ||
211 | 108 | self._status = status | ||
212 | 109 | |||
213 | 110 | status = property(lambda x: x._status) | ||
214 | 111 | |||
215 | 112 | @property | 130 | @property |
216 | 113 | def is_pending(self): | 131 | def is_pending(self): |
217 | 114 | """See `IJob`.""" | 132 | """See `IJob`.""" |
218 | @@ -153,42 +171,35 @@ | |||
219 | 153 | 171 | ||
220 | 154 | def start(self): | 172 | def start(self): |
221 | 155 | """See `IJob`.""" | 173 | """See `IJob`.""" |
226 | 156 | self._set_status(JobStatus.RUNNING) | 174 | self.status = JobStatus.RUNNING |
223 | 157 | self.date_started = datetime.datetime.now(UTC) | ||
224 | 158 | self.date_finished = None | ||
225 | 159 | self.attempt_count += 1 | ||
227 | 160 | 175 | ||
228 | 161 | def complete(self): | 176 | def complete(self): |
229 | 162 | """See `IJob`.""" | 177 | """See `IJob`.""" |
232 | 163 | self._set_status(JobStatus.COMPLETED) | 178 | self.status = JobStatus.COMPLETED |
231 | 164 | self.date_finished = datetime.datetime.now(UTC) | ||
233 | 165 | 179 | ||
234 | 166 | def fail(self): | 180 | def fail(self): |
235 | 167 | """See `IJob`.""" | 181 | """See `IJob`.""" |
238 | 168 | self._set_status(JobStatus.FAILED) | 182 | self.status = JobStatus.FAILED |
237 | 169 | self.date_finished = datetime.datetime.now(UTC) | ||
239 | 170 | 183 | ||
240 | 171 | def queue(self): | 184 | def queue(self): |
241 | 172 | """See `IJob`.""" | 185 | """See `IJob`.""" |
244 | 173 | self._set_status(JobStatus.WAITING) | 186 | self.status = JobStatus.WAITING |
243 | 174 | self.date_finished = datetime.datetime.now(UTC) | ||
245 | 175 | 187 | ||
246 | 176 | def suspend(self): | 188 | def suspend(self): |
247 | 177 | """See `IJob`.""" | 189 | """See `IJob`.""" |
249 | 178 | self._set_status(JobStatus.SUSPENDED) | 190 | self.status = JobStatus.SUSPENDED |
250 | 179 | 191 | ||
251 | 180 | def resume(self): | 192 | def resume(self): |
252 | 181 | """See `IJob`.""" | 193 | """See `IJob`.""" |
253 | 182 | if self.status is not JobStatus.SUSPENDED: | 194 | if self.status is not JobStatus.SUSPENDED: |
257 | 183 | raise InvalidTransition(self._status, JobStatus.WAITING) | 195 | raise InvalidTransition(self.status, JobStatus.WAITING) |
258 | 184 | self._set_status(JobStatus.WAITING) | 196 | self.status = JobStatus.WAITING |
256 | 185 | self.lease_expires = None | ||
259 | 186 | 197 | ||
260 | 187 | 198 | ||
261 | 188 | Job.ready_jobs = Select( | 199 | Job.ready_jobs = Select( |
262 | 189 | Job.id, | 200 | Job.id, |
263 | 190 | And( | 201 | And( |
265 | 191 | Job._status == JobStatus.WAITING, | 202 | Job.status == JobStatus.WAITING, |
266 | 192 | Or(Job.lease_expires == None, Job.lease_expires < UTC_NOW), | 203 | Or(Job.lease_expires == None, Job.lease_expires < UTC_NOW), |
267 | 193 | Or(Job.scheduled_start == None, Job.scheduled_start <= UTC_NOW), | 204 | Or(Job.scheduled_start == None, Job.scheduled_start <= UTC_NOW), |
268 | 194 | )) | 205 | )) |
269 | 195 | 206 | ||
270 | === added directory 'lib/lp/services/job/testing' | |||
271 | === added file 'lib/lp/services/job/testing/__init__.py' | |||
272 | --- lib/lp/services/job/testing/__init__.py 1970-01-01 00:00:00 +0000 | |||
273 | +++ lib/lp/services/job/testing/__init__.py 2011-10-07 13:40:29 +0000 | |||
274 | @@ -0,0 +1,37 @@ | |||
275 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
276 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
277 | 3 | |||
278 | 4 | """Testing resources for lp.services.job.""" | ||
279 | 5 | |||
280 | 6 | __metaclass__ = type | ||
281 | 7 | |||
282 | 8 | from lp.services.job.model.job import Job | ||
283 | 9 | |||
284 | 10 | |||
285 | 11 | def find_transition(s_from, s_to): | ||
286 | 12 | """Find a valid transition route between `s_from` and `s_to`.""" | ||
287 | 13 | if s_to == s_from: | ||
288 | 14 | return () | ||
289 | 15 | else: | ||
290 | 16 | valid_transitions = Job._valid_transitions.iteritems() | ||
291 | 17 | for from_status, to_statuses in valid_transitions: | ||
292 | 18 | if s_to in to_statuses: | ||
293 | 19 | if s_from == from_status: | ||
294 | 20 | return (s_to,) | ||
295 | 21 | else: | ||
296 | 22 | possible = find_transition(s_from, from_status) | ||
297 | 23 | if possible is not None: | ||
298 | 24 | return possible + (s_to,) | ||
299 | 25 | else: | ||
300 | 26 | return None | ||
301 | 27 | |||
302 | 28 | |||
303 | 29 | def prepare_job(desrired_status, job=None): | ||
304 | 30 | """Creates a new job and transitions its status to `desrired_status`.""" | ||
305 | 31 | if job is None: | ||
306 | 32 | job = Job() | ||
307 | 33 | transitions = find_transition( | ||
308 | 34 | job.status, desrired_status) | ||
309 | 35 | for status in transitions: | ||
310 | 36 | job.status = status | ||
311 | 37 | return job | ||
312 | 0 | 38 | ||
313 | === modified file 'lib/lp/services/job/tests/test_job.py' | |||
314 | --- lib/lp/services/job/tests/test_job.py 2011-09-21 13:06:53 +0000 | |||
315 | +++ lib/lp/services/job/tests/test_job.py 2011-10-07 13:40:29 +0000 | |||
316 | @@ -8,6 +8,12 @@ | |||
317 | 8 | 8 | ||
318 | 9 | import pytz | 9 | import pytz |
319 | 10 | from storm.locals import Store | 10 | from storm.locals import Store |
320 | 11 | from zope.interface.interface import ( | ||
321 | 12 | Attribute, | ||
322 | 13 | Method, | ||
323 | 14 | ) | ||
324 | 15 | from zope.security.interfaces import ForbiddenAttribute | ||
325 | 16 | from zope.security.proxy import ProxyFactory | ||
326 | 11 | 17 | ||
327 | 12 | from canonical.database.constants import UTC_NOW | 18 | from canonical.database.constants import UTC_NOW |
328 | 13 | from canonical.launchpad.interfaces.lpstorm import IStore | 19 | from canonical.launchpad.interfaces.lpstorm import IStore |
329 | @@ -22,6 +28,7 @@ | |||
330 | 22 | Job, | 28 | Job, |
331 | 23 | LeaseHeld, | 29 | LeaseHeld, |
332 | 24 | ) | 30 | ) |
333 | 31 | from lp.services.job.testing import prepare_job | ||
334 | 25 | from lp.testing import ( | 32 | from lp.testing import ( |
335 | 26 | TestCase, | 33 | TestCase, |
336 | 27 | TestCaseWithFactory, | 34 | TestCaseWithFactory, |
337 | @@ -100,17 +107,17 @@ | |||
338 | 100 | 107 | ||
339 | 101 | def test_start_when_completed_is_invalid(self): | 108 | def test_start_when_completed_is_invalid(self): |
340 | 102 | """When a job is completed, attempting to start is invalid.""" | 109 | """When a job is completed, attempting to start is invalid.""" |
342 | 103 | job = Job(_status=JobStatus.COMPLETED) | 110 | job = prepare_job(JobStatus.COMPLETED) |
343 | 104 | self.assertRaises(InvalidTransition, job.start) | 111 | self.assertRaises(InvalidTransition, job.start) |
344 | 105 | 112 | ||
345 | 106 | def test_start_when_failed_is_invalid(self): | 113 | def test_start_when_failed_is_invalid(self): |
346 | 107 | """When a job is failed, attempting to start is invalid.""" | 114 | """When a job is failed, attempting to start is invalid.""" |
348 | 108 | job = Job(_status=JobStatus.FAILED) | 115 | job = prepare_job(JobStatus.FAILED) |
349 | 109 | self.assertRaises(InvalidTransition, job.start) | 116 | self.assertRaises(InvalidTransition, job.start) |
350 | 110 | 117 | ||
351 | 111 | def test_start_when_running_is_invalid(self): | 118 | def test_start_when_running_is_invalid(self): |
352 | 112 | """When a job is running, attempting to start is invalid.""" | 119 | """When a job is running, attempting to start is invalid.""" |
354 | 113 | job = Job(_status=JobStatus.FAILED) | 120 | job = prepare_job(JobStatus.FAILED) |
355 | 114 | self.assertRaises(InvalidTransition, job.start) | 121 | self.assertRaises(InvalidTransition, job.start) |
356 | 115 | 122 | ||
357 | 116 | def test_complete(self): | 123 | def test_complete(self): |
358 | @@ -118,7 +125,7 @@ | |||
359 | 118 | 125 | ||
360 | 119 | It should set date_finished and set the job status to COMPLETED. | 126 | It should set date_finished and set the job status to COMPLETED. |
361 | 120 | """ | 127 | """ |
363 | 121 | job = Job(_status=JobStatus.RUNNING) | 128 | job = Job(status=JobStatus.RUNNING) |
364 | 122 | self.assertEqual(None, job.date_finished) | 129 | self.assertEqual(None, job.date_finished) |
365 | 123 | job.complete() | 130 | job.complete() |
366 | 124 | self.assertNotEqual(None, job.date_finished) | 131 | self.assertNotEqual(None, job.date_finished) |
367 | @@ -126,17 +133,17 @@ | |||
368 | 126 | 133 | ||
369 | 127 | def test_complete_when_waiting_is_invalid(self): | 134 | def test_complete_when_waiting_is_invalid(self): |
370 | 128 | """When a job is waiting, attempting to complete is invalid.""" | 135 | """When a job is waiting, attempting to complete is invalid.""" |
372 | 129 | job = Job(_status=JobStatus.WAITING) | 136 | job = prepare_job(JobStatus.WAITING) |
373 | 130 | self.assertRaises(InvalidTransition, job.complete) | 137 | self.assertRaises(InvalidTransition, job.complete) |
374 | 131 | 138 | ||
375 | 132 | def test_complete_when_completed_is_invalid(self): | 139 | def test_complete_when_completed_is_invalid(self): |
376 | 133 | """When a job is completed, attempting to complete is invalid.""" | 140 | """When a job is completed, attempting to complete is invalid.""" |
378 | 134 | job = Job(_status=JobStatus.COMPLETED) | 141 | job = prepare_job(JobStatus.COMPLETED) |
379 | 135 | self.assertRaises(InvalidTransition, job.complete) | 142 | self.assertRaises(InvalidTransition, job.complete) |
380 | 136 | 143 | ||
381 | 137 | def test_complete_when_failed_is_invalid(self): | 144 | def test_complete_when_failed_is_invalid(self): |
382 | 138 | """When a job is failed, attempting to complete is invalid.""" | 145 | """When a job is failed, attempting to complete is invalid.""" |
384 | 139 | job = Job(_status=JobStatus.FAILED) | 146 | job = prepare_job(JobStatus.FAILED) |
385 | 140 | self.assertRaises(InvalidTransition, job.complete) | 147 | self.assertRaises(InvalidTransition, job.complete) |
386 | 141 | 148 | ||
387 | 142 | def test_fail(self): | 149 | def test_fail(self): |
388 | @@ -144,7 +151,7 @@ | |||
389 | 144 | 151 | ||
390 | 145 | It should set date_finished and set the job status to FAILED. | 152 | It should set date_finished and set the job status to FAILED. |
391 | 146 | """ | 153 | """ |
393 | 147 | job = Job(_status=JobStatus.RUNNING) | 154 | job = Job(status=JobStatus.RUNNING) |
394 | 148 | self.assertEqual(None, job.date_finished) | 155 | self.assertEqual(None, job.date_finished) |
395 | 149 | job.fail() | 156 | job.fail() |
396 | 150 | self.assertNotEqual(None, job.date_finished) | 157 | self.assertNotEqual(None, job.date_finished) |
397 | @@ -152,17 +159,17 @@ | |||
398 | 152 | 159 | ||
399 | 153 | def test_fail_when_waiting_is_invalid(self): | 160 | def test_fail_when_waiting_is_invalid(self): |
400 | 154 | """When a job is waiting, attempting to fail is invalid.""" | 161 | """When a job is waiting, attempting to fail is invalid.""" |
402 | 155 | job = Job(_status=JobStatus.WAITING) | 162 | job = prepare_job(JobStatus.WAITING) |
403 | 156 | self.assertRaises(InvalidTransition, job.fail) | 163 | self.assertRaises(InvalidTransition, job.fail) |
404 | 157 | 164 | ||
405 | 158 | def test_fail_when_completed_is_invalid(self): | 165 | def test_fail_when_completed_is_invalid(self): |
406 | 159 | """When a job is completed, attempting to fail is invalid.""" | 166 | """When a job is completed, attempting to fail is invalid.""" |
408 | 160 | job = Job(_status=JobStatus.COMPLETED) | 167 | job = prepare_job(JobStatus.COMPLETED) |
409 | 161 | self.assertRaises(InvalidTransition, job.fail) | 168 | self.assertRaises(InvalidTransition, job.fail) |
410 | 162 | 169 | ||
411 | 163 | def test_fail_when_failed_is_invalid(self): | 170 | def test_fail_when_failed_is_invalid(self): |
412 | 164 | """When a job is failed, attempting to fail is invalid.""" | 171 | """When a job is failed, attempting to fail is invalid.""" |
414 | 165 | job = Job(_status=JobStatus.FAILED) | 172 | job = prepare_job(JobStatus.FAILED) |
415 | 166 | self.assertRaises(InvalidTransition, job.fail) | 173 | self.assertRaises(InvalidTransition, job.fail) |
416 | 167 | 174 | ||
417 | 168 | def test_queue(self): | 175 | def test_queue(self): |
418 | @@ -170,7 +177,7 @@ | |||
419 | 170 | 177 | ||
420 | 171 | It should set date_finished, and set status to WAITING. | 178 | It should set date_finished, and set status to WAITING. |
421 | 172 | """ | 179 | """ |
423 | 173 | job = Job(_status=JobStatus.RUNNING) | 180 | job = Job(status=JobStatus.RUNNING) |
424 | 174 | self.assertEqual(None, job.date_finished) | 181 | self.assertEqual(None, job.date_finished) |
425 | 175 | job.queue() | 182 | job.queue() |
426 | 176 | self.assertNotEqual(None, job.date_finished) | 183 | self.assertNotEqual(None, job.date_finished) |
427 | @@ -178,22 +185,22 @@ | |||
428 | 178 | 185 | ||
429 | 179 | def test_queue_when_completed_is_invalid(self): | 186 | def test_queue_when_completed_is_invalid(self): |
430 | 180 | """When a job is completed, attempting to queue is invalid.""" | 187 | """When a job is completed, attempting to queue is invalid.""" |
432 | 181 | job = Job(_status=JobStatus.COMPLETED) | 188 | job = prepare_job(JobStatus.COMPLETED) |
433 | 182 | self.assertRaises(InvalidTransition, job.queue) | 189 | self.assertRaises(InvalidTransition, job.queue) |
434 | 183 | 190 | ||
435 | 184 | def test_queue_when_waiting_is_invalid(self): | 191 | def test_queue_when_waiting_is_invalid(self): |
436 | 185 | """When a job is waiting, attempting to queue is invalid.""" | 192 | """When a job is waiting, attempting to queue is invalid.""" |
438 | 186 | job = Job(_status=JobStatus.WAITING) | 193 | job = prepare_job(JobStatus.WAITING) |
439 | 187 | self.assertRaises(InvalidTransition, job.queue) | 194 | self.assertRaises(InvalidTransition, job.queue) |
440 | 188 | 195 | ||
441 | 189 | def test_queue_when_failed_is_invalid(self): | 196 | def test_queue_when_failed_is_invalid(self): |
442 | 190 | """When a job is failed, attempting to queue is invalid.""" | 197 | """When a job is failed, attempting to queue is invalid.""" |
444 | 191 | job = Job(_status=JobStatus.FAILED) | 198 | job = prepare_job(JobStatus.FAILED) |
445 | 192 | self.assertRaises(InvalidTransition, job.queue) | 199 | self.assertRaises(InvalidTransition, job.queue) |
446 | 193 | 200 | ||
447 | 194 | def test_suspend(self): | 201 | def test_suspend(self): |
448 | 195 | """A job that is in the WAITING state can be suspended.""" | 202 | """A job that is in the WAITING state can be suspended.""" |
450 | 196 | job = Job(_status=JobStatus.WAITING) | 203 | job = prepare_job(JobStatus.WAITING) |
451 | 197 | job.suspend() | 204 | job.suspend() |
452 | 198 | self.assertEqual( | 205 | self.assertEqual( |
453 | 199 | job.status, | 206 | job.status, |
454 | @@ -201,23 +208,23 @@ | |||
455 | 201 | 208 | ||
456 | 202 | def test_suspend_when_running(self): | 209 | def test_suspend_when_running(self): |
457 | 203 | """When a job is running, attempting to suspend is valid.""" | 210 | """When a job is running, attempting to suspend is valid.""" |
459 | 204 | job = Job(_status=JobStatus.RUNNING) | 211 | job = Job(status=JobStatus.RUNNING) |
460 | 205 | job.suspend() | 212 | job.suspend() |
461 | 206 | self.assertEqual(JobStatus.SUSPENDED, job.status) | 213 | self.assertEqual(JobStatus.SUSPENDED, job.status) |
462 | 207 | 214 | ||
463 | 208 | def test_suspend_when_completed(self): | 215 | def test_suspend_when_completed(self): |
464 | 209 | """When a job is completed, attempting to suspend is invalid.""" | 216 | """When a job is completed, attempting to suspend is invalid.""" |
466 | 210 | job = Job(_status=JobStatus.COMPLETED) | 217 | job = prepare_job(JobStatus.COMPLETED) |
467 | 211 | self.assertRaises(InvalidTransition, job.suspend) | 218 | self.assertRaises(InvalidTransition, job.suspend) |
468 | 212 | 219 | ||
469 | 213 | def test_suspend_when_failed(self): | 220 | def test_suspend_when_failed(self): |
470 | 214 | """When a job is failed, attempting to suspend is invalid.""" | 221 | """When a job is failed, attempting to suspend is invalid.""" |
472 | 215 | job = Job(_status=JobStatus.FAILED) | 222 | job = prepare_job(JobStatus.FAILED) |
473 | 216 | self.assertRaises(InvalidTransition, job.suspend) | 223 | self.assertRaises(InvalidTransition, job.suspend) |
474 | 217 | 224 | ||
475 | 218 | def test_resume(self): | 225 | def test_resume(self): |
476 | 219 | """A job that is suspended can be resumed.""" | 226 | """A job that is suspended can be resumed.""" |
478 | 220 | job = Job(_status=JobStatus.SUSPENDED) | 227 | job = Job(status=JobStatus.SUSPENDED) |
479 | 221 | job.resume() | 228 | job.resume() |
480 | 222 | self.assertEqual( | 229 | self.assertEqual( |
481 | 223 | job.status, | 230 | job.status, |
482 | @@ -225,30 +232,30 @@ | |||
483 | 225 | 232 | ||
484 | 226 | def test_resume_clears_lease_expiry(self): | 233 | def test_resume_clears_lease_expiry(self): |
485 | 227 | """A job that resumes should null out the lease_expiry.""" | 234 | """A job that resumes should null out the lease_expiry.""" |
487 | 228 | job = Job(_status=JobStatus.SUSPENDED) | 235 | job = Job(status=JobStatus.SUSPENDED) |
488 | 229 | job.lease_expires = UTC_NOW | 236 | job.lease_expires = UTC_NOW |
489 | 230 | job.resume() | 237 | job.resume() |
490 | 231 | self.assertIs(None, job.lease_expires) | 238 | self.assertIs(None, job.lease_expires) |
491 | 232 | 239 | ||
492 | 233 | def test_resume_when_running(self): | 240 | def test_resume_when_running(self): |
493 | 234 | """When a job is running, attempting to resume is invalid.""" | 241 | """When a job is running, attempting to resume is invalid.""" |
495 | 235 | job = Job(_status=JobStatus.RUNNING) | 242 | job = Job(status=JobStatus.RUNNING) |
496 | 236 | self.assertRaises(InvalidTransition, job.resume) | 243 | self.assertRaises(InvalidTransition, job.resume) |
497 | 237 | 244 | ||
498 | 238 | def test_resume_when_completed(self): | 245 | def test_resume_when_completed(self): |
499 | 239 | """When a job is completed, attempting to resume is invalid.""" | 246 | """When a job is completed, attempting to resume is invalid.""" |
501 | 240 | job = Job(_status=JobStatus.COMPLETED) | 247 | job = prepare_job(JobStatus.COMPLETED) |
502 | 241 | self.assertRaises(InvalidTransition, job.resume) | 248 | self.assertRaises(InvalidTransition, job.resume) |
503 | 242 | 249 | ||
504 | 243 | def test_resume_when_failed(self): | 250 | def test_resume_when_failed(self): |
505 | 244 | """When a job is failed, attempting to resume is invalid.""" | 251 | """When a job is failed, attempting to resume is invalid.""" |
507 | 245 | job = Job(_status=JobStatus.FAILED) | 252 | job = prepare_job(JobStatus.FAILED) |
508 | 246 | self.assertRaises(InvalidTransition, job.resume) | 253 | self.assertRaises(InvalidTransition, job.resume) |
509 | 247 | 254 | ||
510 | 248 | def test_is_pending(self): | 255 | def test_is_pending(self): |
511 | 249 | """is_pending is True when the job can possibly complete.""" | 256 | """is_pending is True when the job can possibly complete.""" |
512 | 250 | for status in JobStatus.items: | 257 | for status in JobStatus.items: |
514 | 251 | job = Job(_status=status) | 258 | job = prepare_job(status) |
515 | 252 | self.assertEqual( | 259 | self.assertEqual( |
516 | 253 | status in Job.PENDING_STATUSES, job.is_pending) | 260 | status in Job.PENDING_STATUSES, job.is_pending) |
517 | 254 | 261 | ||
518 | @@ -272,7 +279,7 @@ | |||
519 | 272 | def test_ready_jobs_started(self): | 279 | def test_ready_jobs_started(self): |
520 | 273 | """Job.ready_jobs should not jobs that have been started.""" | 280 | """Job.ready_jobs should not jobs that have been started.""" |
521 | 274 | preexisting = self._sampleData() | 281 | preexisting = self._sampleData() |
523 | 275 | job = Job(_status=JobStatus.RUNNING) | 282 | job = Job(status=JobStatus.RUNNING) |
524 | 276 | self.assertEqual( | 283 | self.assertEqual( |
525 | 277 | preexisting, list(Store.of(job).execute(Job.ready_jobs))) | 284 | preexisting, list(Store.of(job).execute(Job.ready_jobs))) |
526 | 278 | 285 | ||
527 | @@ -340,3 +347,46 @@ | |||
528 | 340 | job = Job() | 347 | job = Job() |
529 | 341 | job.acquireLease(-300) | 348 | job.acquireLease(-300) |
530 | 342 | self.assertEqual(0, job.getTimeout()) | 349 | self.assertEqual(0, job.getTimeout()) |
531 | 350 | |||
532 | 351 | def test_attribute_access(self): | ||
533 | 352 | # Job's security proxy allows anyone to its attributes, but no one may | ||
534 | 353 | # set them. | ||
535 | 354 | job = ProxyFactory(Job()) | ||
536 | 355 | |||
537 | 356 | def can_read(name): | ||
538 | 357 | try: | ||
539 | 358 | getattr(job, name) | ||
540 | 359 | except ForbiddenAttribute: | ||
541 | 360 | return False | ||
542 | 361 | else: | ||
543 | 362 | return True | ||
544 | 363 | |||
545 | 364 | def can_write(name): | ||
546 | 365 | try: | ||
547 | 366 | setattr(job, name, None) | ||
548 | 367 | except ForbiddenAttribute: | ||
549 | 368 | return False | ||
550 | 369 | else: | ||
551 | 370 | return True | ||
552 | 371 | |||
553 | 372 | attributes = set( | ||
554 | 373 | name for name in IJob | ||
555 | 374 | if isinstance(IJob[name], Attribute) | ||
556 | 375 | and not isinstance(IJob[name], Method)) | ||
557 | 376 | |||
558 | 377 | expected = dict.fromkeys(attributes, "r") | ||
559 | 378 | |||
560 | 379 | observed = {} | ||
561 | 380 | for attribute in attributes: | ||
562 | 381 | read = can_read(attribute) | ||
563 | 382 | write = can_write(attribute) | ||
564 | 383 | if read and write: | ||
565 | 384 | observed[attribute] = "rw" | ||
566 | 385 | elif read: | ||
567 | 386 | observed[attribute] = "r" | ||
568 | 387 | elif write: | ||
569 | 388 | observed[attribute] = "w" | ||
570 | 389 | else: | ||
571 | 390 | observed[attribute] = "-" | ||
572 | 391 | |||
573 | 392 | self.assertEqual(expected, observed) | ||
574 | 343 | 393 | ||
575 | === modified file 'lib/lp/soyuz/model/distroseriesdifferencejob.py' | |||
576 | --- lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-09-09 19:34:10 +0000 | |||
577 | +++ lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-10-07 13:40:29 +0000 | |||
578 | @@ -255,7 +255,7 @@ | |||
579 | 255 | DistributionJob, | 255 | DistributionJob, |
580 | 256 | DistributionJob.job_type == cls.class_job_type, | 256 | DistributionJob.job_type == cls.class_job_type, |
581 | 257 | Job.id == DistributionJob.job_id, | 257 | Job.id == DistributionJob.job_id, |
583 | 258 | Job._status.is_in(Job.PENDING_STATUSES), | 258 | Job.status.is_in(Job.PENDING_STATUSES), |
584 | 259 | DistributionJob.distroseries == derived_series) | 259 | DistributionJob.distroseries == derived_series) |
585 | 260 | 260 | ||
586 | 261 | parent_series_ids = set( | 261 | parent_series_ids = set( |
587 | 262 | 262 | ||
588 | === modified file 'lib/lp/soyuz/model/packagecopyjob.py' | |||
589 | --- lib/lp/soyuz/model/packagecopyjob.py 2011-09-13 13:41:36 +0000 | |||
590 | +++ lib/lp/soyuz/model/packagecopyjob.py 2011-10-07 13:40:29 +0000 | |||
591 | @@ -325,7 +325,7 @@ | |||
592 | 325 | PackageCopyJob.job_type == cls.class_job_type, | 325 | PackageCopyJob.job_type == cls.class_job_type, |
593 | 326 | PackageCopyJob.target_archive == target_archive, | 326 | PackageCopyJob.target_archive == target_archive, |
594 | 327 | Job.id == PackageCopyJob.job_id, | 327 | Job.id == PackageCopyJob.job_id, |
596 | 328 | Job._status == JobStatus.WAITING) | 328 | Job.status == JobStatus.WAITING) |
597 | 329 | jobs = jobs.order_by(PackageCopyJob.id) | 329 | jobs = jobs.order_by(PackageCopyJob.id) |
598 | 330 | return DecoratedResultSet(jobs, cls) | 330 | return DecoratedResultSet(jobs, cls) |
599 | 331 | 331 | ||
600 | @@ -337,7 +337,7 @@ | |||
601 | 337 | Job.id == PackageCopyJob.job_id, | 337 | Job.id == PackageCopyJob.job_id, |
602 | 338 | PackageCopyJob.job_type == cls.class_job_type, | 338 | PackageCopyJob.job_type == cls.class_job_type, |
603 | 339 | PackageCopyJob.target_distroseries == target_series, | 339 | PackageCopyJob.target_distroseries == target_series, |
605 | 340 | Job._status.is_in(Job.PENDING_STATUSES)) | 340 | Job.status.is_in(Job.PENDING_STATUSES)) |
606 | 341 | raw_jobs = raw_jobs.order_by(PackageCopyJob.id) | 341 | raw_jobs = raw_jobs.order_by(PackageCopyJob.id) |
607 | 342 | return DecoratedResultSet(raw_jobs, cls) | 342 | return DecoratedResultSet(raw_jobs, cls) |
608 | 343 | 343 | ||
609 | 344 | 344 | ||
610 | === modified file 'lib/lp/soyuz/tests/test_distroseriesdifferencejob.py' | |||
611 | --- lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-08-31 14:12:38 +0000 | |||
612 | +++ lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-10-07 13:40:29 +0000 | |||
613 | @@ -27,6 +27,7 @@ | |||
614 | 27 | from lp.services.database import bulk | 27 | from lp.services.database import bulk |
615 | 28 | from lp.services.features.testing import FeatureFixture | 28 | from lp.services.features.testing import FeatureFixture |
616 | 29 | from lp.services.job.interfaces.job import JobStatus | 29 | from lp.services.job.interfaces.job import JobStatus |
617 | 30 | from lp.services.job.testing import prepare_job | ||
618 | 30 | from lp.soyuz.enums import ( | 31 | from lp.soyuz.enums import ( |
619 | 31 | ArchivePurpose, | 32 | ArchivePurpose, |
620 | 32 | PackagePublishingStatus, | 33 | PackagePublishingStatus, |
621 | @@ -506,7 +507,7 @@ | |||
622 | 506 | dsd = self.factory.makeDistroSeriesDifference() | 507 | dsd = self.factory.makeDistroSeriesDifference() |
623 | 507 | job = create_job( | 508 | job = create_job( |
624 | 508 | dsd.derived_series, dsd.source_package_name, dsd.parent_series) | 509 | dsd.derived_series, dsd.source_package_name, dsd.parent_series) |
626 | 509 | removeSecurityProxy(job).job._status = JobStatus.COMPLETED | 510 | prepare_job(JobStatus.COMPLETED, removeSecurityProxy(job)) |
627 | 510 | self.assertEqual( | 511 | self.assertEqual( |
628 | 511 | {}, | 512 | {}, |
629 | 512 | self.getJobSource().getPendingJobsForDifferences( | 513 | self.getJobSource().getPendingJobsForDifferences( |
630 | 513 | 514 | ||
631 | === modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py' | |||
632 | --- lib/lp/soyuz/tests/test_packagecopyjob.py 2011-08-30 14:24:13 +0000 | |||
633 | +++ lib/lp/soyuz/tests/test_packagecopyjob.py 2011-10-07 13:40:29 +0000 | |||
634 | @@ -33,6 +33,8 @@ | |||
635 | 33 | JobStatus, | 33 | JobStatus, |
636 | 34 | SuspendJobException, | 34 | SuspendJobException, |
637 | 35 | ) | 35 | ) |
638 | 36 | from lp.services.job.model.job import Job | ||
639 | 37 | from lp.services.job.testing import prepare_job | ||
640 | 36 | from lp.soyuz.adapters.overrides import SourceOverride | 38 | from lp.soyuz.adapters.overrides import SourceOverride |
641 | 37 | from lp.soyuz.enums import ( | 39 | from lp.soyuz.enums import ( |
642 | 38 | ArchivePurpose, | 40 | ArchivePurpose, |
643 | @@ -234,7 +236,7 @@ | |||
644 | 234 | def test_getActiveJobs_only_returns_waiting_jobs(self): | 236 | def test_getActiveJobs_only_returns_waiting_jobs(self): |
645 | 235 | # getActiveJobs ignores jobs that aren't in the WAITING state. | 237 | # getActiveJobs ignores jobs that aren't in the WAITING state. |
646 | 236 | job = self.makeJob() | 238 | job = self.makeJob() |
648 | 237 | removeSecurityProxy(job).job._status = JobStatus.RUNNING | 239 | removeSecurityProxy(job).job.status = JobStatus.RUNNING |
649 | 238 | source = getUtility(IPlainPackageCopyJobSource) | 240 | source = getUtility(IPlainPackageCopyJobSource) |
650 | 239 | self.assertContentEqual([], source.getActiveJobs(job.target_archive)) | 241 | self.assertContentEqual([], source.getActiveJobs(job.target_archive)) |
651 | 240 | 242 | ||
652 | @@ -480,21 +482,16 @@ | |||
653 | 480 | def test_getPendingJobsPerPackage_only_returns_pending_jobs(self): | 482 | def test_getPendingJobsPerPackage_only_returns_pending_jobs(self): |
654 | 481 | # getPendingJobsPerPackage ignores jobs that have already been | 483 | # getPendingJobsPerPackage ignores jobs that have already been |
655 | 482 | # run. | 484 | # run. |
658 | 483 | dsd = self.factory.makeDistroSeriesDifference() | 485 | series = self.factory.makeDistroSeries() |
657 | 484 | job = self.makeJob(dsd) | ||
659 | 485 | job_source = getUtility(IPlainPackageCopyJobSource) | 486 | job_source = getUtility(IPlainPackageCopyJobSource) |
660 | 486 | found_by_state = {} | ||
661 | 487 | for status in JobStatus.items: | 487 | for status in JobStatus.items: |
672 | 488 | removeSecurityProxy(job).job._status = status | 488 | dsd = self.factory.makeDistroSeriesDifference( |
673 | 489 | result = job_source.getPendingJobsPerPackage(dsd.derived_series) | 489 | derived_series=series) |
674 | 490 | if len(result) > 0: | 490 | job = self.makeJob(dsd) |
675 | 491 | found_by_state[status] = result[dsd.source_package_name.name] | 491 | prepare_job(status, removeSecurityProxy(job)) |
676 | 492 | expected = { | 492 | jobs = job_source.getPendingJobsPerPackage(series) |
677 | 493 | JobStatus.WAITING: job, | 493 | statuses = set(job.status for job in jobs.itervalues()) |
678 | 494 | JobStatus.RUNNING: job, | 494 | self.assertEqual(Job.PENDING_STATUSES, statuses) |
669 | 495 | JobStatus.SUSPENDED: job, | ||
670 | 496 | } | ||
671 | 497 | self.assertEqual(expected, found_by_state) | ||
679 | 498 | 495 | ||
680 | 499 | def test_getPendingJobsPerPackage_distinguishes_jobs(self): | 496 | def test_getPendingJobsPerPackage_distinguishes_jobs(self): |
681 | 500 | # getPendingJobsPerPackage associates the right job with the | 497 | # getPendingJobsPerPackage associates the right job with the |
682 | 501 | 498 | ||
683 | === modified file 'lib/lp/translations/model/translationsharingjob.py' | |||
684 | --- lib/lp/translations/model/translationsharingjob.py 2011-08-01 08:12:35 +0000 | |||
685 | +++ lib/lp/translations/model/translationsharingjob.py 2011-10-07 13:40:29 +0000 | |||
686 | @@ -232,7 +232,7 @@ | |||
687 | 232 | (TranslationSharingJob.productseries_id == | 232 | (TranslationSharingJob.productseries_id == |
688 | 233 | packaging.productseries.id), | 233 | packaging.productseries.id), |
689 | 234 | TranslationSharingJob.job_type == cls.class_job_type, | 234 | TranslationSharingJob.job_type == cls.class_job_type, |
691 | 235 | Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) | 235 | Job.status.is_in([JobStatus.WAITING, JobStatus.RUNNING])) |
692 | 236 | result.order_by(TranslationSharingJob.id) | 236 | result.order_by(TranslationSharingJob.id) |
693 | 237 | job = result.first() | 237 | job = result.first() |
694 | 238 | if job is None: | 238 | if job is None: |
This is a great change. As you say, everything is much clearer now.