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
1=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
2--- lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-01 08:43:43 +0000
3+++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2009-12-24 15:02:13 +0000
4@@ -9,11 +9,16 @@
5
6 __all__ = [
7 'IBuildFarmJob',
8+ 'IBuildFarmJobDispatchEstimation',
9 'BuildFarmJobType',
10 ]
11
12-from zope.interface import Interface
13+from zope.interface import Interface, Attribute
14+
15+from canonical.launchpad import _
16 from lazr.enum import DBEnumeratedType, DBItem
17+from lazr.restful.fields import Reference
18+from lp.soyuz.interfaces.processor import IProcessor
19
20
21 class BuildFarmJobType(DBEnumeratedType):
22@@ -69,3 +74,63 @@
23 def jobAborted():
24 """'Job aborted' life cycle event, handle as appropriate."""
25
26+ processor = Reference(
27+ IProcessor, title=_("Processor"),
28+ description=_(
29+ "The Processor required by this build farm job. "
30+ "For processor-independent job types please return None."))
31+
32+ virtualized = Attribute(
33+ _(
34+ "The virtualization setting required by this build farm job. "
35+ "For job types that do not care about virtualization please "
36+ "return None."))
37+
38+
39+class IBuildFarmJobDispatchEstimation(Interface):
40+ """Operations needed for job dipatch time estimation."""
41+
42+ def composePendingJobsQuery(min_score, processor, virtualized):
43+ """String SELECT query yielding pending jobs with given minimum score.
44+
45+ This will be used for the purpose of job dispatch time estimation
46+ for a build job of interest (JOI).
47+ In order to estimate the dispatch time for the JOI we need to
48+ calculate the sum of the estimated durations of the *pending* jobs
49+ ahead of JOI.
50+
51+ Depending on the build farm job type the JOI may or may not be tied
52+ to a particular processor type.
53+ Binary builds for example are always built for a specific processor
54+ whereas "create a source package from recipe" type jobs do not care
55+ about processor types or virtualization.
56+
57+ When implementing this method for processor independent build farm job
58+ types (e.g. recipe build) you may safely ignore the `processor` and
59+ `virtualized` parameters.
60+
61+ The SELECT query to be returned needs to select the following data
62+
63+ 1 - BuildQueue.job
64+ 2 - BuildQueue.lastscore
65+ 3 - BuildQueue.estimated_duration
66+ 4 - Processor.id [optional]
67+ 5 - virtualized [optional]
68+
69+ Please do *not* order the result set since it will be UNIONed and
70+ ordered only then.
71+
72+ Job types that are processor independent or do not care about
73+ virtualization should return NULL for the optional data in the result
74+ set.
75+
76+ :param min_score: the pending jobs selected by the returned
77+ query should have score >= min_score.
78+ :param processor: the type of processor that the jobs are expected
79+ to run on.
80+ :param virtualized: whether the jobs are expected to run on the
81+ `processor` natively or inside a virtual machine.
82+ :return: a string SELECT clause that can be used to find
83+ the pending jobs of the appropriate type.
84+ """
85+
86
87=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
88--- lib/lp/buildmaster/model/buildfarmjob.py 2009-11-13 20:39:36 +0000
89+++ lib/lp/buildmaster/model/buildfarmjob.py 2009-12-24 15:02:13 +0000
90@@ -38,3 +38,13 @@
91 """See `IBuildFarmJob`."""
92 pass
93
94+ @property
95+ def processor(self):
96+ """See `IBuildFarmJob`."""
97+ return None
98+
99+ @property
100+ def virtualized(self):
101+ """See `IBuildFarmJob`."""
102+ return None
103+
104
105=== modified file 'lib/lp/soyuz/model/buildpackagejob.py'
106--- lib/lp/soyuz/model/buildpackagejob.py 2009-11-13 20:39:36 +0000
107+++ lib/lp/soyuz/model/buildpackagejob.py 2009-12-24 15:02:13 +0000
108@@ -10,12 +10,16 @@
109
110 from storm.locals import Int, Reference, Storm
111
112-from zope.interface import implements
113+from zope.interface import classProvides, implements
114
115 from canonical.database.constants import UTC_NOW
116-from canonical.launchpad.interfaces import SourcePackageUrgency
117-from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
118+from canonical.database.sqlbase import sqlvalues
119+
120+from lp.buildmaster.interfaces.buildfarmjob import (
121+ BuildFarmJobType, IBuildFarmJob, IBuildFarmJobDispatchEstimation)
122+from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
123 from lp.registry.interfaces.pocket import PackagePublishingPocket
124+from lp.services.job.interfaces.job import JobStatus
125 from lp.soyuz.interfaces.archive import ArchivePurpose
126 from lp.soyuz.interfaces.build import BuildStatus
127 from lp.soyuz.interfaces.buildpackagejob import IBuildPackageJob
128@@ -24,6 +28,8 @@
129 class BuildPackageJob(Storm):
130 """See `IBuildPackageJob`."""
131 implements(IBuildFarmJob, IBuildPackageJob)
132+ classProvides(IBuildFarmJobDispatchEstimation)
133+
134 __storm_table__ = 'buildpackagejob'
135 id = Int(primary=True)
136
137@@ -166,3 +172,41 @@
138 # fix it.
139 self.build.buildstate = BuildStatus.BUILDING
140
141+ @staticmethod
142+ def composePendingJobsQuery(min_score, processor, virtualized):
143+ """See `IBuildFarmJob`."""
144+ return """
145+ SELECT
146+ BuildQueue.job,
147+ BuildQueue.lastscore,
148+ BuildQueue.estimated_duration,
149+ Build.processor AS processor,
150+ Archive.require_virtualized AS virtualized
151+ FROM
152+ BuildQueue, Build, BuildPackageJob, Archive, Job
153+ WHERE
154+ BuildQueue.job_type = %s
155+ AND BuildPackageJob.job = BuildQueue.job
156+ AND BuildPackageJob.job = Job.id
157+ AND Job.status = %s
158+ AND BuildPackageJob.build = Build.id
159+ AND Build.buildstate = %s
160+ AND Build.archive = Archive.id
161+ AND Archive.enabled = TRUE
162+ AND BuildQueue.lastscore >= %s
163+ AND Build.processor = %s
164+ AND Archive.require_virtualized = %s
165+ """ % sqlvalues(
166+ BuildFarmJobType.PACKAGEBUILD, JobStatus.WAITING,
167+ BuildStatus.NEEDSBUILD, min_score, processor, virtualized)
168+
169+ @property
170+ def processor(self):
171+ """See `IBuildFarmJob`."""
172+ return self.build.processor
173+
174+ @property
175+ def virtualized(self):
176+ """See `IBuildFarmJob`."""
177+ return self.build.is_virtualized
178+
179
180=== added file 'lib/lp/soyuz/tests/test_buildpackagejob.py'
181--- lib/lp/soyuz/tests/test_buildpackagejob.py 1970-01-01 00:00:00 +0000
182+++ lib/lp/soyuz/tests/test_buildpackagejob.py 2009-12-24 15:02:13 +0000
183@@ -0,0 +1,384 @@
184+# Copyright 2009 Canonical Ltd. This software is licensed under the
185+# GNU Affero General Public License version 3 (see the file LICENSE).
186+
187+"""Test BuildQueue features."""
188+
189+from datetime import timedelta
190+
191+from zope.component import getUtility
192+
193+from canonical.launchpad.webapp.interfaces import (
194+ IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
195+from canonical.launchpad.webapp.testing import verifyObject
196+from canonical.testing import LaunchpadZopelessLayer
197+
198+from lp.buildmaster.interfaces.buildfarmjob import (
199+ IBuildFarmJobDispatchEstimation)
200+from lp.soyuz.interfaces.archive import ArchivePurpose
201+from lp.soyuz.interfaces.build import BuildStatus
202+from lp.soyuz.interfaces.builder import IBuilderSet
203+from lp.soyuz.interfaces.publishing import PackagePublishingStatus
204+from lp.soyuz.model.build import Build
205+from lp.soyuz.model.buildpackagejob import BuildPackageJob
206+from lp.soyuz.model.processor import ProcessorFamilySet
207+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
208+from lp.testing import TestCaseWithFactory
209+
210+def find_job(test, name, processor='386'):
211+ """Find build and queue instance for the given source and processor."""
212+ for build in test.builds:
213+ if (build.sourcepackagerelease.name == name
214+ and build.processor.name == processor):
215+ return (build, build.buildqueue_record)
216+ return (None, None)
217+
218+
219+def builder_key(build):
220+ """Return processor and virtualization for the given build."""
221+ return (build.processor.id, build.is_virtualized)
222+
223+
224+def assign_to_builder(test, job_name, builder_number, processor='386'):
225+ """Simulate assigning a build to a builder."""
226+ def nth_builder(test, build, n):
227+ """Get builder #n for the given build processor and virtualization."""
228+ builder = None
229+ builders = test.builders.get(builder_key(build), [])
230+ try:
231+ builder = builders[n-1]
232+ except IndexError:
233+ pass
234+ return builder
235+
236+ build, bq = find_job(test, job_name, processor)
237+ builder = nth_builder(test, build, builder_number)
238+ bq.markAsBuilding(builder)
239+
240+
241+class TestBuildJobBase(TestCaseWithFactory):
242+ """Setup the test publisher and some builders."""
243+
244+ layer = LaunchpadZopelessLayer
245+
246+ def setUp(self):
247+ super(TestBuildJobBase, self).setUp()
248+ self.publisher = SoyuzTestPublisher()
249+ self.publisher.prepareBreezyAutotest()
250+
251+ self.i8 = self.factory.makeBuilder(name='i386-n-8', virtualized=False)
252+ self.i9 = self.factory.makeBuilder(name='i386-n-9', virtualized=False)
253+
254+ processor_fam = ProcessorFamilySet().getByName('hppa')
255+ proc = processor_fam.processors[0]
256+ self.h6 = self.factory.makeBuilder(
257+ name='hppa-n-6', processor=proc, virtualized=False)
258+ self.h7 = self.factory.makeBuilder(
259+ name='hppa-n-7', processor=proc, virtualized=False)
260+
261+ self.builders = dict()
262+ # x86 native
263+ self.builders[(1, False)] = [self.i8, self.i9]
264+
265+ # hppa native
266+ self.builders[(3, True)] = [self.h6, self.h7]
267+
268+ # Ensure all builders are operational.
269+ for builders in self.builders.values():
270+ for builder in builders:
271+ builder.builderok = True
272+ builder.manual = False
273+
274+ # Disable the sample data builders.
275+ getUtility(IBuilderSet)['bob'].builderok = False
276+ getUtility(IBuilderSet)['frog'].builderok = False
277+
278+
279+class TestBuildPackageJob(TestBuildJobBase):
280+ """Test dispatch time estimates for binary builds (i.e. single build
281+ farm job type) targetting a single processor architecture and the primary
282+ archive.
283+ """
284+ def setUp(self):
285+ """Set up some native x86 builds for the test archive."""
286+ super(TestBuildPackageJob, self).setUp()
287+ # The builds will be set up as follows:
288+ #
289+ # j: 3 gedit p: hppa v:False e:0:01:00 *** s: 1001
290+ # j: 4 gedit p: 386 v:False e:0:02:00 *** s: 1002
291+ # j: 5 firefox p: hppa v:False e:0:03:00 *** s: 1003
292+ # j: 6 firefox p: 386 v:False e:0:04:00 *** s: 1004
293+ # j: 7 cobblers p: hppa v:False e:0:05:00 *** s: 1005
294+ # j: 8 cobblers p: 386 v:False e:0:06:00 *** s: 1006
295+ # j: 9 thunderpants p: hppa v:False e:0:07:00 *** s: 1007
296+ # j:10 thunderpants p: 386 v:False e:0:08:00 *** s: 1008
297+ # j:11 apg p: hppa v:False e:0:09:00 *** s: 1009
298+ # j:12 apg p: 386 v:False e:0:10:00 *** s: 1010
299+ # j:13 vim p: hppa v:False e:0:11:00 *** s: 1011
300+ # j:14 vim p: 386 v:False e:0:12:00 *** s: 1012
301+ # j:15 gcc p: hppa v:False e:0:13:00 *** s: 1013
302+ # j:16 gcc p: 386 v:False e:0:14:00 *** s: 1014
303+ # j:17 bison p: hppa v:False e:0:15:00 *** s: 1015
304+ # j:18 bison p: 386 v:False e:0:16:00 *** s: 1016
305+ # j:19 flex p: hppa v:False e:0:17:00 *** s: 1017
306+ # j:20 flex p: 386 v:False e:0:18:00 *** s: 1018
307+ # j:21 postgres p: hppa v:False e:0:19:00 *** s: 1019
308+ # j:22 postgres p: 386 v:False e:0:20:00 *** s: 1020
309+ #
310+ # j=job, p=processor, v=virtualized, e=estimated_duration, s=score
311+
312+ # First mark all builds in the sample data as already built.
313+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
314+ sample_data = store.find(Build)
315+ for build in sample_data:
316+ build.buildstate = BuildStatus.FULLYBUILT
317+ store.flush()
318+
319+ # We test builds that target a primary archive.
320+ self.non_ppa = self.factory.makeArchive(
321+ name="primary", purpose=ArchivePurpose.PRIMARY)
322+ self.non_ppa.require_virtualized = False
323+
324+ self.builds = []
325+ self.builds.extend(
326+ self.publisher.getPubSource(
327+ sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
328+ archive=self.non_ppa,
329+ architecturehintlist='any').createMissingBuilds())
330+ self.builds.extend(
331+ self.publisher.getPubSource(
332+ sourcename="firefox",
333+ status=PackagePublishingStatus.PUBLISHED,
334+ archive=self.non_ppa,
335+ architecturehintlist='any').createMissingBuilds())
336+ self.builds.extend(
337+ self.publisher.getPubSource(
338+ sourcename="cobblers",
339+ status=PackagePublishingStatus.PUBLISHED,
340+ archive=self.non_ppa,
341+ architecturehintlist='any').createMissingBuilds())
342+ self.builds.extend(
343+ self.publisher.getPubSource(
344+ sourcename="thunderpants",
345+ status=PackagePublishingStatus.PUBLISHED,
346+ archive=self.non_ppa,
347+ architecturehintlist='any').createMissingBuilds())
348+ self.builds.extend(
349+ self.publisher.getPubSource(
350+ sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
351+ archive=self.non_ppa,
352+ architecturehintlist='any').createMissingBuilds())
353+ self.builds.extend(
354+ self.publisher.getPubSource(
355+ sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
356+ archive=self.non_ppa,
357+ architecturehintlist='any').createMissingBuilds())
358+ self.builds.extend(
359+ self.publisher.getPubSource(
360+ sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
361+ archive=self.non_ppa,
362+ architecturehintlist='any').createMissingBuilds())
363+ self.builds.extend(
364+ self.publisher.getPubSource(
365+ sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
366+ archive=self.non_ppa,
367+ architecturehintlist='any').createMissingBuilds())
368+ self.builds.extend(
369+ self.publisher.getPubSource(
370+ sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
371+ archive=self.non_ppa,
372+ architecturehintlist='any').createMissingBuilds())
373+ self.builds.extend(
374+ self.publisher.getPubSource(
375+ sourcename="postgres",
376+ status=PackagePublishingStatus.PUBLISHED,
377+ archive=self.non_ppa,
378+ architecturehintlist='any').createMissingBuilds())
379+ # We want the builds to have a lot of variety when it comes to score
380+ # and estimated duration etc. so that the queries under test get
381+ # exercised properly.
382+ score = 1000
383+ duration = 0
384+ for build in self.builds:
385+ score += 1
386+ duration += 60
387+ bq = build.buildqueue_record
388+ bq.lastscore = score
389+ bq.estimated_duration = timedelta(seconds=duration)
390+
391+ def test_x86_pending_queries(self):
392+ # Select x86 jobs with equal or better score.
393+ #
394+ # The x86 builds are as follows:
395+ #
396+ # j: 4 gedit p: 386 v:False e:0:02:00 *** s: 1002
397+ # j: 6 firefox p: 386 v:False e:0:04:00 *** s: 1004
398+ # j: 8 cobblers p: 386 v:False e:0:06:00 *** s: 1006
399+ # j:10 thunderpants p: 386 v:False e:0:08:00 *** s: 1008
400+ # j:12 apg p: 386 v:False e:0:10:00 *** s: 1010
401+ # j:14 vim p: 386 v:False e:0:12:00 *** s: 1012
402+ # j:16 gcc p: 386 v:False e:0:14:00 *** s: 1014
403+ # j:18 bison p: 386 v:False e:0:16:00 *** s: 1016
404+ # j:20 flex p: 386 v:False e:0:18:00 *** s: 1018
405+ # j:22 postgres p: 386 v:False e:0:20:00 *** s: 1020
406+ #
407+ # Please note: the higher scored jobs are lower in the list.
408+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
409+
410+ build, bq = find_job(self, 'apg')
411+ bpj = bq.specific_job
412+ query = bpj.composePendingJobsQuery(1010, *builder_key(build))
413+ result_set = store.execute(query).get_all()
414+ # The pending x86 jobs with score 1010 or higher are as follows.
415+ # Please note that we do not require the results to be in any
416+ # particular order.
417+
418+ # Processor == 1 -> Intel 386
419+ # SELECT id,name,title FROM processor
420+ # id | name | title
421+ # ----+-------+----------------
422+ # 1 | 386 | Intel 386
423+ # 2 | amd64 | AMD 64bit
424+ # 3 | hppa | HPPA Processor
425+
426+ expected_results = [
427+ # job score estimated_duration processor virtualized
428+ (12, 1010, timedelta(0, 600), 1, False),
429+ (14, 1012, timedelta(0, 720), 1, False),
430+ (16, 1014, timedelta(0, 840), 1, False),
431+ (18, 1016, timedelta(0, 960), 1, False),
432+ (20, 1018, timedelta(0, 1080), 1, False),
433+ (22, 1020, timedelta(0, 1200), 1, False)]
434+ self.assertEqual(sorted(result_set), sorted(expected_results))
435+ # How about builds with lower scores? Please note also that no
436+ # hppa builds are listed.
437+ query = bpj.composePendingJobsQuery(0, *builder_key(build))
438+ result_set = store.execute(query).get_all()
439+ expected_results = [
440+ # job score estimated_duration processor virtualized
441+ (22, 1020, timedelta(0, 1200), 1, False),
442+ (20, 1018, timedelta(0, 1080), 1, False),
443+ (18, 1016, timedelta(0, 960), 1, False),
444+ (16, 1014, timedelta(0, 840), 1, False),
445+ (14, 1012, timedelta(0, 720), 1, False),
446+ (12, 1010, timedelta(0, 600), 1, False),
447+ (10, 1008, timedelta(0, 480), 1, False),
448+ (8, 1006, timedelta(0, 360), 1, False),
449+ (6, 1004, timedelta(0, 240), 1, False),
450+ (4, 1002, timedelta(0, 120), 1, False)]
451+ self.assertEqual(sorted(result_set), sorted(expected_results))
452+ # How about builds with higher scores?
453+ query = bpj.composePendingJobsQuery(2500, *builder_key(build))
454+ result_set = store.execute(query).get_all()
455+ expected_results = []
456+ self.assertEqual(sorted(result_set), sorted(expected_results))
457+
458+ # We will start the 'flex' job now and see whether it still turns
459+ # up in our pending job list.
460+ assign_to_builder(self, 'flex', 1)
461+ query = bpj.composePendingJobsQuery(1016, *builder_key(build))
462+ result_set = store.execute(query).get_all()
463+ expected_results = [
464+ # job score estimated_duration processor virtualized
465+ (22, 1020, timedelta(0, 1200), 1, False),
466+ (18, 1016, timedelta(0, 960), 1, False)]
467+ self.assertEqual(sorted(result_set), sorted(expected_results))
468+ # As we can see it was absent as expected.
469+
470+ def test_hppa_pending_queries(self):
471+ # Select hppa jobs with equal or better score.
472+ #
473+ # The hppa builds are as follows:
474+ #
475+ # j: 3 gedit p: hppa v:False e:0:01:00 *** s: 1001
476+ # j: 5 firefox p: hppa v:False e:0:03:00 *** s: 1003
477+ # j: 7 cobblers p: hppa v:False e:0:05:00 *** s: 1005
478+ # j: 9 thunderpants p: hppa v:False e:0:07:00 *** s: 1007
479+ # j:11 apg p: hppa v:False e:0:09:00 *** s: 1009
480+ # j:13 vim p: hppa v:False e:0:11:00 *** s: 1011
481+ # j:15 gcc p: hppa v:False e:0:13:00 *** s: 1013
482+ # j:17 bison p: hppa v:False e:0:15:00 *** s: 1015
483+ # j:19 flex p: hppa v:False e:0:17:00 *** s: 1017
484+ # j:21 postgres p: hppa v:False e:0:19:00 *** s: 1019
485+ #
486+ # Please note: the higher scored jobs are lower in the list.
487+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
488+
489+ build, bq = find_job(self, 'vim', 'hppa')
490+ bpj = bq.specific_job
491+ query = bpj.composePendingJobsQuery(1011, *builder_key(build))
492+ result_set = store.execute(query).get_all()
493+ # The pending hppa jobs with score 1011 or higher are as follows.
494+ # Please note that we do not require the results to be in any
495+ # particular order.
496+
497+ # Processor == 3 -> HPPA
498+ # SELECT id,name,title FROM processor
499+ # id | name | title
500+ # ----+-------+----------------
501+ # 1 | 386 | Intel 386
502+ # 2 | amd64 | AMD 64bit
503+ # 3 | hppa | HPPA Processor
504+
505+ expected_results = [
506+ # job score estimated_duration processor virtualized
507+ (13, 1011, timedelta(0, 660), 3, False),
508+ (15, 1013, timedelta(0, 780), 3, False),
509+ (17, 1015, timedelta(0, 900), 3, False),
510+ (19, 1017, timedelta(0, 1020), 3, False),
511+ (21, 1019, timedelta(0, 1140), 3, False)]
512+ self.assertEqual(sorted(result_set), sorted(expected_results))
513+ # How about builds with lower scores? Please note also that no
514+ # hppa builds are listed.
515+ query = bpj.composePendingJobsQuery(0, *builder_key(build))
516+ result_set = store.execute(query).get_all()
517+ expected_results = [
518+ # job score estimated_duration processor virtualized
519+ (3, 1001, timedelta(0, 60), 3, False),
520+ (5, 1003, timedelta(0, 180), 3, False),
521+ (7, 1005, timedelta(0, 300), 3, False),
522+ (9, 1007, timedelta(0, 420), 3, False),
523+ (11, 1009, timedelta(0, 540), 3, False),
524+ (13, 1011, timedelta(0, 660), 3, False),
525+ (15, 1013, timedelta(0, 780), 3, False),
526+ (17, 1015, timedelta(0, 900), 3, False),
527+ (19, 1017, timedelta(0, 1020), 3, False),
528+ (21, 1019, timedelta(0, 1140), 3, False)]
529+ self.assertEqual(sorted(result_set), sorted(expected_results))
530+ # How about builds with higher scores?
531+ query = bpj.composePendingJobsQuery(2500, *builder_key(build))
532+ result_set = store.execute(query).get_all()
533+ expected_results = []
534+ self.assertEqual(sorted(result_set), sorted(expected_results))
535+
536+ # We will start the 'flex' job now and see whether it still turns
537+ # up in our pending job list.
538+ assign_to_builder(self, 'flex', 1, 'hppa')
539+ query = bpj.composePendingJobsQuery(1014, *builder_key(build))
540+ result_set = store.execute(query).get_all()
541+ expected_results = [
542+ # job score estimated_duration processor virtualized
543+ (17, 1015, timedelta(0, 900), 3, False),
544+ (21, 1019, timedelta(0, 1140), 3, False)]
545+ self.assertEqual(sorted(result_set), sorted(expected_results))
546+ # As we can see it was absent as expected.
547+
548+ def test_processor(self):
549+ # Test that BuildPackageJob returns the correct processor.
550+ build, bq = find_job(self, 'gcc', '386')
551+ bpj = bq.specific_job
552+ self.assertEqual(bpj.processor.id, 1)
553+ build, bq = find_job(self, 'bison', 'hppa')
554+ bpj = bq.specific_job
555+ self.assertEqual(bpj.processor.id, 3)
556+
557+ def test_virtualized(self):
558+ # Test that BuildPackageJob returns the correct virtualized flag.
559+ build, bq = find_job(self, 'apg', '386')
560+ bpj = bq.specific_job
561+ self.assertEqual(bpj.virtualized, False)
562+ build, bq = find_job(self, 'flex', 'hppa')
563+ bpj = bq.specific_job
564+ self.assertEqual(bpj.virtualized, False)
565+
566+ def test_provides_dispatch_estimation_interface(self):
567+ verifyObject(IBuildFarmJobDispatchEstimation, BuildPackageJob)