Merge lp:~al-maisan/launchpad/xx-select-job-506617 into lp:launchpad/db-devel

Proposed by Muharem Hrnjadovic
Status: Merged
Merged at revision: not available
Proposed branch: lp:~al-maisan/launchpad/xx-select-job-506617
Merge into: lp: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:~al-maisan/launchpad/xx-select-job-506617
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+17327@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

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.

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, IBuildFarmCandidateJobSelection, plays a role that seems to fit well in the one I added in my branch lp:~jtv/launchpad/bug-500110 called ISpecificBuildFarmJobClass. Consider merging these two interfaces; it may also help prevent conflicts since your branch is going into db-devel whereas mine is for devel.

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

Subscribers

People subscribed via source and target branches

to status/vote changes: