Merge lp:~al-maisan/launchpad/spec-job-class-503839 into lp:launchpad

Proposed by Muharem Hrnjadovic
Status: Merged
Merged at revision: not available
Proposed branch: lp:~al-maisan/launchpad/spec-job-class-503839
Merge into: lp:launchpad
Diff against target: 182 lines (+114/-3)
4 files modified
lib/lp/soyuz/configure.zcml (+10/-0)
lib/lp/soyuz/interfaces/buildqueue.py (+4/-0)
lib/lp/soyuz/model/buildqueue.py (+22/-3)
lib/lp/soyuz/tests/test_buildqueue.py (+78/-0)
To merge this branch: bzr merge lp:~al-maisan/launchpad/spec-job-class-503839
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Björn Tillenius code Pending
Review via email: mp+16946@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hello there!

This is another step in the renovation of the (Soyuz) build farm. Now that
we're introducing additional build farm job types we need to be able to figure
out what these are without hardcoding that information.

This is mostly required by the general build farm infrastructure i.e. for the
purpose of job dispatch time estimation and candidate job selection.

Pre-implementation discussion (via email) with Gary Poster and others.

Tests to run:

    bin/test -vv -t TestJobClasses

No pertinent "make lint" errors or warnings.

Please see also bug #503839 for more detail.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (9.3 KiB)

Hi Muharem!

It's great seeing the pieces of the generalisation falling into place!

I've got one rather large question about this change: Why can't you just
use a normal adapter? It seems that you've gone the long way around to
implement adapter-like functionality.

That is, you've got a BuildFarmJobType specified on IBuildQueue.job_type,
and you want to find the corresponding class. I don't know whether it is
the best solution, but you *could* in this case, adapt the build_queue
entry itself to the correct IBuildFarmJob implementation?

IBuildFarmJob(specific_build_queue_entry)

which would return the specifc build farm job.

I'm wondering if it's really that you need specific_job_classes for further
work in the build estimations, rather than specifically for this branch. That
is, originally when you were chatting with gary about using the class manager,
I had understood that you needed to *iterate* over the different
classes to build a query... in that case you would certainly need something
like the specific_job_classes that you've provided below. But as it is, you are
not needing to iterate them at all for this branch, and looking at this branch
in isolation would suggest that a normal adapter would be more intuitive.

If it is the case that you will need specific_job_classes for a later branch,
then it may indeed make sense to *not* use adaption here and re-use the
specific_job_classes exactly as you've done, as otherwise new types
would need to define both the utility *and* the adapter. I'd like to get
BjornT's thoughts on that point, hence marking as needs information.

Some comments in line below.

> === modified file 'lib/lp/soyuz/configure.zcml'
> --- lib/lp/soyuz/configure.zcml 2010-01-04 21:15:46 +0000
> +++ lib/lp/soyuz/configure.zcml 2010-01-07 08:23:28 +0000
> @@ -897,6 +897,16 @@
> <allow
> interface="lp.soyuz.interfaces.buildpackagejob.IBuildPackageJob"/>
> </class>
> + <!--
> + The registration below is used to discover all build farm job classes
> + that implement the `IBuildFarmJob` interface. Please see bug #503839
> + for more detail.
> + The 'name' attribute needs to be set to the appropriate
> + `BuildFarmJobType` enumeration value.
> + -->
> + <utility component="lp.soyuz.model.buildpackagejob.BuildPackageJob"
> + name="PACKAGEBUILD"
> + provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"/>
>
> <!-- BinaryPackageBuildBehavior -->
> <adapter
>
> === modified file 'lib/lp/soyuz/interfaces/buildqueue.py'
> --- lib/lp/soyuz/interfaces/buildqueue.py 2009-11-27 13:33:55 +0000
> +++ lib/lp/soyuz/interfaces/buildqueue.py 2010-01-07 08:23:28 +0000
> @@ -55,6 +55,10 @@
> title=_('The builder behavior required to run this job.'),
> required=False, readonly=True)
>
> + specific_job_classes = Field(
> + title=_('Job classes that may run on the build farm.'),
> + required=True, readonly=True)
> +
> estimated_duration = Timedelta(
> title=_("Estimated Job Duration"), required=True,
> description=_("Estimated job duration interval."))
>
> === modified file 'lib/lp/soyuz/...

Read more...

review: Needs Information (code)
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :
Download full text (10.1 KiB)

Michael Nelson wrote:
> Review: Needs Information code
> Hi Muharem!
>
> It's great seeing the pieces of the generalisation falling into place!
>
> I've got one rather large question about this change: Why can't you just
> use a normal adapter? It seems that you've gone the long way around to
> implement adapter-like functionality.
>
> That is, you've got a BuildFarmJobType specified on IBuildQueue.job_type,
> and you want to find the corresponding class. I don't know whether it is
> the best solution, but you *could* in this case, adapt the build_queue
> entry itself to the correct IBuildFarmJob implementation?
>
> IBuildFarmJob(specific_build_queue_entry)
>
> which would return the specifc build farm job.
>
> I'm wondering if it's really that you need specific_job_classes for further
> work in the build estimations, rather than specifically for this branch. That
> is, originally when you were chatting with gary about using the class manager,
> I had understood that you needed to *iterate* over the different
> classes to build a query... in that case you would certainly need something
> like the specific_job_classes that you've provided below. But as it is, you are
> not needing to iterate them at all for this branch, and looking at this branch
> in isolation would suggest that a normal adapter would be more intuitive.
>
> If it is the case that you will need specific_job_classes for a later branch,
> then it may indeed make sense to *not* use adaption here and re-use the
> specific_job_classes exactly as you've done, as otherwise new types
> would need to define both the utility *and* the adapter. I'd like to get
> BjornT's thoughts on that point, hence marking as needs information.

Hello Michael, sorry for the terse answer but, yes, I need to iterate
over the farm build job classes.
See e.g. lines 48-54 in the enclosed diff which is an excerpt from
lp:~al-maisan/launchpad/job-delays-504086, the branch I am currently
working on.

> Some comments in line below.
>
>> === modified file 'lib/lp/soyuz/configure.zcml'
>> --- lib/lp/soyuz/configure.zcml 2010-01-04 21:15:46 +0000
>> +++ lib/lp/soyuz/configure.zcml 2010-01-07 08:23:28 +0000
>> @@ -897,6 +897,16 @@
>> <allow
>> interface="lp.soyuz.interfaces.buildpackagejob.IBuildPackageJob"/>
>> </class>
>> + <!--
>> + The registration below is used to discover all build farm job classes
>> + that implement the `IBuildFarmJob` interface. Please see bug #503839
>> + for more detail.
>> + The 'name' attribute needs to be set to the appropriate
>> + `BuildFarmJobType` enumeration value.
>> + -->
>> + <utility component="lp.soyuz.model.buildpackagejob.BuildPackageJob"
>> + name="PACKAGEBUILD"
>> + provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"/>
>>
>> <!-- BinaryPackageBuildBehavior -->
>> <adapter
>>
>> === modified file 'lib/lp/soyuz/interfaces/buildqueue.py'
>> --- lib/lp/soyuz/interfaces/buildqueue.py 2009-11-27 13:33:55 +0000
>> +++ lib/lp/soyuz/interfaces/buildqueue.py 2010-01-07 08:23:28 +0000
>> @@ -55,6 +55,10 @@
>> title=_('The builder behavior required to run this ...

=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-07 06:49:04 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-01-07 09:58:59 +0000
@@ -211,6 +211,9 @@
211 # The job of interest (JOI) is processor independent.211 # The job of interest (JOI) is processor independent.
212 builders_for_job = builders_in_total212 builders_for_job = builders_in_total
213213
214 # All builders can take on platform-independent jobs.
215 builder_stats[(None,None)] = builders_in_total
216
214 return (builders_in_total, builders_for_job, builder_stats)217 return (builders_in_total, builders_for_job, builder_stats)
215218
216 def _freeBuildersCount(self, processor, virtualized):219 def _freeBuildersCount(self, processor, virtualized):
@@ -313,6 +316,123 @@
313 else:316 else:
314 return int(head_job_delay)317 return int(head_job_delay)
315318
319 def _estimateJobDelay(self, builders_in_total, builder_stats):
320 """Sum of estimated durations for *pending* jobs ahead in queue.
321
322 For the purpose of estimating the dispatch time of the job of
323 interest (JOI) we need to know the delay caused by all the pending
324 jobs that are ahead of the JOI in the queue and that compete with it
325 for builders.
326
327 :param builders_in_total: How many active builders are available
328 altogether?
329 :param builder_stats: A dictionary with builder counts where the
330 key is a (processor, virtualized) combination (aka "platform") and
331 the value is the number of builders that can take on jobs
332 requiring that combination.
333 :return: A (sum_of_delays, head_job_platform) tuple where
334 * 'sum_of_delays' is the estimated delay in seconds and
335 * 'head_job_platform' is the platform ((processor, virtualized)
336 combination) required by the job at the head of the queue.
337 """
338 # Please note: this method will send only one request to the database.
339 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
340 my_processor = self.specific_job.processor
341 my_virtualized = self.specific_job.virtualized
342 my_platform = (my_processor, my_virtualized)
343
344 # Ask all build farm job type classes for a plain SQL SELECT query
345 # that will yield the pending jobs whose score is equal or better
346 # than the score of the job of interest (JOI).
347 # Chain these queries in "SELECT .. UNION SELECT .." fashion and
348 # use them to obtain the jobs that compete with the JOI for builders.
349 queries = []
350 for job_class in self.specific_job_classes.values():
351 queries.append(
352 job_class.composePendingJobsQuery(
353 self.lastscore, my_processor, my_virtualized))
354 query = '%s ORDER BY lastscore DESC, job' % ' UNION '.join(queries)
355 job_queue = store.execute(query).get_all()
356
357 sum_of_delays = 0
358
359 # This will be used to capture per-platform delay totals.
360 delays = dict()
361 # This will be used to capture per-platform job counts.
362 job_counts = dict()
363
364 # Which platform is the job at the head of the queue requiring?
365 head_job_platform = None
366
367 # Apply weights to the estimated duration of the jobs as follows:
368 # - if a job is tied to a processor TP then divide the estimated
369 # duration of that job by the number of builders that target TP
370 # since only these can build the job.
371 # - if the job is processor-independent then divide its estimated
372 # duration by the total number of builders because any one of
373 # them may run it.
374 for job, score, duration, processor, virtualized in job_queue:
375 if job == self.job.id:
376 # We have seen all jobs that are ahead of us in the queue
377 # and can stop now.
378 # This is guaranteed by the "ORDER BY lastscore DESC.."
379 # clause above.
380 break
381
382 # For the purpose of estimating the delay for dispatching the job
383 # at the head of the queue to a builder we need to capture the
384 # platform it targets.
385 if head_job_platform is None:
386 platform = (processor, virtualized)
387 if my_processor is None:
388 # The JOI is platform-independent i.e. the highest scored
389 # job will be the head job.
390 head_job_platform = platform
391 else:
392 # The JOI targets a specific platform. The head job is
393 # thus the highest scored job that either targets the
394 # same platform or is platform-independent.
395 if (my_platform == platform or processor is None):
396 head_job_platform = platform
397
398 if processor is None:
399 # Processor independent job, can be run on any builder.
400 builder_count = builders_in_total
401 else:
402 # Tied to a particular processor, see how many builders
403 # can run it.
404 builder_count = builder_stats.get((processor, virtualized), 0)
405
406 if builder_count == 0:
407 # There is no builder that can run this job, ignore it
408 # for the purpose of dispatch time estimation.
409 continue
410
411 # Accumulate the delays, and count the number of jobs causing them
412 # on a (processor, virtualized) basis.
413 duration = duration.seconds
414 delays[(processor, virtualized)] = (
415 delays.setdefault((processor, virtualized), 0) + duration)
416 job_counts[(processor, virtualized)] = (
417 job_counts.setdefault((processor, virtualized), 0) + 1)
418
419 # Now weight/average the delays based on a jobs/builders comparison.
420 for platform, duration in delays.iteritems():
421 jobs = job_counts[platform]
422 builders = builder_stats[platform]
423 if jobs < builders:
424 # There are less jobs than builders that can take them on,
425 # the delays should be averaged/divided by the number of jobs.
426 denominator = jobs
427 else:
428 denominator = builders
429 if denominator > 1:
430 duration = int(duration/float(denominator))
431
432 sum_of_delays += duration
433
434 return (sum_of_delays, head_job_platform)
435
316436
317class BuildQueueSet(object):437class BuildQueueSet(object):
318 """Utility to deal with BuildQueue content class."""438 """Utility to deal with BuildQueue content class."""
319439
=== modified file 'lib/lp/soyuz/tests/test_buildqueue.py'
--- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-07 06:49:04 +0000
+++ lib/lp/soyuz/tests/test_buildqueue.py 2010-01-07 10:12:09 +0000
@@ -552,7 +552,7 @@
552 score = 1000552 score = 1000
553 duration = 0553 duration = 0
554 for build in self.builds:554 for build in self.builds:
555 score += 1555 score += getattr(self, 'score_increment', 1)
556 duration += 60556 duration += 60
557 bq = build.buildqueue_record557 bq = build.buildqueue_record
558 bq.lastscore = score558 bq.lastscore = score
@@ -740,3 +740,96 @@
740 self.assertEqual(740 self.assertEqual(
741 bq.specific_job_classes[BuildFarmJobType.BRANCHBUILD],741 bq.specific_job_classes[BuildFarmJobType.BRANCHBUILD],
742 FakeBranchBuild)742 FakeBranchBuild)
743
744
745class TestMultiArchJobDelayEstimation(MultiArchBuildsBase):
746 """Test estimated job delays with various processors."""
747 score_increment = 2
748 def setUp(self):
749 """Set up a fake 'BRANCHBUILD' build farm job class.
750
751 The two platform-independent jobs will have a score of 1017 and 1033
752 respectively.
753
754 gedit, p: hppa, v:False e:0:01:00 *** s: 1002
755 gedit, p: 386, v:False e:0:02:00 *** s: 1004
756 firefox, p: hppa, v:False e:0:03:00 *** s: 1006
757 firefox, p: 386, v:False e:0:04:00 *** s: 1008
758 apg, p: hppa, v:False e:0:05:00 *** s: 1010
759 apg, p: 386, v:False e:0:06:00 *** s: 1012
760 vim, p: hppa, v:False e:0:07:00 *** s: 1014
761 vim, p: 386, v:False e:0:08:00 *** s: 1016
762 --> fake1, none none 0:00:22 1017
763 gcc, p: hppa, v:False e:0:09:00 *** s: 1018
764 gcc, p: 386, v:False e:0:10:00 *** s: 1020
765 bison, p: hppa, v:False e:0:11:00 *** s: 1022
766 bison, p: 386, v:False e:0:12:00 *** s: 1024
767 flex, p: hppa, v:False e:0:13:00 *** s: 1026
768 flex, p: 386, v:False e:0:14:00 *** s: 1028
769 postgres, p: hppa, v:False e:0:15:00 *** s: 1030
770 postgres, p: 386, v:False e:0:16:00 *** s: 1032
771 --> fake2, none none 0:03:42 1033
772
773 p=processor, v=virtualized, e=estimated_duration, s=score
774 """
775 super(TestMultiArchJobDelayEstimation, self).setUp()
776
777 from zope import component
778 from zope.interface import implements
779 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
780
781 class FakeBranchBuild:
782 implements(IBuildFarmJob)
783 def score(self):
784 return -9999
785 def getLogFileName(self):
786 return 'buildlog_fake-branch-build.txt'
787 def getName(self):
788 return 'fake-branch-build'
789 @staticmethod
790 def composePendingJobsQuery(min_score, processor, virtualized):
791 return """
792 (SELECT
793 1000001::integer AS job,
794 1017::integer AS lastscore,
795 '22 seconds'::interval AS estimated_duration,
796 NULL::integer AS processor,
797 NULL::boolean AS virtualized
798 UNION
799 SELECT
800 1000002::integer AS job,
801 1033::integer AS lastscore,
802 '222 seconds'::interval AS estimated_duration,
803 NULL::integer AS processor,
804 NULL::boolean AS virtualized)
805 """
806
807 # Pretend that our `FakeBranchBuild` class implements the
808 # `IBuildFarmJob` interface.
809 component.provideUtility(
810 FakeBranchBuild, IBuildFarmJob, 'BRANCHBUILD')
811
812 def test_job_delay(self):
813 processor_fam = ProcessorFamilySet().getByName('hppa')
814 hppa_proc = processor_fam.processors[0]
815
816 # One of four builders for the 'apg' build is immediately available.
817 flex_build, flex_job = find_job(self, 'flex', 'hppa')
818 check_mintime_to_builder(self, flex_job, hppa_proc, False, 0)
819
820 builder_data = flex_job._getBuilderData()
821 builders_in_total, builders_for_job, builder_stats = builder_data
822
823 # The delay is 900 (= 15*60) + 222 seconds, the head job is
824 # platform-independent.
825 self.assertEqual(
826 flex_job._estimateJobDelay(builders_in_total, builder_stats),
827 (1122, (None, None)))
828
829 # Assign the postgres job to a builder.
830 assign_to_builder(self, 'postgres', 1, 'hppa')
831 # Now only the 222 seconds (the estimated duration of the
832 # platform-independent job) should be returned.
833 self.assertEqual(
834 flex_job._estimateJobDelay(builders_in_total, builder_stats),
835 (222, (None, None)))
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Michael Nelson wrote:
> Review: Needs Information code
[..]
> I've got one rather large question about this change: Why can't you
> just use a normal adapter? It seems that you've gone the long way
> around to implement adapter-like functionality.
>
> That is, you've got a BuildFarmJobType specified on
> IBuildQueue.job_type, and you want to find the corresponding class. I
> don't know whether it is the best solution, but you *could* in this
> case, adapt the build_queue entry itself to the correct IBuildFarmJob
> implementation?
>
> IBuildFarmJob(specific_build_queue_entry)
How would that actually work? IBuildFarmJob is not what we want here.
IBuildQueue.specific_job is supposed to yield something like a
BuildPackageJob instance.

> which would return the specifc build farm job.
>
> I'm wondering if it's really that you need specific_job_classes for
> further work in the build estimations, rather than specifically for
That's the case indeed, sorry for not explaining that better.

> this branch. That is, originally when you were chatting with gary
> about using the class manager, I had understood that you needed to
> *iterate* over the different classes to build a query... in that case
> you would certainly need something like the specific_job_classes that
> you've provided below. But as it is, you are not needing to iterate
> them at all for this branch, and looking at this branch in isolation
> would suggest that a normal adapter would be more intuitive.
Sorry for not explaining in more detail why this is needed please see my
previous email for an example where I do need to iterate over the build
farm job classes.
I also expect that that's something that will be needed when we
generalize the candidate job selection.

> If it is the case that you will need specific_job_classes for a later
> branch, then it may indeed make sense to *not* use adaption here and
> re-use the specific_job_classes exactly as you've done, as otherwise
As stated in the previous reply I do require the iteration over the build
farm job classes for the purpose of job dispatch time estimation.
Being able to register build farm classes in the way suggested makes it
also much easier to test the code.
Please see the end of the diff (lines 193-222) enclosed in my previous
reply for an example.

[..]

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Great, thanks for the info Muharem.

So given that you need the class iteration, I'm keen to approve this now (assuming you address the issues I mentioned above inline).

Thanks!

review: Approve (code)
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :
Download full text (8.5 KiB)

Michael Nelson wrote:
[..]

Hello Michael,

thanks again for taking the time to review this branch despite your own
busy schedule. That's very much appreciated :)

> Some comments in line below.
Please see my replies as well as the enclosed incremental diff.

>> === modified file 'lib/lp/soyuz/configure.zcml'
>> --- lib/lp/soyuz/configure.zcml 2010-01-04 21:15:46 +0000
>> +++ lib/lp/soyuz/configure.zcml 2010-01-07 08:23:28 +0000
>> @@ -897,6 +897,16 @@
>> <allow
>> interface="lp.soyuz.interfaces.buildpackagejob.IBuildPackageJob"/>
>> </class>
>> + <!--
>> + The registration below is used to discover all build farm job classes
>> + that implement the `IBuildFarmJob` interface. Please see bug #503839
>> + for more detail.
>> + The 'name' attribute needs to be set to the appropriate
>> + `BuildFarmJobType` enumeration value.
>> + -->
>> + <utility component="lp.soyuz.model.buildpackagejob.BuildPackageJob"
>> + name="PACKAGEBUILD"
>> + provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"/>
>>
>> <!-- BinaryPackageBuildBehavior -->
>> <adapter
>>
>> === modified file 'lib/lp/soyuz/interfaces/buildqueue.py'
>> --- lib/lp/soyuz/interfaces/buildqueue.py 2009-11-27 13:33:55 +0000
>> +++ lib/lp/soyuz/interfaces/buildqueue.py 2010-01-07 08:23:28 +0000
>> @@ -55,6 +55,10 @@
>> title=_('The builder behavior required to run this job.'),
>> required=False, readonly=True)
>>
>> + specific_job_classes = Field(
>> + title=_('Job classes that may run on the build farm.'),
>> + required=True, readonly=True)
>> +
>> estimated_duration = Timedelta(
>> title=_("Estimated Job Duration"), required=True,
>> description=_("Estimated job duration interval."))
>>
>> === modified file 'lib/lp/soyuz/model/buildqueue.py'
>> --- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
>> +++ lib/lp/soyuz/model/buildqueue.py 2010-01-07 08:23:28 +0000
>> @@ -57,11 +57,31 @@
>> return IBuildFarmJobBehavior(self.specific_job)
>>
>> @property
>> + def specific_job_classes(self):
>> + """See `IBuildQueue`."""
>> + from zope.component import getSiteManager
>> + from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
>
> Is there a reason for these imports to be inline? If so, please add a
> comment or otherwise move them as per the guidelines.
No other code in the module uses them. I have added a comment to that
effect.

>> +
>> + job_classes = []
>> + components = getSiteManager()
>> + # Get all components that implement the `IBuildFarmJob` interface.
>> + implementations = sorted(components.getUtilitiesFor(IBuildFarmJob))
>> + # The above yields a collection of 2-tuples where the first element
>> + # is the name of the `BuildFarmJobType` enum and the second element
>> + # is the implementing class respectively.
>> + for job_enum_name, job_class in implementations:
>> + job_enum = getattr(BuildFarmJobType, job_enum_name)
>
> I was originally going to say that we should be checking first with
> safe_hasat...

Read more...

=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-07 05:36:53 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-01-07 18:41:24 +0000
@@ -59,21 +59,23 @@
59 @property59 @property
60 def specific_job_classes(self):60 def specific_job_classes(self):
61 """See `IBuildQueue`."""61 """See `IBuildQueue`."""
62 # The reason for keeping the imports below local is that they are not
63 # of interest to other methods in this module.
62 from zope.component import getSiteManager64 from zope.component import getSiteManager
63 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob65 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
6466
65 job_classes = []67 job_classes = dict()
68 # Get all components that implement the `IBuildFarmJob` interface.
66 components = getSiteManager()69 components = getSiteManager()
67 # Get all components that implement the `IBuildFarmJob` interface.
68 implementations = sorted(components.getUtilitiesFor(IBuildFarmJob))70 implementations = sorted(components.getUtilitiesFor(IBuildFarmJob))
69 # The above yields a collection of 2-tuples where the first element71 # The above yields a collection of 2-tuples where the first element
70 # is the name of the `BuildFarmJobType` enum and the second element72 # is the name of the `BuildFarmJobType` enum and the second element
71 # is the implementing class respectively.73 # is the implementing class respectively.
72 for job_enum_name, job_class in implementations:74 for job_enum_name, job_class in implementations:
73 job_enum = getattr(BuildFarmJobType, job_enum_name)75 job_enum = getattr(BuildFarmJobType, job_enum_name)
74 job_classes.append((job_enum, job_class))76 job_classes[job_enum] = job_class
7577
76 return dict(job_classes)78 return job_classes
7779
78 @property80 @property
79 def specific_job(self):81 def specific_job(self):
Revision history for this message
Michael Nelson (michael.nelson) wrote :

On Thu, Jan 7, 2010 at 8:57 PM, Muharem Hrnjadovic
<email address hidden> wrote:
> Michael Nelson wrote:
> [..]
>
> Hello Michael,
>
> thanks again for taking the time to review this branch despite your own
> busy schedule. That's very much appreciated :)

No probs!

>
>> Some comments in line below.
> Please see my replies as well as the enclosed incremental diff.
>
>>> === modified file 'lib/lp/soyuz/model/buildqueue.py'
>>> --- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
>>> +++ lib/lp/soyuz/model/buildqueue.py 2010-01-07 08:23:28 +0000
>>> @@ -57,11 +57,31 @@
>>>          return IBuildFarmJobBehavior(self.specific_job)
>>>
>>>      @property
>>> +    def specific_job_classes(self):
>>> +        """See `IBuildQueue`."""
>>> +        from zope.component import getSiteManager
>>> +        from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
>>
>> Is there a reason for these imports to be inline? If so, please add a
>> comment or otherwise move them as per the guidelines.
> No other code in the module uses them. I have added a comment to that
> effect.

Erm... I didn't realise that was a valid reason? I thought we only did
so if we *had* to (ie. circular imports etc.). Please check our
styleguide... I'm going from memory but I thought the imports should
always be at the top even if no other code in the module uses them.

Cheers, and happy flying!
-Michael

Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Michael Nelson wrote:
> On Thu, Jan 7, 2010 at 8:57 PM, Muharem Hrnjadovic
> <email address hidden> wrote:
>> Michael Nelson wrote:
>> [..]
>>
>> Hello Michael,
>>
>> thanks again for taking the time to review this branch despite your own
>> busy schedule. That's very much appreciated :)
>
> No probs!
>
>>> Some comments in line below.
>> Please see my replies as well as the enclosed incremental diff.
>>
>>>> === modified file 'lib/lp/soyuz/model/buildqueue.py'
>>>> --- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
>>>> +++ lib/lp/soyuz/model/buildqueue.py 2010-01-07 08:23:28 +0000
>>>> @@ -57,11 +57,31 @@
>>>> return IBuildFarmJobBehavior(self.specific_job)
>>>>
>>>> @property
>>>> + def specific_job_classes(self):
>>>> + """See `IBuildQueue`."""
>>>> + from zope.component import getSiteManager
>>>> + from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
>>> Is there a reason for these imports to be inline? If so, please add a
>>> comment or otherwise move them as per the guidelines.
>> No other code in the module uses them. I have added a comment to that
>> effect.
>
> Erm... I didn't realise that was a valid reason? I thought we only did
> so if we *had* to (ie. circular imports etc.). Please check our
> styleguide... I'm going from memory but I thought the imports should
> always be at the top even if no other code in the module uses them.

Whoops .. sorry .. and thanks for catching this :)

> Cheers, and happy flying!

Thank you! Have a safe journey as well :)

Best regards

--
Muharem Hrnjadovic <email address hidden>
Public key id : B2BBFCFC
Key fingerprint : A5A3 CC67 2B87 D641 103F 5602 219F 6B60 B2BB FCFC

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2010-01-04 21:15:46 +0000
+++ lib/lp/soyuz/configure.zcml 2010-01-09 02:49:17 +0000
@@ -897,6 +897,16 @@
897 <allow897 <allow
898 interface="lp.soyuz.interfaces.buildpackagejob.IBuildPackageJob"/>898 interface="lp.soyuz.interfaces.buildpackagejob.IBuildPackageJob"/>
899 </class>899 </class>
900 <!--
901 The registration below is used to discover all build farm job classes
902 that implement the `IBuildFarmJob` interface. Please see bug #503839
903 for more detail.
904 The 'name' attribute needs to be set to the appropriate
905 `BuildFarmJobType` enumeration value.
906 -->
907 <utility component="lp.soyuz.model.buildpackagejob.BuildPackageJob"
908 name="PACKAGEBUILD"
909 provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"/>
900910
901 <!-- BinaryPackageBuildBehavior -->911 <!-- BinaryPackageBuildBehavior -->
902 <adapter912 <adapter
903913
=== modified file 'lib/lp/soyuz/interfaces/buildqueue.py'
--- lib/lp/soyuz/interfaces/buildqueue.py 2009-11-27 13:33:55 +0000
+++ lib/lp/soyuz/interfaces/buildqueue.py 2010-01-09 02:49:17 +0000
@@ -55,6 +55,10 @@
55 title=_('The builder behavior required to run this job.'),55 title=_('The builder behavior required to run this job.'),
56 required=False, readonly=True)56 required=False, readonly=True)
5757
58 specific_job_classes = Field(
59 title=_('Job classes that may run on the build farm.'),
60 required=True, readonly=True)
61
58 estimated_duration = Timedelta(62 estimated_duration = Timedelta(
59 title=_("Estimated Job Duration"), required=True,63 title=_("Estimated Job Duration"), required=True,
60 description=_("Estimated job duration interval."))64 description=_("Estimated job duration interval."))
6165
=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-06 15:58:41 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-01-09 02:49:17 +0000
@@ -12,7 +12,8 @@
1212
13import logging13import logging
1414
15from zope.component import getUtility15from zope.component import getSiteManager, getUtility
16
16from zope.interface import implements17from zope.interface import implements
1718
18from sqlobject import (19from sqlobject import (
@@ -24,7 +25,8 @@
24from canonical.database.enumcol import EnumCol25from canonical.database.enumcol import EnumCol
25from canonical.database.sqlbase import SQLBase, sqlvalues26from canonical.database.sqlbase import SQLBase, sqlvalues
26from canonical.launchpad.webapp.interfaces import NotFoundError27from canonical.launchpad.webapp.interfaces import NotFoundError
27from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType28from lp.buildmaster.interfaces.buildfarmjob import (
29 BuildFarmJobType, IBuildFarmJob)
28from lp.buildmaster.interfaces.buildfarmjobbehavior import (30from lp.buildmaster.interfaces.buildfarmjobbehavior import (
29 IBuildFarmJobBehavior)31 IBuildFarmJobBehavior)
30from lp.services.job.interfaces.job import JobStatus32from lp.services.job.interfaces.job import JobStatus
@@ -57,11 +59,28 @@
57 return IBuildFarmJobBehavior(self.specific_job)59 return IBuildFarmJobBehavior(self.specific_job)
5860
59 @property61 @property
62 def specific_job_classes(self):
63 """See `IBuildQueue`."""
64 job_classes = dict()
65 # Get all components that implement the `IBuildFarmJob` interface.
66 components = getSiteManager()
67 implementations = sorted(components.getUtilitiesFor(IBuildFarmJob))
68 # The above yields a collection of 2-tuples where the first element
69 # is the name of the `BuildFarmJobType` enum and the second element
70 # is the implementing class respectively.
71 for job_enum_name, job_class in implementations:
72 job_enum = getattr(BuildFarmJobType, job_enum_name)
73 job_classes[job_enum] = job_class
74
75 return job_classes
76
77 @property
60 def specific_job(self):78 def specific_job(self):
61 """See `IBuildQueue`."""79 """See `IBuildQueue`."""
80 specific_class = self.specific_job_classes[self.job_type]
62 store = Store.of(self)81 store = Store.of(self)
63 result_set = store.find(82 result_set = store.find(
64 BuildPackageJob, BuildPackageJob.job == self.job)83 specific_class, specific_class.job == self.job)
65 return result_set.one()84 return result_set.one()
6685
67 @property86 @property
6887
=== modified file 'lib/lp/soyuz/tests/test_buildqueue.py'
--- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-06 15:58:41 +0000
+++ lib/lp/soyuz/tests/test_buildqueue.py 2010-01-09 02:49:17 +0000
@@ -13,6 +13,7 @@
13 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)13 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
14from canonical.testing import LaunchpadZopelessLayer14from canonical.testing import LaunchpadZopelessLayer
1515
16from lp.buildmaster.interfaces.buildfarmjob import BuildFarmJobType
16from lp.soyuz.interfaces.archive import ArchivePurpose17from lp.soyuz.interfaces.archive import ArchivePurpose
17from lp.soyuz.interfaces.build import BuildStatus18from lp.soyuz.interfaces.build import BuildStatus
18from lp.soyuz.interfaces.builder import IBuilderSet19from lp.soyuz.interfaces.builder import IBuilderSet
@@ -662,3 +663,80 @@
662663
663 # That builder should be available immediately since it's idle.664 # That builder should be available immediately since it's idle.
664 check_mintime_to_builder(self, apg_job, None, None, 0)665 check_mintime_to_builder(self, apg_job, None, None, 0)
666
667
668class TestJobClasses(TestCaseWithFactory):
669 """Tests covering build farm job type classes."""
670 layer = LaunchpadZopelessLayer
671 def setUp(self):
672 """Set up a native x86 build for the test archive."""
673 super(TestJobClasses, self).setUp()
674
675 self.publisher = SoyuzTestPublisher()
676 self.publisher.prepareBreezyAutotest()
677
678 # First mark all builds in the sample data as already built.
679 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
680 sample_data = store.find(Build)
681 for build in sample_data:
682 build.buildstate = BuildStatus.FULLYBUILT
683 store.flush()
684
685 # We test builds that target a primary archive.
686 self.non_ppa = self.factory.makeArchive(
687 name="primary", purpose=ArchivePurpose.PRIMARY)
688 self.non_ppa.require_virtualized = False
689
690 self.builds = []
691 self.builds.extend(
692 self.publisher.getPubSource(
693 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
694 archive=self.non_ppa).createMissingBuilds())
695
696 def test_BuildPackageJob(self):
697 """`BuildPackageJob` is one of the job type classes."""
698 from lp.soyuz.model.buildpackagejob import BuildPackageJob
699 _build, bq = find_job(self, 'gedit')
700
701 # This is a binary package build.
702 self.assertEqual(
703 bq.job_type, BuildFarmJobType.PACKAGEBUILD,
704 "This is a binary package build")
705
706 # The class registered for 'PACKAGEBUILD' is `BuildPackageJob`.
707 self.assertEqual(
708 bq.specific_job_classes[BuildFarmJobType.PACKAGEBUILD],
709 BuildPackageJob,
710 "The class registered for 'PACKAGEBUILD' is `BuildPackageJob`")
711
712 # The 'specific_job' object associated with this `BuildQueue`
713 # instance is of type `BuildPackageJob`.
714 self.assertTrue(bq.specific_job is not None)
715 self.assertEqual(
716 bq.specific_job.__class__, BuildPackageJob,
717 "The 'specific_job' object associated with this `BuildQueue` "
718 "instance is of type `BuildPackageJob`")
719
720 def test_OtherTypeClasses(self):
721 """Other job type classes are picked up as well."""
722 from zope import component
723 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
724 class FakeBranchBuild:
725 pass
726
727 _build, bq = find_job(self, 'gedit')
728 # First make sure that we don't have a job type class registered for
729 # 'BRANCHBUILD' yet.
730 self.assertTrue(
731 bq.specific_job_classes.get(BuildFarmJobType.BRANCHBUILD) is None)
732
733 # Pretend that our `FakeBranchBuild` class implements the
734 # `IBuildFarmJob` interface.
735 component.provideUtility(
736 FakeBranchBuild, IBuildFarmJob, 'BRANCHBUILD')
737
738 # Now we should see the `FakeBranchBuild` class "registered" in the
739 # `specific_job_classes` dictionary under the 'BRANCHBUILD' key.
740 self.assertEqual(
741 bq.specific_job_classes[BuildFarmJobType.BRANCHBUILD],
742 FakeBranchBuild)