Merge lp:~al-maisan/launchpad/pending-jobs-499861 into lp:launchpad

Proposed by Muharem Hrnjadovic
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~al-maisan/launchpad/pending-jobs-499861
Merge into: lp:launchpad
Diff against target: 567 lines (+507/-4)
4 files modified
lib/lp/buildmaster/interfaces/buildfarmjob.py (+66/-1)
lib/lp/buildmaster/model/buildfarmjob.py (+10/-0)
lib/lp/soyuz/model/buildpackagejob.py (+47/-3)
lib/lp/soyuz/tests/test_buildpackagejob.py (+384/-0)
To merge this branch: bzr merge lp:~al-maisan/launchpad/pending-jobs-499861
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Jonathan Lange (community) Approve
Review via email: mp+16550@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hi there!

this branch

    - introduces a method (IBuildFarmJob.pendingJobsQuery()) that all
      Soyuz build farm jobs need to implement in order to enable a
      general (i.e. across all job types) job dispatch time estimation.
    - implements the pendingJobsQuery() method for the `BuildPackageJob`
      class i.e. for binary builds.

The logic was broken out from a larger branch that implements the
general job dispatch time estimation logic so it land earlier allowing
jtv and mwhudson to implement the pendingJobsQuery() method inside their
respective translation and recipe build job classes.

Tests to run:

    bin/test -vv -t TestBuildPackageJob

No pertinent "make lint" errors or warnings.

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (26.2 KiB)

On Thu, Dec 24, 2009 at 3:45 AM, Muharem Hrnjadovic
<email address hidden> wrote:
> Muharem Hrnjadovic has proposed merging lp:~al-maisan/launchpad/pending-jobs-499861 into lp:launchpad/devel.
>
>    Requested reviews:
>    Canonical Launchpad Engineering (launchpad)
> Related bugs:
>  #499861 Define interface allowing build farm job classes to select pending jobs
>  https://bugs.launchpad.net/bugs/499861
>
>
> Hi there!
>
> this branch
>
>    - introduces a method (IBuildFarmJob.pendingJobsQuery()) that all
>      Soyuz build farm jobs need to implement in order to enable a
>      general (i.e. across all job types) job dispatch time estimation.

Although this sounds like a good idea to me, I'm confused by the
terminology. Why do we ask a job (singular) to return a list of
pending jobs (plural)? It seems like this method would be better
placed on an interface representing a type of job.

...
> === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
> --- lib/lp/buildmaster/interfaces/buildfarmjob.py       2009-12-01 08:43:43 +0000
> +++ lib/lp/buildmaster/interfaces/buildfarmjob.py       2009-12-23 16:45:30 +0000
> @@ -12,8 +12,12 @@
>     'BuildFarmJobType',
>     ]
>
> -from zope.interface import Interface
> +from zope.interface import Interface, Attribute
> +
> +from canonical.launchpad import _
>  from lazr.enum import DBEnumeratedType, DBItem
> +from lazr.restful.fields import Reference
> +from lp.soyuz.interfaces.processor import IProcessor
>
>
>  class BuildFarmJobType(DBEnumeratedType):
> @@ -69,3 +73,64 @@
>     def jobAborted():
>         """'Job aborted' life cycle event, handle as appropriate."""
>
> +    def pendingJobsQuery(minscore, processor, virtualized):

Given that this is a method, it's name should be in the imperative,
e.g. getPendingJobsQuery.

> +        """String SELECT query yielding pending jobs with given minimum score.
> +
> +        This will be used for the purpose of job dispatch time estimation
> +        for a build job of interest (JOI).
> +        In order to estimate the dispatch time for the JOI we need to
> +        calculate the sum of the estimated durations of the *pending* jobs
> +        ahead of JOI.
> +
> +        Depending on the build farm job type the JOI may or may not be tied
> +        to a particular processor type.
> +        Binary builds for example are always built for a specific processor
> +        whereas "create a source package from recipe" type jobs do not care
> +        about processor types or virtualization.
> +
> +        When implementing this method for processor independent build farm job
> +        types (e.g. recipe build) you may safely ignore the `processor` and
> +        `virtualized` parameters.
> +
> +        The SELECT query to be returned needs to select the following data
> +
> +            1 - BuildQueue.job
> +            2 - BuildQueue.lastscore
> +            3 - BuildQueue.estimated_duration
> +            4 - Processor.id    [optional]
> +            5 - virtualized     [optional]
> +
> +        Please do *not* order the result set since it will be UNIONed and
> +        ordered only then.
> +

Thanks, this is a useful docstring.

> +        Job...

Revision history for this message
Jonathan Lange (jml) wrote :

As per my email, there are still some things to fix up.

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

Jonathan Lange wrote:
> On Thu, Dec 24, 2009 at 3:45 AM, Muharem Hrnjadovic
> <email address hidden> wrote:
>> Muharem Hrnjadovic has proposed merging lp:~al-maisan/launchpad/pending-jobs-499861 into lp:launchpad/devel.
>>
>> Requested reviews:
>> Canonical Launchpad Engineering (launchpad)
>> Related bugs:
>> #499861 Define interface allowing build farm job classes to select pending jobs
>> https://bugs.launchpad.net/bugs/499861
>>
>>
>> Hi there!
>>
>> this branch
>>
>> - introduces a method (IBuildFarmJob.pendingJobsQuery()) that all
>> Soyuz build farm jobs need to implement in order to enable a
>> general (i.e. across all job types) job dispatch time estimation.
>
> Although this sounds like a good idea to me, I'm confused by the
> terminology. Why do we ask a job (singular) to return a list of
> pending jobs (plural)? It seems like this method would be better
> placed on an interface representing a type of job.

Hmm .. good point .. can you point to an example?

The original idea was that these methods be implemented as static
methods. I can stress that point in the doc string if desired.

> ...
>> === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
>> --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-01 08:43:43 +0000
>> +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-23 16:45:30 +0000
>> @@ -12,8 +12,12 @@
>> 'BuildFarmJobType',
>> ]
>>
>> -from zope.interface import Interface
>> +from zope.interface import Interface, Attribute
>> +
>> +from canonical.launchpad import _
>> from lazr.enum import DBEnumeratedType, DBItem
>> +from lazr.restful.fields import Reference
>> +from lp.soyuz.interfaces.processor import IProcessor
>>
>>
>> class BuildFarmJobType(DBEnumeratedType):
>> @@ -69,3 +73,64 @@
>> def jobAborted():
>> """'Job aborted' life cycle event, handle as appropriate."""
>>
>> + def pendingJobsQuery(minscore, processor, virtualized):
>
> Given that this is a method, it's name should be in the imperative,
> e.g. getPendingJobsQuery.
done.

>> + """String SELECT query yielding pending jobs with given minimum score.
>> +
>> + This will be used for the purpose of job dispatch time estimation
>> + for a build job of interest (JOI).
>> + In order to estimate the dispatch time for the JOI we need to
>> + calculate the sum of the estimated durations of the *pending* jobs
>> + ahead of JOI.
>> +
>> + Depending on the build farm job type the JOI may or may not be tied
>> + to a particular processor type.
>> + Binary builds for example are always built for a specific processor
>> + whereas "create a source package from recipe" type jobs do not care
>> + about processor types or virtualization.
>> +
>> + When implementing this method for processor independent build farm job
>> + types (e.g. recipe build) you may safely ignore the `processor` and
>> + `virtualized` parameters.
>> +
>> + The SELECT query to be returned needs to select the following data
>> +
>> + 1 - BuildQueue.job
>> + 2 - BuildQueue.lastscore
>> + 3...

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (4.3 KiB)

On Thu, Dec 24, 2009 at 8:16 PM, Muharem Hrnjadovic <email address hidden> wrote:
> Jonathan Lange wrote:
>> On Thu, Dec 24, 2009 at 3:45 AM, Muharem Hrnjadovic
>> <email address hidden> wrote:
>>> Muharem Hrnjadovic has proposed merging lp:~al-maisan/launchpad/pending-jobs-499861 into lp:launchpad/devel.
>>>
>>>    Requested reviews:
>>>    Canonical Launchpad Engineering (launchpad)
>>> Related bugs:
>>>  #499861 Define interface allowing build farm job classes to select pending jobs
>>>  https://bugs.launchpad.net/bugs/499861
>>>
>>>
>>> Hi there!
>>>
>>> this branch
>>>
>>>    - introduces a method (IBuildFarmJob.pendingJobsQuery()) that all
>>>      Soyuz build farm jobs need to implement in order to enable a
>>>      general (i.e. across all job types) job dispatch time estimation.
>>
>> Although this sounds like a good idea to me, I'm confused by the
>> terminology. Why do we ask a job (singular) to return a list of
>> pending jobs (plural)? It seems like this method would be better
>> placed on an interface representing a type of job.
>
> Hmm .. good point .. can you point to an example?
>
> The original idea was that these methods be implemented as static
> methods. I can stress that point in the doc string if desired.
>

I don't know examples off the top of my head, but I reckon if you grep
for classProvides, you'll find some fairly quickly. The key point is
that static methods are a different interface.

>>> +        :param virtualized: the job of interest (JOI) can only run
>>> +            on builders with this virtualization setting.
>>
>> If I understand correctly, when this is passed in, it means that we're
>> telling the job that we intend to run it with a particular
>> virtualization setting. The way it's phrased in the docstring as-is
>> seems a bit backwards, but I can't think of how to phrase it better.
>> Can you?
> I did try :) see how you like the new doc string.
>

It's great, thanks.

>>> +            Again, this information can be used to narrow down the
>>> +            pending jobs that will result from the returned query and
>>> +            processor independent job types may safely ignore it.
>>> +        :return: a string SELECT clause that can be used to find
>>> +            the pending jobs of the appropriate type.
>>
>> Here's my big question: why a string?
>>
>> Why not return a Storm ResultSet or a Select object?
> Good question, I tried both a Storm ResultSet and a Select object (took
> me a day at least).
>
> Ultimately it did not work because
>
>  - once other job types are added to the build farm I will need to
>    UNION their queries and then *order* the UNIONed result set
>  - the job types that do not care about processor or virtualization
>    will return None/NULL "values" for these columns in their result
>    sets
>  - neither the storm `Union` facility nor the ResultSet.union() method
>    would support ordering of result sets that have *literal* values.
>
> I did also ask on the #storm IRC channel but did not get any answers
> that seemed to fit my problem.
>

Hmm. In that case, keep what you've got.

It might be worth asking on the storm mailing list for interests sake,
or future work.

>>...

Read more...

Revision history for this message
Jonathan Lange (jml) :
review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

This is that rubber stamp that jml mentioned.

We went through a few more small things in IRC: getPendingJobsQuery is now composePendingJobsQuery. Some docstrings and comments were polished up a bit more. And we now have a test verifying that BuildPackageJob really provides IBuildFarmJobDispatchEstimation.

Looks fine. Land that baby!

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-01 08:43:43 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-24 15:02:13 +0000
@@ -9,11 +9,16 @@
99
10__all__ = [10__all__ = [
11 'IBuildFarmJob',11 'IBuildFarmJob',
12 'IBuildFarmJobDispatchEstimation',
12 'BuildFarmJobType',13 'BuildFarmJobType',
13 ]14 ]
1415
15from zope.interface import Interface16from zope.interface import Interface, Attribute
17
18from canonical.launchpad import _
16from lazr.enum import DBEnumeratedType, DBItem19from lazr.enum import DBEnumeratedType, DBItem
20from lazr.restful.fields import Reference
21from lp.soyuz.interfaces.processor import IProcessor
1722
1823
19class BuildFarmJobType(DBEnumeratedType):24class BuildFarmJobType(DBEnumeratedType):
@@ -69,3 +74,63 @@
69 def jobAborted():74 def jobAborted():
70 """'Job aborted' life cycle event, handle as appropriate."""75 """'Job aborted' life cycle event, handle as appropriate."""
7176
77 processor = Reference(
78 IProcessor, title=_("Processor"),
79 description=_(
80 "The Processor required by this build farm job. "
81 "For processor-independent job types please return None."))
82
83 virtualized = Attribute(
84 _(
85 "The virtualization setting required by this build farm job. "
86 "For job types that do not care about virtualization please "
87 "return None."))
88
89
90class IBuildFarmJobDispatchEstimation(Interface):
91 """Operations needed for job dipatch time estimation."""
92
93 def composePendingJobsQuery(min_score, processor, virtualized):
94 """String SELECT query yielding pending jobs with given minimum score.
95
96 This will be used for the purpose of job dispatch time estimation
97 for a build job of interest (JOI).
98 In order to estimate the dispatch time for the JOI we need to
99 calculate the sum of the estimated durations of the *pending* jobs
100 ahead of JOI.
101
102 Depending on the build farm job type the JOI may or may not be tied
103 to a particular processor type.
104 Binary builds for example are always built for a specific processor
105 whereas "create a source package from recipe" type jobs do not care
106 about processor types or virtualization.
107
108 When implementing this method for processor independent build farm job
109 types (e.g. recipe build) you may safely ignore the `processor` and
110 `virtualized` parameters.
111
112 The SELECT query to be returned needs to select the following data
113
114 1 - BuildQueue.job
115 2 - BuildQueue.lastscore
116 3 - BuildQueue.estimated_duration
117 4 - Processor.id [optional]
118 5 - virtualized [optional]
119
120 Please do *not* order the result set since it will be UNIONed and
121 ordered only then.
122
123 Job types that are processor independent or do not care about
124 virtualization should return NULL for the optional data in the result
125 set.
126
127 :param min_score: the pending jobs selected by the returned
128 query should have score >= min_score.
129 :param processor: the type of processor that the jobs are expected
130 to run on.
131 :param virtualized: whether the jobs are expected to run on the
132 `processor` natively or inside a virtual machine.
133 :return: a string SELECT clause that can be used to find
134 the pending jobs of the appropriate type.
135 """
136
72137
=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
--- lib/lp/buildmaster/model/buildfarmjob.py 2009-11-13 20:39:36 +0000
+++ lib/lp/buildmaster/model/buildfarmjob.py 2009-12-24 15:02:13 +0000
@@ -38,3 +38,13 @@
38 """See `IBuildFarmJob`."""38 """See `IBuildFarmJob`."""
39 pass39 pass
4040
41 @property
42 def processor(self):
43 """See `IBuildFarmJob`."""
44 return None
45
46 @property
47 def virtualized(self):
48 """See `IBuildFarmJob`."""
49 return None
50
4151
=== modified file 'lib/lp/soyuz/model/buildpackagejob.py'
--- lib/lp/soyuz/model/buildpackagejob.py 2009-11-13 20:39:36 +0000
+++ lib/lp/soyuz/model/buildpackagejob.py 2009-12-24 15:02:13 +0000
@@ -10,12 +10,16 @@
1010
11from storm.locals import Int, Reference, Storm11from storm.locals import Int, Reference, Storm
1212
13from zope.interface import implements13from zope.interface import classProvides, implements
1414
15from canonical.database.constants import UTC_NOW15from canonical.database.constants import UTC_NOW
16from canonical.launchpad.interfaces import SourcePackageUrgency16from canonical.database.sqlbase import sqlvalues
17from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob17
18from lp.buildmaster.interfaces.buildfarmjob import (
19 BuildFarmJobType, IBuildFarmJob, IBuildFarmJobDispatchEstimation)
20from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
18from lp.registry.interfaces.pocket import PackagePublishingPocket21from lp.registry.interfaces.pocket import PackagePublishingPocket
22from lp.services.job.interfaces.job import JobStatus
19from lp.soyuz.interfaces.archive import ArchivePurpose23from lp.soyuz.interfaces.archive import ArchivePurpose
20from lp.soyuz.interfaces.build import BuildStatus24from lp.soyuz.interfaces.build import BuildStatus
21from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob25from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob
@@ -24,6 +28,8 @@
24class BuildPackageJob(Storm):28class BuildPackageJob(Storm):
25 """See `IBuildPackageJob`."""29 """See `IBuildPackageJob`."""
26 implements(IBuildFarmJob, IBuildPackageJob)30 implements(IBuildFarmJob, IBuildPackageJob)
31 classProvides(IBuildFarmJobDispatchEstimation)
32
27 __storm_table__ = 'buildpackagejob'33 __storm_table__ = 'buildpackagejob'
28 id = Int(primary=True)34 id = Int(primary=True)
2935
@@ -166,3 +172,41 @@
166 # fix it.172 # fix it.
167 self.build.buildstate = BuildStatus.BUILDING173 self.build.buildstate = BuildStatus.BUILDING
168174
175 @staticmethod
176 def composePendingJobsQuery(min_score, processor, virtualized):
177 """See `IBuildFarmJob`."""
178 return """
179 SELECT
180 BuildQueue.job,
181 BuildQueue.lastscore,
182 BuildQueue.estimated_duration,
183 Build.processor AS processor,
184 Archive.require_virtualized AS virtualized
185 FROM
186 BuildQueue, Build, BuildPackageJob, Archive, Job
187 WHERE
188 BuildQueue.job_type = %s
189 AND BuildPackageJob.job = BuildQueue.job
190 AND BuildPackageJob.job = Job.id
191 AND Job.status = %s
192 AND BuildPackageJob.build = Build.id
193 AND Build.buildstate = %s
194 AND Build.archive = Archive.id
195 AND Archive.enabled = TRUE
196 AND BuildQueue.lastscore >= %s
197 AND Build.processor = %s
198 AND Archive.require_virtualized = %s
199 """ % sqlvalues(
200 BuildFarmJobType.PACKAGEBUILD, JobStatus.WAITING,
201 BuildStatus.NEEDSBUILD, min_score, processor, virtualized)
202
203 @property
204 def processor(self):
205 """See `IBuildFarmJob`."""
206 return self.build.processor
207
208 @property
209 def virtualized(self):
210 """See `IBuildFarmJob`."""
211 return self.build.is_virtualized
212
169213
=== added file 'lib/lp/soyuz/tests/test_buildpackagejob.py'
--- lib/lp/soyuz/tests/test_buildpackagejob.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/tests/test_buildpackagejob.py 2009-12-24 15:02:13 +0000
@@ -0,0 +1,384 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test BuildQueue features."""
5
6from datetime import timedelta
7
8from zope.component import getUtility
9
10from canonical.launchpad.webapp.interfaces import (
11 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
12from canonical.launchpad.webapp.testing import verifyObject
13from canonical.testing import LaunchpadZopelessLayer
14
15from lp.buildmaster.interfaces.buildfarmjob import (
16 IBuildFarmJobDispatchEstimation)
17from lp.soyuz.interfaces.archive import ArchivePurpose
18from lp.soyuz.interfaces.build import BuildStatus
19from lp.soyuz.interfaces.builder import IBuilderSet
20from lp.soyuz.interfaces.publishing import PackagePublishingStatus
21from lp.soyuz.model.build import Build
22from lp.soyuz.model.buildpackagejob import BuildPackageJob
23from lp.soyuz.model.processor import ProcessorFamilySet
24from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
25from lp.testing import TestCaseWithFactory
26
27def find_job(test, name, processor='386'):
28 """Find build and queue instance for the given source and processor."""
29 for build in test.builds:
30 if (build.sourcepackagerelease.name == name
31 and build.processor.name == processor):
32 return (build, build.buildqueue_record)
33 return (None, None)
34
35
36def builder_key(build):
37 """Return processor and virtualization for the given build."""
38 return (build.processor.id, build.is_virtualized)
39
40
41def assign_to_builder(test, job_name, builder_number, processor='386'):
42 """Simulate assigning a build to a builder."""
43 def nth_builder(test, build, n):
44 """Get builder #n for the given build processor and virtualization."""
45 builder = None
46 builders = test.builders.get(builder_key(build), [])
47 try:
48 builder = builders[n-1]
49 except IndexError:
50 pass
51 return builder
52
53 build, bq = find_job(test, job_name, processor)
54 builder = nth_builder(test, build, builder_number)
55 bq.markAsBuilding(builder)
56
57
58class TestBuildJobBase(TestCaseWithFactory):
59 """Setup the test publisher and some builders."""
60
61 layer = LaunchpadZopelessLayer
62
63 def setUp(self):
64 super(TestBuildJobBase, self).setUp()
65 self.publisher = SoyuzTestPublisher()
66 self.publisher.prepareBreezyAutotest()
67
68 self.i8 = self.factory.makeBuilder(name='i386-n-8', virtualized=False)
69 self.i9 = self.factory.makeBuilder(name='i386-n-9', virtualized=False)
70
71 processor_fam = ProcessorFamilySet().getByName('hppa')
72 proc = processor_fam.processors[0]
73 self.h6 = self.factory.makeBuilder(
74 name='hppa-n-6', processor=proc, virtualized=False)
75 self.h7 = self.factory.makeBuilder(
76 name='hppa-n-7', processor=proc, virtualized=False)
77
78 self.builders = dict()
79 # x86 native
80 self.builders[(1, False)] = [self.i8, self.i9]
81
82 # hppa native
83 self.builders[(3, True)] = [self.h6, self.h7]
84
85 # Ensure all builders are operational.
86 for builders in self.builders.values():
87 for builder in builders:
88 builder.builderok = True
89 builder.manual = False
90
91 # Disable the sample data builders.
92 getUtility(IBuilderSet)['bob'].builderok = False
93 getUtility(IBuilderSet)['frog'].builderok = False
94
95
96class TestBuildPackageJob(TestBuildJobBase):
97 """Test dispatch time estimates for binary builds (i.e. single build
98 farm job type) targetting a single processor architecture and the primary
99 archive.
100 """
101 def setUp(self):
102 """Set up some native x86 builds for the test archive."""
103 super(TestBuildPackageJob, self).setUp()
104 # The builds will be set up as follows:
105 #
106 # j: 3 gedit p: hppa v:False e:0:01:00 *** s: 1001
107 # j: 4 gedit p: 386 v:False e:0:02:00 *** s: 1002
108 # j: 5 firefox p: hppa v:False e:0:03:00 *** s: 1003
109 # j: 6 firefox p: 386 v:False e:0:04:00 *** s: 1004
110 # j: 7 cobblers p: hppa v:False e:0:05:00 *** s: 1005
111 # j: 8 cobblers p: 386 v:False e:0:06:00 *** s: 1006
112 # j: 9 thunderpants p: hppa v:False e:0:07:00 *** s: 1007
113 # j:10 thunderpants p: 386 v:False e:0:08:00 *** s: 1008
114 # j:11 apg p: hppa v:False e:0:09:00 *** s: 1009
115 # j:12 apg p: 386 v:False e:0:10:00 *** s: 1010
116 # j:13 vim p: hppa v:False e:0:11:00 *** s: 1011
117 # j:14 vim p: 386 v:False e:0:12:00 *** s: 1012
118 # j:15 gcc p: hppa v:False e:0:13:00 *** s: 1013
119 # j:16 gcc p: 386 v:False e:0:14:00 *** s: 1014
120 # j:17 bison p: hppa v:False e:0:15:00 *** s: 1015
121 # j:18 bison p: 386 v:False e:0:16:00 *** s: 1016
122 # j:19 flex p: hppa v:False e:0:17:00 *** s: 1017
123 # j:20 flex p: 386 v:False e:0:18:00 *** s: 1018
124 # j:21 postgres p: hppa v:False e:0:19:00 *** s: 1019
125 # j:22 postgres p: 386 v:False e:0:20:00 *** s: 1020
126 #
127 # j=job, p=processor, v=virtualized, e=estimated_duration, s=score
128
129 # First mark all builds in the sample data as already built.
130 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
131 sample_data = store.find(Build)
132 for build in sample_data:
133 build.buildstate = BuildStatus.FULLYBUILT
134 store.flush()
135
136 # We test builds that target a primary archive.
137 self.non_ppa = self.factory.makeArchive(
138 name="primary", purpose=ArchivePurpose.PRIMARY)
139 self.non_ppa.require_virtualized = False
140
141 self.builds = []
142 self.builds.extend(
143 self.publisher.getPubSource(
144 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
145 archive=self.non_ppa,
146 architecturehintlist='any').createMissingBuilds())
147 self.builds.extend(
148 self.publisher.getPubSource(
149 sourcename="firefox",
150 status=PackagePublishingStatus.PUBLISHED,
151 archive=self.non_ppa,
152 architecturehintlist='any').createMissingBuilds())
153 self.builds.extend(
154 self.publisher.getPubSource(
155 sourcename="cobblers",
156 status=PackagePublishingStatus.PUBLISHED,
157 archive=self.non_ppa,
158 architecturehintlist='any').createMissingBuilds())
159 self.builds.extend(
160 self.publisher.getPubSource(
161 sourcename="thunderpants",
162 status=PackagePublishingStatus.PUBLISHED,
163 archive=self.non_ppa,
164 architecturehintlist='any').createMissingBuilds())
165 self.builds.extend(
166 self.publisher.getPubSource(
167 sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
168 archive=self.non_ppa,
169 architecturehintlist='any').createMissingBuilds())
170 self.builds.extend(
171 self.publisher.getPubSource(
172 sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
173 archive=self.non_ppa,
174 architecturehintlist='any').createMissingBuilds())
175 self.builds.extend(
176 self.publisher.getPubSource(
177 sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
178 archive=self.non_ppa,
179 architecturehintlist='any').createMissingBuilds())
180 self.builds.extend(
181 self.publisher.getPubSource(
182 sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
183 archive=self.non_ppa,
184 architecturehintlist='any').createMissingBuilds())
185 self.builds.extend(
186 self.publisher.getPubSource(
187 sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
188 archive=self.non_ppa,
189 architecturehintlist='any').createMissingBuilds())
190 self.builds.extend(
191 self.publisher.getPubSource(
192 sourcename="postgres",
193 status=PackagePublishingStatus.PUBLISHED,
194 archive=self.non_ppa,
195 architecturehintlist='any').createMissingBuilds())
196 # We want the builds to have a lot of variety when it comes to score
197 # and estimated duration etc. so that the queries under test get
198 # exercised properly.
199 score = 1000
200 duration = 0
201 for build in self.builds:
202 score += 1
203 duration += 60
204 bq = build.buildqueue_record
205 bq.lastscore = score
206 bq.estimated_duration = timedelta(seconds=duration)
207
208 def test_x86_pending_queries(self):
209 # Select x86 jobs with equal or better score.
210 #
211 # The x86 builds are as follows:
212 #
213 # j: 4 gedit p: 386 v:False e:0:02:00 *** s: 1002
214 # j: 6 firefox p: 386 v:False e:0:04:00 *** s: 1004
215 # j: 8 cobblers p: 386 v:False e:0:06:00 *** s: 1006
216 # j:10 thunderpants p: 386 v:False e:0:08:00 *** s: 1008
217 # j:12 apg p: 386 v:False e:0:10:00 *** s: 1010
218 # j:14 vim p: 386 v:False e:0:12:00 *** s: 1012
219 # j:16 gcc p: 386 v:False e:0:14:00 *** s: 1014
220 # j:18 bison p: 386 v:False e:0:16:00 *** s: 1016
221 # j:20 flex p: 386 v:False e:0:18:00 *** s: 1018
222 # j:22 postgres p: 386 v:False e:0:20:00 *** s: 1020
223 #
224 # Please note: the higher scored jobs are lower in the list.
225 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
226
227 build, bq = find_job(self, 'apg')
228 bpj = bq.specific_job
229 query = bpj.composePendingJobsQuery(1010, *builder_key(build))
230 result_set = store.execute(query).get_all()
231 # The pending x86 jobs with score 1010 or higher are as follows.
232 # Please note that we do not require the results to be in any
233 # particular order.
234
235 # Processor == 1 -> Intel 386
236 # SELECT id,name,title FROM processor
237 # id | name | title
238 # ----+-------+----------------
239 # 1 | 386 | Intel 386
240 # 2 | amd64 | AMD 64bit
241 # 3 | hppa | HPPA Processor
242
243 expected_results = [
244 # job score estimated_duration processor virtualized
245 (12, 1010, timedelta(0, 600), 1, False),
246 (14, 1012, timedelta(0, 720), 1, False),
247 (16, 1014, timedelta(0, 840), 1, False),
248 (18, 1016, timedelta(0, 960), 1, False),
249 (20, 1018, timedelta(0, 1080), 1, False),
250 (22, 1020, timedelta(0, 1200), 1, False)]
251 self.assertEqual(sorted(result_set), sorted(expected_results))
252 # How about builds with lower scores? Please note also that no
253 # hppa builds are listed.
254 query = bpj.composePendingJobsQuery(0, *builder_key(build))
255 result_set = store.execute(query).get_all()
256 expected_results = [
257 # job score estimated_duration processor virtualized
258 (22, 1020, timedelta(0, 1200), 1, False),
259 (20, 1018, timedelta(0, 1080), 1, False),
260 (18, 1016, timedelta(0, 960), 1, False),
261 (16, 1014, timedelta(0, 840), 1, False),
262 (14, 1012, timedelta(0, 720), 1, False),
263 (12, 1010, timedelta(0, 600), 1, False),
264 (10, 1008, timedelta(0, 480), 1, False),
265 (8, 1006, timedelta(0, 360), 1, False),
266 (6, 1004, timedelta(0, 240), 1, False),
267 (4, 1002, timedelta(0, 120), 1, False)]
268 self.assertEqual(sorted(result_set), sorted(expected_results))
269 # How about builds with higher scores?
270 query = bpj.composePendingJobsQuery(2500, *builder_key(build))
271 result_set = store.execute(query).get_all()
272 expected_results = []
273 self.assertEqual(sorted(result_set), sorted(expected_results))
274
275 # We will start the 'flex' job now and see whether it still turns
276 # up in our pending job list.
277 assign_to_builder(self, 'flex', 1)
278 query = bpj.composePendingJobsQuery(1016, *builder_key(build))
279 result_set = store.execute(query).get_all()
280 expected_results = [
281 # job score estimated_duration processor virtualized
282 (22, 1020, timedelta(0, 1200), 1, False),
283 (18, 1016, timedelta(0, 960), 1, False)]
284 self.assertEqual(sorted(result_set), sorted(expected_results))
285 # As we can see it was absent as expected.
286
287 def test_hppa_pending_queries(self):
288 # Select hppa jobs with equal or better score.
289 #
290 # The hppa builds are as follows:
291 #
292 # j: 3 gedit p: hppa v:False e:0:01:00 *** s: 1001
293 # j: 5 firefox p: hppa v:False e:0:03:00 *** s: 1003
294 # j: 7 cobblers p: hppa v:False e:0:05:00 *** s: 1005
295 # j: 9 thunderpants p: hppa v:False e:0:07:00 *** s: 1007
296 # j:11 apg p: hppa v:False e:0:09:00 *** s: 1009
297 # j:13 vim p: hppa v:False e:0:11:00 *** s: 1011
298 # j:15 gcc p: hppa v:False e:0:13:00 *** s: 1013
299 # j:17 bison p: hppa v:False e:0:15:00 *** s: 1015
300 # j:19 flex p: hppa v:False e:0:17:00 *** s: 1017
301 # j:21 postgres p: hppa v:False e:0:19:00 *** s: 1019
302 #
303 # Please note: the higher scored jobs are lower in the list.
304 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
305
306 build, bq = find_job(self, 'vim', 'hppa')
307 bpj = bq.specific_job
308 query = bpj.composePendingJobsQuery(1011, *builder_key(build))
309 result_set = store.execute(query).get_all()
310 # The pending hppa jobs with score 1011 or higher are as follows.
311 # Please note that we do not require the results to be in any
312 # particular order.
313
314 # Processor == 3 -> HPPA
315 # SELECT id,name,title FROM processor
316 # id | name | title
317 # ----+-------+----------------
318 # 1 | 386 | Intel 386
319 # 2 | amd64 | AMD 64bit
320 # 3 | hppa | HPPA Processor
321
322 expected_results = [
323 # job score estimated_duration processor virtualized
324 (13, 1011, timedelta(0, 660), 3, False),
325 (15, 1013, timedelta(0, 780), 3, False),
326 (17, 1015, timedelta(0, 900), 3, False),
327 (19, 1017, timedelta(0, 1020), 3, False),
328 (21, 1019, timedelta(0, 1140), 3, False)]
329 self.assertEqual(sorted(result_set), sorted(expected_results))
330 # How about builds with lower scores? Please note also that no
331 # hppa builds are listed.
332 query = bpj.composePendingJobsQuery(0, *builder_key(build))
333 result_set = store.execute(query).get_all()
334 expected_results = [
335 # job score estimated_duration processor virtualized
336 (3, 1001, timedelta(0, 60), 3, False),
337 (5, 1003, timedelta(0, 180), 3, False),
338 (7, 1005, timedelta(0, 300), 3, False),
339 (9, 1007, timedelta(0, 420), 3, False),
340 (11, 1009, timedelta(0, 540), 3, False),
341 (13, 1011, timedelta(0, 660), 3, False),
342 (15, 1013, timedelta(0, 780), 3, False),
343 (17, 1015, timedelta(0, 900), 3, False),
344 (19, 1017, timedelta(0, 1020), 3, False),
345 (21, 1019, timedelta(0, 1140), 3, False)]
346 self.assertEqual(sorted(result_set), sorted(expected_results))
347 # How about builds with higher scores?
348 query = bpj.composePendingJobsQuery(2500, *builder_key(build))
349 result_set = store.execute(query).get_all()
350 expected_results = []
351 self.assertEqual(sorted(result_set), sorted(expected_results))
352
353 # We will start the 'flex' job now and see whether it still turns
354 # up in our pending job list.
355 assign_to_builder(self, 'flex', 1, 'hppa')
356 query = bpj.composePendingJobsQuery(1014, *builder_key(build))
357 result_set = store.execute(query).get_all()
358 expected_results = [
359 # job score estimated_duration processor virtualized
360 (17, 1015, timedelta(0, 900), 3, False),
361 (21, 1019, timedelta(0, 1140), 3, False)]
362 self.assertEqual(sorted(result_set), sorted(expected_results))
363 # As we can see it was absent as expected.
364
365 def test_processor(self):
366 # Test that BuildPackageJob returns the correct processor.
367 build, bq = find_job(self, 'gcc', '386')
368 bpj = bq.specific_job
369 self.assertEqual(bpj.processor.id, 1)
370 build, bq = find_job(self, 'bison', 'hppa')
371 bpj = bq.specific_job
372 self.assertEqual(bpj.processor.id, 3)
373
374 def test_virtualized(self):
375 # Test that BuildPackageJob returns the correct virtualized flag.
376 build, bq = find_job(self, 'apg', '386')
377 bpj = bq.specific_job
378 self.assertEqual(bpj.virtualized, False)
379 build, bq = find_job(self, 'flex', 'hppa')
380 bpj = bq.specific_job
381 self.assertEqual(bpj.virtualized, False)
382
383 def test_provides_dispatch_estimation_interface(self):
384 verifyObject(IBuildFarmJobDispatchEstimation, BuildPackageJob)