Merge lp://staging/~al-maisan/launchpad/pending-jobs-499861 into lp://staging/launchpad
- pending-jobs-499861
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp://staging/~al-maisan/launchpad/pending-jobs-499861 |
Merge into: | lp://staging/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://staging/~al-maisan/launchpad/pending-jobs-499861 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | code | Approve | |
Jonathan Lange (community) | Approve | ||
Review via email: mp+16550@code.staging.launchpad.net |
Commit message
Description of the change
Muharem Hrnjadovic (al-maisan) wrote : | # |
Jonathan Lange (jml) 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:/
>
>
> Hi there!
>
> this branch
>
> - introduces a method (IBuildFarmJob.
> 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/
> --- lib/lp/
> +++ lib/lp/
> @@ -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.
>
>
> class BuildFarmJobTyp
> @@ -69,3 +73,64 @@
> def jobAborted():
> """'Job aborted' life cycle event, handle as appropriate."""
>
> + def pendingJobsQuer
Given that this is a method, it's name should be in the imperative,
e.g. getPendingJobsQ
> + """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.
> + 3 - BuildQueue.
> + 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...
Jonathan Lange (jml) wrote : | # |
As per my email, there are still some things to fix up.
Muharem Hrnjadovic (al-maisan) 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:/
>>
>>
>> Hi there!
>>
>> this branch
>>
>> - introduces a method (IBuildFarmJob.
>> 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/
>> --- lib/lp/
>> +++ lib/lp/
>> @@ -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.
>>
>>
>> class BuildFarmJobTyp
>> @@ -69,3 +73,64 @@
>> def jobAborted():
>> """'Job aborted' life cycle event, handle as appropriate."""
>>
>> + def pendingJobsQuer
>
> Given that this is a method, it's name should be in the imperative,
> e.g. getPendingJobsQ
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.
>> + 3...
Jonathan Lange (jml) wrote : | # |
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:/
>>>
>>>
>>> Hi there!
>>>
>>> this branch
>>>
>>> - introduces a method (IBuildFarmJob.
>>> 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.
>>...
Jonathan Lange (jml) : | # |
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 composePendingJ
Looks fine. Land that baby!
Preview Diff
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) |
Hi there!
this branch
- introduces a method (IBuildFarmJob. pendingJobsQuer y()) 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.