Merge lp://staging/~al-maisan/launchpad/xx-select-job-506617 into lp://staging/launchpad/db-devel
- xx-select-job-506617
- Merge into db-devel
Proposed by
Muharem Hrnjadovic
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp://staging/~al-maisan/launchpad/xx-select-job-506617 |
Merge into: | lp://staging/launchpad/db-devel |
Diff against target: |
644 lines (+247/-178) 9 files modified
lib/lp/buildmaster/interfaces/buildfarmjob.py (+39/-0) lib/lp/buildmaster/model/builder.py (+55/-142) lib/lp/buildmaster/model/buildfarmjob.py (+13/-2) lib/lp/soyuz/interfaces/buildpackagejob.py (+2/-2) lib/lp/soyuz/interfaces/buildqueue.py (+0/-4) lib/lp/soyuz/model/buildpackagejob.py (+110/-4) lib/lp/soyuz/model/buildqueue.py (+19/-18) lib/lp/soyuz/tests/test_buildpackagejob.py (+2/-1) lib/lp/soyuz/tests/test_buildqueue.py (+7/-5) |
To merge this branch: | bzr merge lp://staging/~al-maisan/launchpad/xx-select-job-506617 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+17327@code.staging.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote : | # |
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote : | # |
We went through this together, resulting in a few very small changes (mainly naming).
I'm approving this branch, but one issue remains open: the new interface, IBuildFarmCandi
Jeroen
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' |
2 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-01-08 12:26:22 +0000 |
3 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-01-14 02:33:14 +0000 |
4 | @@ -9,6 +9,7 @@ |
5 | |
6 | __all__ = [ |
7 | 'IBuildFarmJob', |
8 | + 'IBuildFarmCandidateJobSelection', |
9 | 'IBuildFarmJobDispatchEstimation', |
10 | 'BuildFarmJobType', |
11 | ] |
12 | @@ -134,3 +135,41 @@ |
13 | the pending jobs of the appropriate type. |
14 | """ |
15 | |
16 | + |
17 | +class IBuildFarmCandidateJobSelection(Interface): |
18 | + """Operations for refining candidate job selection (optional). |
19 | + |
20 | + Job type classes that do *not* need to refine candidate job selection may |
21 | + be derived from `BuildFarmJob` which provides a base implementation of |
22 | + this interface. |
23 | + """ |
24 | + |
25 | + def addCandidateSelectionCriteria(processor, virtualized): |
26 | + """Provide extra clauses that will refine the candidate job selection. |
27 | + |
28 | + Return a 2-tuple with extra tables and clauses to be used to |
29 | + narrow down the list of candidate jobs. |
30 | + |
31 | + Example: |
32 | + (('Build', 'BuildPackageJob'), |
33 | + "BuildPackageJob.build = Build.id AND ..") |
34 | + |
35 | + :param processor: the type of processor that the candidate jobs are |
36 | + expected to run on. |
37 | + :param virtualized: whether the candidate jobs are expected to run on |
38 | + the `processor` natively or inside a virtual machine. |
39 | + :return: an (extra_tables, extra_query) tuple where `extra_tables` is |
40 | + a collection of tables that need to appear in the FROM clause of |
41 | + the combined query for `extra_query` to work. |
42 | + """ |
43 | + |
44 | + def postprocessCandidate(job, logger): |
45 | + """True if the candidate job is fine and should be dispatched |
46 | + to a builder, False otherwise. |
47 | + |
48 | + :param job: The `BuildQueue` instance to be scrutinized. |
49 | + :param logger: The logger to use. |
50 | + |
51 | + :return: True if the candidate job should be dispatched |
52 | + to a builder, False otherwise. |
53 | + """ |
54 | |
55 | === modified file 'lib/lp/buildmaster/model/builder.py' |
56 | --- lib/lp/buildmaster/model/builder.py 2010-01-13 20:19:43 +0000 |
57 | +++ lib/lp/buildmaster/model/builder.py 2010-01-14 02:33:14 +0000 |
58 | @@ -41,24 +41,23 @@ |
59 | # These dependencies on soyuz will be removed when getBuildRecords() |
60 | # is moved, as well as when the generalisation of findBuildCandidate() |
61 | # is completed. |
62 | -from lp.soyuz.model.buildqueue import BuildQueue |
63 | +from lp.soyuz.model.buildqueue import BuildQueue, specific_job_classes |
64 | from lp.registry.interfaces.person import validate_public_person |
65 | -from lp.registry.interfaces.pocket import PackagePublishingPocket |
66 | from canonical.launchpad.helpers import filenameToContentType |
67 | +from lp.services.job.interfaces.job import JobStatus |
68 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
69 | from lp.soyuz.interfaces.distroarchseries import IDistroArchSeriesSet |
70 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
71 | from canonical.launchpad.webapp.interfaces import NotFoundError |
72 | -from lp.soyuz.interfaces.archive import ArchivePurpose |
73 | from lp.soyuz.interfaces.build import BuildStatus, IBuildSet |
74 | from lp.buildmaster.interfaces.builder import ( |
75 | BuildDaemonError, BuildSlaveFailure, CannotBuild, CannotFetchFile, |
76 | CannotResumeHost, IBuilder, IBuilderSet, ProtocolVersionMismatch) |
77 | from lp.soyuz.interfaces.buildqueue import IBuildQueueSet |
78 | -from lp.soyuz.interfaces.publishing import ( |
79 | - PackagePublishingStatus) |
80 | from lp.soyuz.model.buildpackagejob import BuildPackageJob |
81 | from canonical.launchpad.webapp import urlappend |
82 | +from canonical.launchpad.webapp.interfaces import ( |
83 | + IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
84 | from canonical.lazr.utils import safe_hasattr |
85 | from canonical.librarian.utils import copy_and_close |
86 | |
87 | @@ -409,99 +408,6 @@ |
88 | return False |
89 | return True |
90 | |
91 | - # XXX cprov 20071116: It should become part of the public |
92 | - # _findBuildCandidate once we start to detect superseded builds |
93 | - # at build creation time. |
94 | - def _findBinaryBuildCandidate(self): |
95 | - """Return the highest priority build candidate for this builder. |
96 | - |
97 | - Returns a pending IBuildQueue record queued for this builder |
98 | - processorfamily with the highest lastscore or None if there |
99 | - is no one available. |
100 | - """ |
101 | - # If a private build does not yet have its source published then |
102 | - # we temporarily skip it because we want to wait for the publisher |
103 | - # to place the source in the archive, which is where builders |
104 | - # download the source from in the case of private builds (because |
105 | - # it's a secure location). |
106 | - private_statuses = ( |
107 | - PackagePublishingStatus.PUBLISHED, |
108 | - PackagePublishingStatus.SUPERSEDED, |
109 | - PackagePublishingStatus.DELETED, |
110 | - ) |
111 | - clauses = [""" |
112 | - ((archive.private IS TRUE AND |
113 | - EXISTS ( |
114 | - SELECT SourcePackagePublishingHistory.id |
115 | - FROM SourcePackagePublishingHistory |
116 | - WHERE |
117 | - SourcePackagePublishingHistory.distroseries = |
118 | - DistroArchSeries.distroseries AND |
119 | - SourcePackagePublishingHistory.sourcepackagerelease = |
120 | - Build.sourcepackagerelease AND |
121 | - SourcePackagePublishingHistory.archive = Archive.id AND |
122 | - SourcePackagePublishingHistory.status IN %s)) |
123 | - OR |
124 | - archive.private IS FALSE) AND |
125 | - buildqueue.job = buildpackagejob.job AND |
126 | - buildpackagejob.build = build.id AND |
127 | - build.distroarchseries = distroarchseries.id AND |
128 | - build.archive = archive.id AND |
129 | - archive.enabled = TRUE AND |
130 | - build.buildstate = %s AND |
131 | - distroarchseries.processorfamily = %s AND |
132 | - buildqueue.builder IS NULL |
133 | - """ % sqlvalues( |
134 | - private_statuses, BuildStatus.NEEDSBUILD, self.processor.family)] |
135 | - |
136 | - clauseTables = [ |
137 | - 'Build', 'BuildPackageJob', 'DistroArchSeries', 'Archive'] |
138 | - |
139 | - clauses.append(""" |
140 | - archive.require_virtualized = %s |
141 | - """ % sqlvalues(self.virtualized)) |
142 | - |
143 | - # Ensure that if BUILDING builds exist for the same |
144 | - # public ppa archive and architecture and another would not |
145 | - # leave at least 20% of them free, then we don't consider |
146 | - # another as a candidate. |
147 | - # |
148 | - # This clause selects the count of currently building builds on |
149 | - # the arch in question, then adds one to that total before |
150 | - # deriving a percentage of the total available builders on that |
151 | - # arch. It then makes sure that percentage is under 80. |
152 | - # |
153 | - # The extra clause is only used if the number of available |
154 | - # builders is greater than one, or nothing would get dispatched |
155 | - # at all. |
156 | - num_arch_builders = Builder.selectBy( |
157 | - processor=self.processor, manual=False, builderok=True).count() |
158 | - if num_arch_builders > 1: |
159 | - clauses.append(""" |
160 | - EXISTS (SELECT true |
161 | - WHERE (( |
162 | - SELECT COUNT(build2.id) |
163 | - FROM Build build2, DistroArchSeries distroarchseries2 |
164 | - WHERE |
165 | - build2.archive = build.archive AND |
166 | - archive.purpose = %s AND |
167 | - archive.private IS FALSE AND |
168 | - build2.distroarchseries = distroarchseries2.id AND |
169 | - distroarchseries2.processorfamily = %s AND |
170 | - build2.buildstate = %s) + 1::numeric) |
171 | - *100 / %s |
172 | - < 80) |
173 | - """ % sqlvalues( |
174 | - ArchivePurpose.PPA, self.processor.family, |
175 | - BuildStatus.BUILDING, num_arch_builders)) |
176 | - |
177 | - query = " AND ".join(clauses) |
178 | - candidate = BuildQueue.selectFirst( |
179 | - query, clauseTables=clauseTables, |
180 | - orderBy=['-buildqueue.lastscore', 'build.id']) |
181 | - |
182 | - return candidate |
183 | - |
184 | def _getSlaveScannerLogger(self): |
185 | """Return the logger instance from buildd-slave-scanner.py.""" |
186 | # XXX cprov 20071120: Ideally the Launchpad logging system |
187 | @@ -515,52 +421,59 @@ |
188 | """Find a candidate job for dispatch to an idle buildd slave. |
189 | |
190 | The pending BuildQueue item with the highest score for this builder |
191 | - ProcessorFamily or None if no candidate is available. |
192 | - |
193 | - For public PPA builds, subsequent builds for a given ppa and |
194 | - architecture will not be returned until the current build for |
195 | - the ppa and architecture is finished. |
196 | - |
197 | - :return: A binary build candidate job. |
198 | + or None if no candidate is available. |
199 | + |
200 | + :return: A candidate job. |
201 | """ |
202 | - |
203 | logger = self._getSlaveScannerLogger() |
204 | - candidate = self._findBinaryBuildCandidate() |
205 | - |
206 | - # Mark build records targeted to old source versions as SUPERSEDED |
207 | - # and build records target to SECURITY pocket as FAILEDTOBUILD. |
208 | - # Builds in those situation should not be built because they will |
209 | - # be wasting build-time, the former case already has a newer source |
210 | - # and the latter could not be built in DAK. |
211 | - build_set = getUtility(IBuildSet) |
212 | - while candidate is not None: |
213 | - build = build_set.getByQueueEntry(candidate) |
214 | - if build.pocket == PackagePublishingPocket.SECURITY: |
215 | - # We never build anything in the security pocket. |
216 | - logger.debug( |
217 | - "Build %s FAILEDTOBUILD, queue item %s REMOVED" |
218 | - % (build.id, candidate.id)) |
219 | - build.buildstate = BuildStatus.FAILEDTOBUILD |
220 | - candidate.destroySelf() |
221 | - candidate = self._findBinaryBuildCandidate() |
222 | - continue |
223 | - |
224 | - publication = build.current_source_publication |
225 | - |
226 | - if publication is None: |
227 | - # The build should be superseded if it no longer has a |
228 | - # current publishing record. |
229 | - logger.debug( |
230 | - "Build %s SUPERSEDED, queue item %s REMOVED" |
231 | - % (build.id, candidate.id)) |
232 | - build.buildstate = BuildStatus.SUPERSEDED |
233 | - candidate.destroySelf() |
234 | - candidate = self._findBinaryBuildCandidate() |
235 | - continue |
236 | - |
237 | - return candidate |
238 | - |
239 | - # No candidate was found. |
240 | + candidate = None |
241 | + |
242 | + query_tables = set(('buildqueue', 'job')) |
243 | + general_query = """ |
244 | + SELECT buildqueue.id FROM %%s |
245 | + WHERE |
246 | + buildqueue.job = job.id |
247 | + AND job.status = %s |
248 | + AND ( |
249 | + (buildqueue.processor = %s |
250 | + AND buildqueue.virtualized = %s) |
251 | + OR |
252 | + (buildqueue.processor IS NULL |
253 | + AND buildqueue.virtualized IS NULL)) |
254 | + AND buildqueue.builder IS NULL |
255 | + """ % sqlvalues(JobStatus.WAITING, self.processor, self.virtualized) |
256 | + order_clause = " ORDER BY buildqueue.lastscore DESC, buildqueue.id" |
257 | + |
258 | + extra_tables = set() |
259 | + extra_queries = [] |
260 | + job_classes = specific_job_classes() |
261 | + for job_class in job_classes.values(): |
262 | + tables, query = job_class.addCandidateSelectionCriteria( |
263 | + self.processor, self.virtualized) |
264 | + if query == '': |
265 | + # This job class does not need to refine candidate jobs |
266 | + # further. |
267 | + continue |
268 | + |
269 | + # Table names are case-insensitive in SQL. All table names are in |
270 | + # lower case in order to avoid duplicates in the FROM clause. |
271 | + query_tables = query_tables.union( |
272 | + set(table.lower() for table in tables)) |
273 | + extra_queries.append(query) |
274 | + general_query = general_query % ', '.join(query_tables) |
275 | + query = ' AND '.join([general_query] + extra_queries) + order_clause |
276 | + |
277 | + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
278 | + candidate_jobs = store.execute(query).get_all() |
279 | + |
280 | + for (candidate_id,) in candidate_jobs: |
281 | + candidate = getUtility(IBuildQueueSet).get(candidate_id) |
282 | + job_class = job_classes[candidate.job_type] |
283 | + candidate_approved = job_class.postprocessCandidate( |
284 | + candidate, logger) |
285 | + if candidate_approved: |
286 | + return candidate |
287 | + |
288 | return None |
289 | |
290 | def _dispatchBuildCandidate(self, candidate): |
291 | |
292 | === modified file 'lib/lp/buildmaster/model/buildfarmjob.py' |
293 | --- lib/lp/buildmaster/model/buildfarmjob.py 2009-12-24 13:18:16 +0000 |
294 | +++ lib/lp/buildmaster/model/buildfarmjob.py 2010-01-14 02:33:14 +0000 |
295 | @@ -5,14 +5,16 @@ |
296 | __all__ = ['BuildFarmJob'] |
297 | |
298 | |
299 | -from zope.interface import implements |
300 | +from zope.interface import classProvides, implements |
301 | |
302 | -from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
303 | +from lp.buildmaster.interfaces.buildfarmjob import ( |
304 | + IBuildFarmJob, IBuildFarmCandidateJobSelection) |
305 | |
306 | |
307 | class BuildFarmJob: |
308 | """Mix-in class for `IBuildFarmJob` implementations.""" |
309 | implements(IBuildFarmJob) |
310 | + classProvides(IBuildFarmCandidateJobSelection) |
311 | |
312 | def score(self): |
313 | """See `IBuildFarmJob`.""" |
314 | @@ -48,3 +50,12 @@ |
315 | """See `IBuildFarmJob`.""" |
316 | return None |
317 | |
318 | + @staticmethod |
319 | + def addCandidateSelectionCriteria(processor, virtualized): |
320 | + """See `IBuildFarmCandidateJobSelection`.""" |
321 | + return ([], '') |
322 | + |
323 | + @staticmethod |
324 | + def postprocessCandidate(job, logger): |
325 | + """See `IBuildFarmCandidateJobSelection`.""" |
326 | + return True |
327 | |
328 | === modified file 'lib/lp/soyuz/interfaces/buildpackagejob.py' |
329 | --- lib/lp/soyuz/interfaces/buildpackagejob.py 2009-11-13 20:39:36 +0000 |
330 | +++ lib/lp/soyuz/interfaces/buildpackagejob.py 2010-01-14 02:33:14 +0000 |
331 | @@ -11,16 +11,16 @@ |
332 | 'IBuildPackageJob', |
333 | ] |
334 | |
335 | +from zope.interface import Interface |
336 | from zope.schema import Int |
337 | |
338 | from canonical.launchpad import _ |
339 | from lazr.restful.fields import Reference |
340 | -from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
341 | from lp.services.job.interfaces.job import IJob |
342 | from lp.soyuz.interfaces.build import IBuild |
343 | |
344 | |
345 | -class IBuildPackageJob(IBuildFarmJob): |
346 | +class IBuildPackageJob(Interface): |
347 | """A read-only interface for build package jobs.""" |
348 | id = Int(title=_('ID'), required=True, readonly=True) |
349 | |
350 | |
351 | === modified file 'lib/lp/soyuz/interfaces/buildqueue.py' |
352 | --- lib/lp/soyuz/interfaces/buildqueue.py 2010-01-13 20:36:19 +0000 |
353 | +++ lib/lp/soyuz/interfaces/buildqueue.py 2010-01-14 02:33:14 +0000 |
354 | @@ -68,10 +68,6 @@ |
355 | title=_('The builder behavior required to run this job.'), |
356 | required=False, readonly=True) |
357 | |
358 | - specific_job_classes = Field( |
359 | - title=_('Job classes that may run on the build farm.'), |
360 | - required=True, readonly=True) |
361 | - |
362 | estimated_duration = Timedelta( |
363 | title=_("Estimated Job Duration"), required=True, |
364 | description=_("Estimated job duration interval.")) |
365 | |
366 | === modified file 'lib/lp/soyuz/model/buildpackagejob.py' |
367 | --- lib/lp/soyuz/model/buildpackagejob.py 2010-01-12 10:44:24 +0000 |
368 | +++ lib/lp/soyuz/model/buildpackagejob.py 2010-01-14 02:33:14 +0000 |
369 | @@ -11,21 +11,24 @@ |
370 | from storm.locals import Int, Reference, Storm |
371 | |
372 | from zope.interface import classProvides, implements |
373 | +from zope.component import getUtility |
374 | |
375 | from canonical.database.constants import UTC_NOW |
376 | from canonical.database.sqlbase import sqlvalues |
377 | |
378 | from lp.buildmaster.interfaces.buildfarmjob import ( |
379 | BuildFarmJobType, IBuildFarmJobDispatchEstimation) |
380 | +from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
381 | from lp.registry.interfaces.sourcepackage import SourcePackageUrgency |
382 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
383 | from lp.services.job.interfaces.job import JobStatus |
384 | from lp.soyuz.interfaces.archive import ArchivePurpose |
385 | -from lp.soyuz.interfaces.build import BuildStatus |
386 | +from lp.soyuz.interfaces.build import BuildStatus, IBuildSet |
387 | from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob |
388 | - |
389 | - |
390 | -class BuildPackageJob(Storm): |
391 | +from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
392 | + |
393 | + |
394 | +class BuildPackageJob(Storm, BuildFarmJob): |
395 | """See `IBuildPackageJob`.""" |
396 | implements(IBuildPackageJob) |
397 | classProvides(IBuildFarmJobDispatchEstimation) |
398 | @@ -209,3 +212,106 @@ |
399 | def virtualized(self): |
400 | """See `IBuildFarmJob`.""" |
401 | return self.build.is_virtualized |
402 | + |
403 | + @staticmethod |
404 | + def addCandidateSelectionCriteria(processor, virtualized): |
405 | + """See `IBuildFarmCandidateJobSelection`.""" |
406 | + # Avoiding circular import. |
407 | + from lp.buildmaster.model.builder import Builder |
408 | + |
409 | + private_statuses = ( |
410 | + PackagePublishingStatus.PUBLISHED, |
411 | + PackagePublishingStatus.SUPERSEDED, |
412 | + PackagePublishingStatus.DELETED, |
413 | + ) |
414 | + extra_tables = [ |
415 | + 'Archive', 'Build', 'BuildPackageJob', 'DistroArchSeries'] |
416 | + extra_clauses = """ |
417 | + BuildPackageJob.job = Job.id AND |
418 | + BuildPackageJob.build = Build.id AND |
419 | + Build.distroarchseries = DistroArchSeries.id AND |
420 | + Build.archive = Archive.id AND |
421 | + ((Archive.private IS TRUE AND |
422 | + EXISTS ( |
423 | + SELECT SourcePackagePublishingHistory.id |
424 | + FROM SourcePackagePublishingHistory |
425 | + WHERE |
426 | + SourcePackagePublishingHistory.distroseries = |
427 | + DistroArchSeries.distroseries AND |
428 | + SourcePackagePublishingHistory.sourcepackagerelease = |
429 | + Build.sourcepackagerelease AND |
430 | + SourcePackagePublishingHistory.archive = Archive.id AND |
431 | + SourcePackagePublishingHistory.status IN %s)) |
432 | + OR |
433 | + archive.private IS FALSE) AND |
434 | + build.buildstate = %s |
435 | + """ % sqlvalues(private_statuses, BuildStatus.NEEDSBUILD) |
436 | + |
437 | + # Ensure that if BUILDING builds exist for the same |
438 | + # public ppa archive and architecture and another would not |
439 | + # leave at least 20% of them free, then we don't consider |
440 | + # another as a candidate. |
441 | + # |
442 | + # This clause selects the count of currently building builds on |
443 | + # the arch in question, then adds one to that total before |
444 | + # deriving a percentage of the total available builders on that |
445 | + # arch. It then makes sure that percentage is under 80. |
446 | + # |
447 | + # The extra clause is only used if the number of available |
448 | + # builders is greater than one, or nothing would get dispatched |
449 | + # at all. |
450 | + num_arch_builders = Builder.selectBy( |
451 | + processor=processor, manual=False, builderok=True).count() |
452 | + if num_arch_builders > 1: |
453 | + extra_clauses += """ |
454 | + AND EXISTS (SELECT true |
455 | + WHERE (( |
456 | + SELECT COUNT(build2.id) |
457 | + FROM Build build2, DistroArchSeries distroarchseries2 |
458 | + WHERE |
459 | + build2.archive = build.archive AND |
460 | + archive.purpose = %s AND |
461 | + archive.private IS FALSE AND |
462 | + build2.distroarchseries = distroarchseries2.id AND |
463 | + distroarchseries2.processorfamily = %s AND |
464 | + build2.buildstate = %s) + 1::numeric) |
465 | + *100 / %s |
466 | + < 80) |
467 | + """ % sqlvalues( |
468 | + ArchivePurpose.PPA, processor.family, |
469 | + BuildStatus.BUILDING, num_arch_builders) |
470 | + |
471 | + return(extra_tables, extra_clauses) |
472 | + |
473 | + @staticmethod |
474 | + def postprocessCandidate(job, logger): |
475 | + """See `IBuildFarmCandidateJobSelection`.""" |
476 | + # Mark build records targeted to old source versions as SUPERSEDED |
477 | + # and build records target to SECURITY pocket as FAILEDTOBUILD. |
478 | + # Builds in those situation should not be built because they will |
479 | + # be wasting build-time, the former case already has a newer source |
480 | + # and the latter could not be built in DAK. |
481 | + build_set = getUtility(IBuildSet) |
482 | + |
483 | + build = build_set.getByQueueEntry(job) |
484 | + if build.pocket == PackagePublishingPocket.SECURITY: |
485 | + # We never build anything in the security pocket. |
486 | + logger.debug( |
487 | + "Build %s FAILEDTOBUILD, queue item %s REMOVED" |
488 | + % (build.id, job.id)) |
489 | + build.buildstate = BuildStatus.FAILEDTOBUILD |
490 | + job.destroySelf() |
491 | + return False |
492 | + |
493 | + publication = build.current_source_publication |
494 | + if publication is None: |
495 | + # The build should be superseded if it no longer has a |
496 | + # current publishing record. |
497 | + logger.debug( |
498 | + "Build %s SUPERSEDED, queue item %s REMOVED" |
499 | + % (build.id, job.id)) |
500 | + build.buildstate = BuildStatus.SUPERSEDED |
501 | + job.destroySelf() |
502 | + return False |
503 | + |
504 | + return True |
505 | |
506 | === modified file 'lib/lp/soyuz/model/buildqueue.py' |
507 | --- lib/lp/soyuz/model/buildqueue.py 2010-01-13 11:11:40 +0000 |
508 | +++ lib/lp/soyuz/model/buildqueue.py 2010-01-14 02:33:14 +0000 |
509 | @@ -7,7 +7,8 @@ |
510 | |
511 | __all__ = [ |
512 | 'BuildQueue', |
513 | - 'BuildQueueSet' |
514 | + 'BuildQueueSet', |
515 | + 'specific_job_classes', |
516 | ] |
517 | |
518 | import logging |
519 | @@ -38,6 +39,22 @@ |
520 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
521 | |
522 | |
523 | +def specific_job_classes(): |
524 | + """Job classes that may run on the build farm.""" |
525 | + job_classes = dict() |
526 | + # Get all components that implement the `IBuildFarmJob` interface. |
527 | + components = getSiteManager() |
528 | + implementations = sorted(components.getUtilitiesFor(IBuildFarmJob)) |
529 | + # The above yields a collection of 2-tuples where the first element |
530 | + # is the name of the `BuildFarmJobType` enum and the second element |
531 | + # is the implementing class respectively. |
532 | + for job_enum_name, job_class in implementations: |
533 | + job_enum = getattr(BuildFarmJobType, job_enum_name) |
534 | + job_classes[job_enum] = job_class |
535 | + |
536 | + return job_classes |
537 | + |
538 | + |
539 | class BuildQueue(SQLBase): |
540 | implements(IBuildQueue) |
541 | _table = "BuildQueue" |
542 | @@ -62,25 +79,9 @@ |
543 | return IBuildFarmJobBehavior(self.specific_job) |
544 | |
545 | @property |
546 | - def specific_job_classes(self): |
547 | - """See `IBuildQueue`.""" |
548 | - job_classes = dict() |
549 | - # Get all components that implement the `IBuildFarmJob` interface. |
550 | - components = getSiteManager() |
551 | - implementations = sorted(components.getUtilitiesFor(IBuildFarmJob)) |
552 | - # The above yields a collection of 2-tuples where the first element |
553 | - # is the name of the `BuildFarmJobType` enum and the second element |
554 | - # is the implementing class respectively. |
555 | - for job_enum_name, job_class in implementations: |
556 | - job_enum = getattr(BuildFarmJobType, job_enum_name) |
557 | - job_classes[job_enum] = job_class |
558 | - |
559 | - return job_classes |
560 | - |
561 | - @property |
562 | def specific_job(self): |
563 | """See `IBuildQueue`.""" |
564 | - specific_class = self.specific_job_classes[self.job_type] |
565 | + specific_class = specific_job_classes()[self.job_type] |
566 | store = Store.of(self) |
567 | result_set = store.find( |
568 | specific_class, specific_class.job == self.job) |
569 | |
570 | === modified file 'lib/lp/soyuz/tests/test_buildpackagejob.py' |
571 | --- lib/lp/soyuz/tests/test_buildpackagejob.py 2010-01-11 23:43:59 +0000 |
572 | +++ lib/lp/soyuz/tests/test_buildpackagejob.py 2010-01-14 02:33:14 +0000 |
573 | @@ -12,11 +12,11 @@ |
574 | from canonical.launchpad.webapp.testing import verifyObject |
575 | from canonical.testing import LaunchpadZopelessLayer |
576 | |
577 | +from lp.buildmaster.interfaces.builder import IBuilderSet |
578 | from lp.buildmaster.interfaces.buildfarmjob import ( |
579 | IBuildFarmJobDispatchEstimation) |
580 | from lp.soyuz.interfaces.archive import ArchivePurpose |
581 | from lp.soyuz.interfaces.build import BuildStatus |
582 | -from lp.buildmaster.interfaces.builder import IBuilderSet |
583 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
584 | from lp.soyuz.model.build import Build |
585 | from lp.soyuz.model.buildpackagejob import BuildPackageJob |
586 | @@ -24,6 +24,7 @@ |
587 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
588 | from lp.testing import TestCaseWithFactory |
589 | |
590 | + |
591 | def find_job(test, name, processor='386'): |
592 | """Find build and queue instance for the given source and processor.""" |
593 | for build in test.builds: |
594 | |
595 | === modified file 'lib/lp/soyuz/tests/test_buildqueue.py' |
596 | --- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-13 11:11:40 +0000 |
597 | +++ lib/lp/soyuz/tests/test_buildqueue.py 2010-01-14 02:33:14 +0000 |
598 | @@ -13,10 +13,12 @@ |
599 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
600 | from canonical.testing import LaunchpadZopelessLayer |
601 | |
602 | +from lp.buildmaster.interfaces.builder import IBuilderSet |
603 | from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType |
604 | +from lp.buildmaster.model.builder import specific_job_classes |
605 | +from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
606 | from lp.soyuz.interfaces.archive import ArchivePurpose |
607 | from lp.soyuz.interfaces.build import BuildStatus |
608 | -from lp.buildmaster.interfaces.builder import IBuilderSet |
609 | from lp.soyuz.model.processor import ProcessorFamilySet |
610 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
611 | from lp.soyuz.model.build import Build |
612 | @@ -705,7 +707,7 @@ |
613 | |
614 | # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`. |
615 | self.assertEqual( |
616 | - bq.specific_job_classes[BuildFarmJobType.PACKAGEBUILD], |
617 | + specific_job_classes()[BuildFarmJobType.PACKAGEBUILD], |
618 | BuildPackageJob, |
619 | "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`") |
620 | |
621 | @@ -721,14 +723,14 @@ |
622 | """Other job type classes are picked up as well.""" |
623 | from zope import component |
624 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
625 | - class FakeBranchBuild: |
626 | + class FakeBranchBuild(BuildFarmJob): |
627 | pass |
628 | |
629 | _build, bq = find_job(self, 'gedit') |
630 | # First make sure that we don't have a job type class registered for |
631 | # 'BRANCHBUILD' yet. |
632 | self.assertTrue( |
633 | - bq.specific_job_classes.get(BuildFarmJobType.BRANCHBUILD) is None) |
634 | + specific_job_classes().get(BuildFarmJobType.BRANCHBUILD) is None) |
635 | |
636 | # Pretend that our `FakeBranchBuild` class implements the |
637 | # `IBuildFarmJob` interface. |
638 | @@ -738,7 +740,7 @@ |
639 | # Now we should see the `FakeBranchBuild` class "registered" in the |
640 | # `specific_job_classes` dictionary under the 'BRANCHBUILD' key. |
641 | self.assertEqual( |
642 | - bq.specific_job_classes[BuildFarmJobType.BRANCHBUILD], |
643 | + specific_job_classes()[BuildFarmJobType.BRANCHBUILD], |
644 | FakeBranchBuild) |
645 | |
646 |
Hello there!
The branch at hand generalizes the selection of candidate jobs for idle
builders while enabling specific job classes to influence or refine the db
queries used in the process of selection.
Test to build:
bin/test -vv -t build
No pertinent "make lint" issues.