Merge lp:~al-maisan/launchpad/xx-next-builder-502927 into lp:launchpad

Proposed by Muharem Hrnjadovic
Status: Merged
Merged at revision: not available
Proposed branch: lp:~al-maisan/launchpad/xx-next-builder-502927
Merge into: lp:launchpad
Diff against target: 759 lines (+524/-111)
2 files modified
lib/lp/soyuz/model/buildqueue.py (+81/-0)
lib/lp/soyuz/tests/test_buildqueue.py (+443/-111)
To merge this branch: bzr merge lp:~al-maisan/launchpad/xx-next-builder-502927
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+16896@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Hello there!

This is the next step in getting us to a point where we can estimate build
farm job dispatch times irrespective of job type (binary build, "create source
package from recipe" job, translation job).

The dispatch time estimation requires us to know when the next builder capable
of running the job of interest (JOI) will become available. The branch at hand
adds that logic.

Tests to run:

    bin/test -vv -t test_buildqueue

No "make lint" errors or warnings.

Revision history for this message
Graham Binns (gmb) wrote :
Download full text (27.8 KiB)

Hi Muharem,

Great branch this; I'm really impressed with the whole thing. Couple of
minor nitpicks but otherwise this is r=me.

> === modified file 'lib/lp/soyuz/model/buildqueue.py'
> --- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
> +++ lib/lp/soyuz/model/buildqueue.py 2010-01-06 14:11:14 +0000
> @@ -212,6 +212,83 @@
> free_builders = result_set.get_one()[0]
> return free_builders
>
> + def _estimateTimeToNextBuilder(
> + self, head_job_processor, head_job_virtualized):
> + """Estimate time until next builder becomes available.
> +
> + For the purpose of estimating the dispatch time of the job of interest
> + (JOI) we need to know how long it will take until the job at the head
> + of JOI's queue is dispatched.
> +
> + There are two cases to consider here: the head job is
> +
> + - processor dependent: only builders with the matching
> + processor/virtualization combination should be considered.
> + - *not* processor dependent: all builders should be considered.
> +
> + :param head_job_processor: The processor required by the job at the
> + head of the queue.
> + :param head_job_virtualized: The virtualization setting required by
> + the job at the head of the queue.
> + :return: The estimated number of seconds untils a builder capable of
> + running the head job becomes available or None if no such builder
> + exists.
> + """
> + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
> +
> + # First check whether we have free builders.
> + free_builders = self._freeBuildersCount(
> + head_job_processor, head_job_virtualized)
> +
> + if free_builders > 0:
> + # We have free builders for the given processor/virtualization
> + # combination -> zero delay
> + return 0
> +
> + extra_clauses = ''
> + if head_job_processor is not None:
> + # Only look at builders with specific processor types.
> + extra_clauses += """
> + AND Builder.processor = %s
> + AND Builder.virtualized = %s
> + """ % sqlvalues(head_job_processor, head_job_virtualized)
> +
> + params = sqlvalues(JobStatus.RUNNING) + (extra_clauses,)
> +
> + delay_query = """
> + SELECT MIN(
> + CASE WHEN
> + EXTRACT(EPOCH FROM
> + (BuildQueue.estimated_duration -
> + (((now() AT TIME ZONE 'UTC') - Job.date_started)))) >= 0
> + THEN
> + EXTRACT(EPOCH FROM
> + (BuildQueue.estimated_duration -
> + (((now() AT TIME ZONE 'UTC') - Job.date_started))))
> + ELSE
> + -- Assume that jobs that have overdrawn their estimated
> + -- duration time budget will complete within 2 minutes.
> + -- This is a wild guess but has worked well so far.

As discussed on IRC, this comment should indicate that nothing will
actually break if this wild guess i...

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

Graham Binns wrote:
> Review: Approve code
> Hi Muharem,
>
> Great branch this; I'm really impressed with the whole thing. Couple of
> minor nitpicks but otherwise this is r=me.

Hello Graham,

thank you for being so courteous and reviewing this branch on request.
I have revised the branch to accommodate all your suggestions. Please
find the incremental diff enclosed.

>
>> === modified file 'lib/lp/soyuz/model/buildqueue.py'
>> --- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
>> +++ lib/lp/soyuz/model/buildqueue.py 2010-01-06 14:11:14 +0000
>> @@ -212,6 +212,83 @@
>> free_builders = result_set.get_one()[0]
>> return free_builders
>>
>> + def _estimateTimeToNextBuilder(
>> + self, head_job_processor, head_job_virtualized):
>> + """Estimate time until next builder becomes available.
>> +
>> + For the purpose of estimating the dispatch time of the job of interest
>> + (JOI) we need to know how long it will take until the job at the head
>> + of JOI's queue is dispatched.
>> +
>> + There are two cases to consider here: the head job is
>> +
>> + - processor dependent: only builders with the matching
>> + processor/virtualization combination should be considered.
>> + - *not* processor dependent: all builders should be considered.
>> +
>> + :param head_job_processor: The processor required by the job at the
>> + head of the queue.
>> + :param head_job_virtualized: The virtualization setting required by
>> + the job at the head of the queue.
>> + :return: The estimated number of seconds untils a builder capable of
>> + running the head job becomes available or None if no such builder
>> + exists.
>> + """
>> + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
>> +
>> + # First check whether we have free builders.
>> + free_builders = self._freeBuildersCount(
>> + head_job_processor, head_job_virtualized)
>> +
>> + if free_builders > 0:
>> + # We have free builders for the given processor/virtualization
>> + # combination -> zero delay
>> + return 0
>> +
>> + extra_clauses = ''
>> + if head_job_processor is not None:
>> + # Only look at builders with specific processor types.
>> + extra_clauses += """
>> + AND Builder.processor = %s
>> + AND Builder.virtualized = %s
>> + """ % sqlvalues(head_job_processor, head_job_virtualized)
>> +
>> + params = sqlvalues(JobStatus.RUNNING) + (extra_clauses,)
>> +
>> + delay_query = """
>> + SELECT MIN(
>> + CASE WHEN
>> + EXTRACT(EPOCH FROM
>> + (BuildQueue.estimated_duration -
>> + (((now() AT TIME ZONE 'UTC') - Job.date_started)))) >= 0
>> + THEN
>> + EXTRACT(EPOCH FROM
>> + (BuildQueue.estimated_duration -
>> + (((now() AT TIME ZONE 'UTC') - Job.date_started))))
>> + ELSE
>> + ...

=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-06 14:08:31 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-01-06 15:49:24 +0000
@@ -269,6 +269,10 @@
269 -- Assume that jobs that have overdrawn their estimated269 -- Assume that jobs that have overdrawn their estimated
270 -- duration time budget will complete within 2 minutes.270 -- duration time budget will complete within 2 minutes.
271 -- This is a wild guess but has worked well so far.271 -- This is a wild guess but has worked well so far.
272 --
273 -- Please note that this is entirely innocuous i.e. if our
274 -- guess is off nothing bad will happen but our estimate will
275 -- not be as good as it could be.
272 120276 120
273 END)277 END)
274 FROM278 FROM
275279
=== modified file 'lib/lp/soyuz/tests/test_buildqueue.py'
--- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-06 14:08:31 +0000
+++ lib/lp/soyuz/tests/test_buildqueue.py 2010-01-06 15:51:51 +0000
@@ -154,21 +154,21 @@
154 processor_fam = ProcessorFamilySet().getByName('x86')154 processor_fam = ProcessorFamilySet().getByName('x86')
155 x86_proc = processor_fam.processors[0]155 x86_proc = processor_fam.processors[0]
156 # x86 native156 # x86 native
157 self.builders[(x86_proc.id,False)] = [157 self.builders[(x86_proc.id, False)] = [
158 self.i6, self.i7, self.i8, self.i9]158 self.i6, self.i7, self.i8, self.i9]
159 # x86 virtual159 # x86 virtual
160 self.builders[(x86_proc.id,True)] = [160 self.builders[(x86_proc.id, True)] = [
161 self.i1, self.i2, self.i3, self.i4, self.i5]161 self.i1, self.i2, self.i3, self.i4, self.i5]
162162
163 # amd64 native163 # amd64 native
164 self.builders[(amd_proc.id,False)] = [self.a4, self.a5]164 self.builders[(amd_proc.id, False)] = [self.a4, self.a5]
165 # amd64 virtual165 # amd64 virtual
166 self.builders[(amd_proc.id,True)] = [self.a1, self.a2, self.a3]166 self.builders[(amd_proc.id, True)] = [self.a1, self.a2, self.a3]
167167
168 # hppa native168 # hppa native
169 self.builders[(hppa_proc.id,False)] = [self.h5, self.h6, self.h7]169 self.builders[(hppa_proc.id, False)] = [self.h5, self.h6, self.h7]
170 # hppa virtual170 # hppa virtual
171 self.builders[(hppa_proc.id,True)] = [171 self.builders[(hppa_proc.id, True)] = [
172 self.h1, self.h2, self.h3, self.h4]172 self.h1, self.h2, self.h3, self.h4]
173173
174 # Ensure all builders are operational.174 # Ensure all builders are operational.
@@ -183,9 +183,8 @@
183183
184184
185class SingleArchBuildsBase(TestBuildQueueBase):185class SingleArchBuildsBase(TestBuildQueueBase):
186 """Test the retrieval of builder related data. The latter is required186 """Set up a test environment with builds that target a single
187 for job dispatch time estimations irrespective of job processor187 processor."""
188 architecture and virtualization setting."""
189 def setUp(self):188 def setUp(self):
190 """Set up some native x86 builds for the test archive."""189 """Set up some native x86 builds for the test archive."""
191 super(SingleArchBuildsBase, self).setUp()190 super(SingleArchBuildsBase, self).setUp()
@@ -280,29 +279,29 @@
280 processor_fam = ProcessorFamilySet().getByName('x86')279 processor_fam = ProcessorFamilySet().getByName('x86')
281 x86_proc = processor_fam.processors[0]280 x86_proc = processor_fam.processors[0]
282 self.assertEqual(281 self.assertEqual(
283 builder_stats[(x86_proc.id,False)], 4,282 builder_stats[(x86_proc.id, False)], 4,
284 "The number of native x86 builders is wrong")283 "The number of native x86 builders is wrong")
285 self.assertEqual(284 self.assertEqual(
286 builder_stats[(x86_proc.id,True)], 5,285 builder_stats[(x86_proc.id, True)], 5,
287 "The number of virtual x86 builders is wrong")286 "The number of virtual x86 builders is wrong")
288 processor_fam = ProcessorFamilySet().getByName('amd64')287 processor_fam = ProcessorFamilySet().getByName('amd64')
289 amd_proc = processor_fam.processors[0]288 amd_proc = processor_fam.processors[0]
290 self.assertEqual(289 self.assertEqual(
291 builder_stats[(amd_proc.id,False)], 2,290 builder_stats[(amd_proc.id, False)], 2,
292 "The number of native amd64 builders is wrong")291 "The number of native amd64 builders is wrong")
293 self.assertEqual(292 self.assertEqual(
294 builder_stats[(amd_proc.id,True)], 3,293 builder_stats[(amd_proc.id, True)], 3,
295 "The number of virtual amd64 builders is wrong")294 "The number of virtual amd64 builders is wrong")
296 processor_fam = ProcessorFamilySet().getByName('hppa')295 processor_fam = ProcessorFamilySet().getByName('hppa')
297 hppa_proc = processor_fam.processors[0]296 hppa_proc = processor_fam.processors[0]
298 self.assertEqual(297 self.assertEqual(
299 builder_stats[(hppa_proc.id,False)], 3,298 builder_stats[(hppa_proc.id, False)], 3,
300 "The number of native hppa builders is wrong")299 "The number of native hppa builders is wrong")
301 self.assertEqual(300 self.assertEqual(
302 builder_stats[(hppa_proc.id,True)], 4,301 builder_stats[(hppa_proc.id, True)], 4,
303 "The number of virtual hppa builders is wrong")302 "The number of virtual hppa builders is wrong")
304 # Disable the native x86 builders.303 # Disable the native x86 builders.
305 for builder in self.builders[(x86_proc.id,False)]:304 for builder in self.builders[(x86_proc.id, False)]:
306 builder.builderok = False305 builder.builderok = False
307 # Get the builder statistics again.306 # Get the builder statistics again.
308 builder_data = bq._getBuilderData()307 builder_data = bq._getBuilderData()
@@ -314,7 +313,7 @@
314 "[2] The total number of builders that can build the job in "313 "[2] The total number of builders that can build the job in "
315 "question is wrong.")314 "question is wrong.")
316 # Re-enable one of them.315 # Re-enable one of them.
317 for builder in self.builders[(x86_proc.id,False)]:316 for builder in self.builders[(x86_proc.id, False)]:
318 builder.builderok = True317 builder.builderok = True
319 break318 break
320 # Get the builder statistics again.319 # Get the builder statistics again.
@@ -327,7 +326,7 @@
327 "question is wrong.")326 "question is wrong.")
328 # Disable the *virtual* x86 builders -- should not make any327 # Disable the *virtual* x86 builders -- should not make any
329 # difference.328 # difference.
330 for builder in self.builders[(x86_proc.id,True)]:329 for builder in self.builders[(x86_proc.id, True)]:
331 builder.builderok = False330 builder.builderok = False
332 # Get the builder statistics again.331 # Get the builder statistics again.
333 builder_data = bq._getBuilderData()332 builder_data = bq._getBuilderData()
@@ -352,7 +351,7 @@
352 builders_in_total, builders_for_job, builder_stats = builder_data351 builders_in_total, builders_for_job, builder_stats = builder_data
353 # We have 4 x86 native builders.352 # We have 4 x86 native builders.
354 self.assertEqual(353 self.assertEqual(
355 builder_stats[(proc_386.id,False)], 4,354 builder_stats[(proc_386.id, False)], 4,
356 "The number of native x86 builders is wrong")355 "The number of native x86 builders is wrong")
357 # Initially all 4 builders are free.356 # Initially all 4 builders are free.
358 free_count = bq._freeBuildersCount(357 free_count = bq._freeBuildersCount(
@@ -390,9 +389,11 @@
390389
391390
392class TestMinTimeToNextBuilder(SingleArchBuildsBase):391class TestMinTimeToNextBuilder(SingleArchBuildsBase):
393 """When is the next builder capable of running a given job becoming392 """Test estimated time-to-builder with builds targetting a single
394 available?"""393 processor."""
395 def test_min_time_to_next_builder(self):394 def test_min_time_to_next_builder(self):
395 """When is the next builder capable of running the job at the head of
396 the queue becoming available?"""
396 # Test the estimation of the minimum time until a builder becomes397 # Test the estimation of the minimum time until a builder becomes
397 # available.398 # available.
398399
@@ -457,7 +458,7 @@
457 check_mintime_to_builder(self, apg_job, x86_proc, False, 30)458 check_mintime_to_builder(self, apg_job, x86_proc, False, 30)
458459
459 # Disable the native x86 builders.460 # Disable the native x86 builders.
460 for builder in self.builders[(1,False)]:461 for builder in self.builders[(x86_proc.id, False)]:
461 builder.builderok = False462 builder.builderok = False
462463
463 # No builders capable of running the job at hand are available now,464 # No builders capable of running the job at hand are available now,
@@ -466,10 +467,7 @@
466467
467468
468class MultiArchBuildsBase(TestBuildQueueBase):469class MultiArchBuildsBase(TestBuildQueueBase):
469 """Test dispatch time estimates for binary builds (i.e. single build470 """Set up a test environment with builds and multiple processors."""
470 farm job type) targetting a single processor architecture and the primary
471 archive.
472 """
473 def setUp(self):471 def setUp(self):
474 """Set up some native x86 builds for the test archive."""472 """Set up some native x86 builds for the test archive."""
475 super(MultiArchBuildsBase, self).setUp()473 super(MultiArchBuildsBase, self).setUp()
@@ -562,9 +560,10 @@
562560
563561
564class TestMinTimeToNextBuilderMulti(MultiArchBuildsBase):562class TestMinTimeToNextBuilderMulti(MultiArchBuildsBase):
565 """When is the next builder capable of running a given job becoming563 """Test estimated time-to-builder with builds and multiple processors."""
566 available?"""
567 def test_min_time_to_next_builder(self):564 def test_min_time_to_next_builder(self):
565 """When is the next builder capable of running the job at the head of
566 the queue becoming available?"""
568 processor_fam = ProcessorFamilySet().getByName('hppa')567 processor_fam = ProcessorFamilySet().getByName('hppa')
569 hppa_proc = processor_fam.processors[0]568 hppa_proc = processor_fam.processors[0]
570569
@@ -610,7 +609,7 @@
610 check_mintime_to_builder(self, apg_job, hppa_proc, False, 30)609 check_mintime_to_builder(self, apg_job, hppa_proc, False, 30)
611610
612 # Disable the native hppa builders.611 # Disable the native hppa builders.
613 for builder in self.builders[(hppa_proc.id,False)]:612 for builder in self.builders[(hppa_proc.id, False)]:
614 builder.builderok = False613 builder.builderok = False
615614
616 # No builders capable of running the job at hand are available now,615 # No builders capable of running the job at hand are available now,
@@ -636,7 +635,7 @@
636 check_mintime_to_builder(self, apg_job, None, None, None)635 check_mintime_to_builder(self, apg_job, None, None, None)
637636
638 # Re-enable the native hppa builders.637 # Re-enable the native hppa builders.
639 for builder in self.builders[(hppa_proc.id,False)]:638 for builder in self.builders[(hppa_proc.id, False)]:
640 builder.builderok = True639 builder.builderok = True
641640
642 # The builder that's becoming available next is the one that's641 # The builder that's becoming available next is the one that's
@@ -646,7 +645,7 @@
646 # Make sure we'll find an x86 builder as well.645 # Make sure we'll find an x86 builder as well.
647 processor_fam = ProcessorFamilySet().getByName('x86')646 processor_fam = ProcessorFamilySet().getByName('x86')
648 x86_proc = processor_fam.processors[0]647 x86_proc = processor_fam.processors[0]
649 builder = self.builders[(x86_proc.id,False)][0]648 builder = self.builders[(x86_proc.id, False)][0]
650 builder.builderok = True649 builder.builderok = True
651650
652 # Now this builder is the one that becomes available next (29 minutes651 # Now this builder is the one that becomes available next (29 minutes
@@ -658,7 +657,7 @@
658 check_mintime_to_builder(self, apg_job, None, None, 29)657 check_mintime_to_builder(self, apg_job, None, None, 29)
659658
660 # Make a second, idle x86 builder available.659 # Make a second, idle x86 builder available.
661 builder = self.builders[(x86_proc.id,False)][1]660 builder = self.builders[(x86_proc.id, False)][1]
662 builder.builderok = True661 builder.builderok = True
663662
664 # That builder should be available immediately since it's idle.663 # That builder should be available immediately since it's idle.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/model/buildqueue.py'
--- lib/lp/soyuz/model/buildqueue.py 2010-01-04 14:30:52 +0000
+++ lib/lp/soyuz/model/buildqueue.py 2010-01-06 16:03:24 +0000
@@ -212,6 +212,87 @@
212 free_builders = result_set.get_one()[0]212 free_builders = result_set.get_one()[0]
213 return free_builders213 return free_builders
214214
215 def _estimateTimeToNextBuilder(
216 self, head_job_processor, head_job_virtualized):
217 """Estimate time until next builder becomes available.
218
219 For the purpose of estimating the dispatch time of the job of interest
220 (JOI) we need to know how long it will take until the job at the head
221 of JOI's queue is dispatched.
222
223 There are two cases to consider here: the head job is
224
225 - processor dependent: only builders with the matching
226 processor/virtualization combination should be considered.
227 - *not* processor dependent: all builders should be considered.
228
229 :param head_job_processor: The processor required by the job at the
230 head of the queue.
231 :param head_job_virtualized: The virtualization setting required by
232 the job at the head of the queue.
233 :return: The estimated number of seconds untils a builder capable of
234 running the head job becomes available or None if no such builder
235 exists.
236 """
237 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
238
239 # First check whether we have free builders.
240 free_builders = self._freeBuildersCount(
241 head_job_processor, head_job_virtualized)
242
243 if free_builders > 0:
244 # We have free builders for the given processor/virtualization
245 # combination -> zero delay
246 return 0
247
248 extra_clauses = ''
249 if head_job_processor is not None:
250 # Only look at builders with specific processor types.
251 extra_clauses += """
252 AND Builder.processor = %s
253 AND Builder.virtualized = %s
254 """ % sqlvalues(head_job_processor, head_job_virtualized)
255
256 params = sqlvalues(JobStatus.RUNNING) + (extra_clauses,)
257
258 delay_query = """
259 SELECT MIN(
260 CASE WHEN
261 EXTRACT(EPOCH FROM
262 (BuildQueue.estimated_duration -
263 (((now() AT TIME ZONE 'UTC') - Job.date_started)))) >= 0
264 THEN
265 EXTRACT(EPOCH FROM
266 (BuildQueue.estimated_duration -
267 (((now() AT TIME ZONE 'UTC') - Job.date_started))))
268 ELSE
269 -- Assume that jobs that have overdrawn their estimated
270 -- duration time budget will complete within 2 minutes.
271 -- This is a wild guess but has worked well so far.
272 --
273 -- Please note that this is entirely innocuous i.e. if our
274 -- guess is off nothing bad will happen but our estimate will
275 -- not be as good as it could be.
276 120
277 END)
278 FROM
279 BuildQueue, Job, Builder
280 WHERE
281 BuildQueue.job = Job.id
282 AND BuildQueue.builder = Builder.id
283 AND Builder.manual = False
284 AND Builder.builderok = True
285 AND Job.status = %s
286 %s
287 """ % params
288
289 result_set = store.execute(delay_query)
290 head_job_delay = result_set.get_one()[0]
291 if head_job_delay is None:
292 return None
293 else:
294 return int(head_job_delay)
295
215296
216class BuildQueueSet(object):297class BuildQueueSet(object):
217 """Utility to deal with BuildQueue content class."""298 """Utility to deal with BuildQueue content class."""
218299
=== modified file 'lib/lp/soyuz/tests/test_buildqueue.py'
--- lib/lp/soyuz/tests/test_buildqueue.py 2010-01-04 10:58:09 +0000
+++ lib/lp/soyuz/tests/test_buildqueue.py 2010-01-06 16:03:24 +0000
@@ -4,7 +4,8 @@
44
5"""Test BuildQueue features."""5"""Test BuildQueue features."""
66
7from datetime import timedelta7from datetime import datetime, timedelta
8from pytz import utc
89
9from zope.component import getUtility10from zope.component import getUtility
1011
@@ -33,8 +34,6 @@
3334
34def nth_builder(test, build, n):35def nth_builder(test, build, n):
35 """Find nth builder that can execute the given build."""36 """Find nth builder that can execute the given build."""
36 def builder_key(build):
37 return (build.processor.id,build.is_virtualized)
38 builder = None37 builder = None
39 builders = test.builders.get(builder_key(build), [])38 builders = test.builders.get(builder_key(build), [])
40 try:39 try:
@@ -44,9 +43,9 @@
44 return builder43 return builder
4544
4645
47def assign_to_builder(test, job_name, builder_number):46def assign_to_builder(test, job_name, builder_number, processor='386'):
48 """Simulate assigning a build to a builder."""47 """Simulate assigning a build to a builder."""
49 build, bq = find_job(test, job_name)48 build, bq = find_job(test, job_name, processor)
50 builder = nth_builder(test, build, builder_number)49 builder = nth_builder(test, build, builder_number)
51 bq.markAsBuilding(builder)50 bq.markAsBuilding(builder)
5251
@@ -61,6 +60,43 @@
61 bq.estimated_duration, bq.lastscore)60 bq.estimated_duration, bq.lastscore)
6261
6362
63def builder_key(job):
64 """Access key for builders capable of running the given job."""
65 return (job.processor.id, job.is_virtualized)
66
67
68def check_mintime_to_builder(
69 test, bq, head_job_processor, head_job_virtualized, min_time):
70 """Test the estimated time until a builder becomes available."""
71 delay = bq._estimateTimeToNextBuilder(
72 head_job_processor, head_job_virtualized)
73 if min_time is not None:
74 test.assertTrue(
75 almost_equal(delay, min_time),
76 "Wrong min time to next available builder (%s != %s)"
77 % (delay, min_time))
78 else:
79 test.assertTrue(delay is None, "No delay to next builder available")
80
81
82def almost_equal(a, b, deviation=1):
83 """Compare the values tolerating the given deviation.
84
85 This used to spurious failures in time based tests.
86 """
87 if abs(a - b) <= deviation:
88 return True
89 else:
90 return False
91
92
93def set_remaining_time_for_running_job(bq, remainder):
94 """Set remaining running time for job."""
95 offset = bq.estimated_duration.seconds - remainder
96 bq.setDateStarted(
97 datetime.utcnow().replace(tzinfo=utc) - timedelta(seconds=offset))
98
99
64class TestBuildQueueBase(TestCaseWithFactory):100class TestBuildQueueBase(TestCaseWithFactory):
65 """Setup the test publisher and some builders."""101 """Setup the test publisher and some builders."""
66102
@@ -84,45 +120,56 @@
84120
85 # Next make seven 'hppa' builders.121 # Next make seven 'hppa' builders.
86 processor_fam = ProcessorFamilySet().getByName('hppa')122 processor_fam = ProcessorFamilySet().getByName('hppa')
87 proc = processor_fam.processors[0]123 hppa_proc = processor_fam.processors[0]
88 self.h1 = self.factory.makeBuilder(name='hppa-v-1', processor=proc)124 self.h1 = self.factory.makeBuilder(
89 self.h2 = self.factory.makeBuilder(name='hppa-v-2', processor=proc)125 name='hppa-v-1', processor=hppa_proc)
90 self.h3 = self.factory.makeBuilder(name='hppa-v-3', processor=proc)126 self.h2 = self.factory.makeBuilder(
91 self.h4 = self.factory.makeBuilder(name='hppa-v-4', processor=proc)127 name='hppa-v-2', processor=hppa_proc)
128 self.h3 = self.factory.makeBuilder(
129 name='hppa-v-3', processor=hppa_proc)
130 self.h4 = self.factory.makeBuilder(
131 name='hppa-v-4', processor=hppa_proc)
92 self.h5 = self.factory.makeBuilder(132 self.h5 = self.factory.makeBuilder(
93 name='hppa-n-5', processor=proc, virtualized=False)133 name='hppa-n-5', processor=hppa_proc, virtualized=False)
94 self.h6 = self.factory.makeBuilder(134 self.h6 = self.factory.makeBuilder(
95 name='hppa-n-6', processor=proc, virtualized=False)135 name='hppa-n-6', processor=hppa_proc, virtualized=False)
96 self.h7 = self.factory.makeBuilder(136 self.h7 = self.factory.makeBuilder(
97 name='hppa-n-7', processor=proc, virtualized=False)137 name='hppa-n-7', processor=hppa_proc, virtualized=False)
98138
99 # Finally make five 'amd64' builders.139 # Finally make five 'amd64' builders.
100 processor_fam = ProcessorFamilySet().getByName('amd64')140 processor_fam = ProcessorFamilySet().getByName('amd64')
101 proc = processor_fam.processors[0]141 amd_proc = processor_fam.processors[0]
102 self.a1 = self.factory.makeBuilder(name='amd64-v-1', processor=proc)142 self.a1 = self.factory.makeBuilder(
103 self.a2 = self.factory.makeBuilder(name='amd64-v-2', processor=proc)143 name='amd64-v-1', processor=amd_proc)
104 self.a3 = self.factory.makeBuilder(name='amd64-v-3', processor=proc)144 self.a2 = self.factory.makeBuilder(
145 name='amd64-v-2', processor=amd_proc)
146 self.a3 = self.factory.makeBuilder(
147 name='amd64-v-3', processor=amd_proc)
105 self.a4 = self.factory.makeBuilder(148 self.a4 = self.factory.makeBuilder(
106 name='amd64-n-4', processor=proc, virtualized=False)149 name='amd64-n-4', processor=amd_proc, virtualized=False)
107 self.a5 = self.factory.makeBuilder(150 self.a5 = self.factory.makeBuilder(
108 name='amd64-n-5', processor=proc, virtualized=False)151 name='amd64-n-5', processor=amd_proc, virtualized=False)
109152
110 self.builders = dict()153 self.builders = dict()
154 processor_fam = ProcessorFamilySet().getByName('x86')
155 x86_proc = processor_fam.processors[0]
111 # x86 native156 # x86 native
112 self.builders[(1,False)] = [self.i6, self.i7, self.i8, self.i9]157 self.builders[(x86_proc.id, False)] = [
158 self.i6, self.i7, self.i8, self.i9]
113 # x86 virtual159 # x86 virtual
114 self.builders[(1,True)] = [160 self.builders[(x86_proc.id, True)] = [
115 self.i1, self.i2, self.i3, self.i4, self.i5]161 self.i1, self.i2, self.i3, self.i4, self.i5]
116162
117 # amd64 native163 # amd64 native
118 self.builders[(2,True)] = [self.a4, self.a5]164 self.builders[(amd_proc.id, False)] = [self.a4, self.a5]
119 # amd64 virtual165 # amd64 virtual
120 self.builders[(2,False)] = [self.a1, self.a2, self.a3]166 self.builders[(amd_proc.id, True)] = [self.a1, self.a2, self.a3]
121167
122 # hppa native168 # hppa native
123 self.builders[(3,True)] = [self.h5, self.h6, self.h7]169 self.builders[(hppa_proc.id, False)] = [self.h5, self.h6, self.h7]
124 # hppa virtual170 # hppa virtual
125 self.builders[(3,False)] = [self.h1, self.h2, self.h3, self.h4]171 self.builders[(hppa_proc.id, True)] = [
172 self.h1, self.h2, self.h3, self.h4]
126173
127 # Ensure all builders are operational.174 # Ensure all builders are operational.
128 for builders in self.builders.values():175 for builders in self.builders.values():
@@ -135,85 +182,88 @@
135 getUtility(IBuilderSet)['frog'].builderok = False182 getUtility(IBuilderSet)['frog'].builderok = False
136183
137184
138class TestBuilderData(TestBuildQueueBase):185class SingleArchBuildsBase(TestBuildQueueBase):
186 """Set up a test environment with builds that target a single
187 processor."""
188 def setUp(self):
189 """Set up some native x86 builds for the test archive."""
190 super(SingleArchBuildsBase, self).setUp()
191 # The builds will be set up as follows:
192 #
193 # gedit, p: 386, v:False e:0:01:00 *** s: 1001
194 # firefox, p: 386, v:False e:0:02:00 *** s: 1002
195 # apg, p: 386, v:False e:0:03:00 *** s: 1003
196 # vim, p: 386, v:False e:0:04:00 *** s: 1004
197 # gcc, p: 386, v:False e:0:05:00 *** s: 1005
198 # bison, p: 386, v:False e:0:06:00 *** s: 1006
199 # flex, p: 386, v:False e:0:07:00 *** s: 1007
200 # postgres, p: 386, v:False e:0:08:00 *** s: 1008
201 #
202 # p=processor, v=virtualized, e=estimated_duration, s=score
203
204 # First mark all builds in the sample data as already built.
205 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
206 sample_data = store.find(Build)
207 for build in sample_data:
208 build.buildstate = BuildStatus.FULLYBUILT
209 store.flush()
210
211 # We test builds that target a primary archive.
212 self.non_ppa = self.factory.makeArchive(
213 name="primary", purpose=ArchivePurpose.PRIMARY)
214 self.non_ppa.require_virtualized = False
215
216 self.builds = []
217 self.builds.extend(
218 self.publisher.getPubSource(
219 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
220 archive=self.non_ppa).createMissingBuilds())
221 self.builds.extend(
222 self.publisher.getPubSource(
223 sourcename="firefox",
224 status=PackagePublishingStatus.PUBLISHED,
225 archive=self.non_ppa).createMissingBuilds())
226 self.builds.extend(
227 self.publisher.getPubSource(
228 sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
229 archive=self.non_ppa).createMissingBuilds())
230 self.builds.extend(
231 self.publisher.getPubSource(
232 sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
233 archive=self.non_ppa).createMissingBuilds())
234 self.builds.extend(
235 self.publisher.getPubSource(
236 sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
237 archive=self.non_ppa).createMissingBuilds())
238 self.builds.extend(
239 self.publisher.getPubSource(
240 sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
241 archive=self.non_ppa).createMissingBuilds())
242 self.builds.extend(
243 self.publisher.getPubSource(
244 sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
245 archive=self.non_ppa).createMissingBuilds())
246 self.builds.extend(
247 self.publisher.getPubSource(
248 sourcename="postgres",
249 status=PackagePublishingStatus.PUBLISHED,
250 archive=self.non_ppa).createMissingBuilds())
251 # Set up the builds for test.
252 score = 1000
253 duration = 0
254 for build in self.builds:
255 score += 1
256 duration += 60
257 bq = build.buildqueue_record
258 bq.lastscore = score
259 bq.estimated_duration = timedelta(seconds=duration)
260 # print_build_setup(self.builds)
261
262
263class TestBuilderData(SingleArchBuildsBase):
139 """Test the retrieval of builder related data. The latter is required264 """Test the retrieval of builder related data. The latter is required
140 for job dispatch time estimations irrespective of job processor265 for job dispatch time estimations irrespective of job processor
141 architecture and virtualization setting."""266 architecture and virtualization setting."""
142
143 def setUp(self):
144 """Set up some native x86 builds for the test archive."""
145 super(TestBuilderData, self).setUp()
146 # The builds will be set up as follows:
147 #
148 # gedit, p: 386, v:False e:0:01:00 *** s: 1001
149 # firefox, p: 386, v:False e:0:02:00 *** s: 1002
150 # apg, p: 386, v:False e:0:03:00 *** s: 1003
151 # vim, p: 386, v:False e:0:04:00 *** s: 1004
152 # gcc, p: 386, v:False e:0:05:00 *** s: 1005
153 # bison, p: 386, v:False e:0:06:00 *** s: 1006
154 # flex, p: 386, v:False e:0:07:00 *** s: 1007
155 # postgres, p: 386, v:False e:0:08:00 *** s: 1008
156 #
157 # p=processor, v=virtualized, e=estimated_duration, s=score
158
159 # First mark all builds in the sample data as already built.
160 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
161 sample_data = store.find(Build)
162 for build in sample_data:
163 build.buildstate = BuildStatus.FULLYBUILT
164 store.flush()
165
166 # We test builds that target a primary archive.
167 self.non_ppa = self.factory.makeArchive(
168 name="primary", purpose=ArchivePurpose.PRIMARY)
169 self.non_ppa.require_virtualized = False
170
171 self.builds = []
172 self.builds.extend(
173 self.publisher.getPubSource(
174 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
175 archive=self.non_ppa).createMissingBuilds())
176 self.builds.extend(
177 self.publisher.getPubSource(
178 sourcename="firefox",
179 status=PackagePublishingStatus.PUBLISHED,
180 archive=self.non_ppa).createMissingBuilds())
181 self.builds.extend(
182 self.publisher.getPubSource(
183 sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
184 archive=self.non_ppa).createMissingBuilds())
185 self.builds.extend(
186 self.publisher.getPubSource(
187 sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
188 archive=self.non_ppa).createMissingBuilds())
189 self.builds.extend(
190 self.publisher.getPubSource(
191 sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
192 archive=self.non_ppa).createMissingBuilds())
193 self.builds.extend(
194 self.publisher.getPubSource(
195 sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
196 archive=self.non_ppa).createMissingBuilds())
197 self.builds.extend(
198 self.publisher.getPubSource(
199 sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
200 archive=self.non_ppa).createMissingBuilds())
201 self.builds.extend(
202 self.publisher.getPubSource(
203 sourcename="postgres",
204 status=PackagePublishingStatus.PUBLISHED,
205 archive=self.non_ppa).createMissingBuilds())
206 # Set up the builds for test.
207 score = 1000
208 duration = 0
209 for build in self.builds:
210 score += 1
211 duration += 60
212 bq = build.buildqueue_record
213 bq.lastscore = score
214 bq.estimated_duration = timedelta(seconds=duration)
215 # print_build_setup(self.builds)
216
217 def test_builder_data(self):267 def test_builder_data(self):
218 # Make sure the builder numbers are correct. The builder data will268 # Make sure the builder numbers are correct. The builder data will
219 # be the same for all of our builds.269 # be the same for all of our builds.
@@ -226,26 +276,32 @@
226 builders_for_job, 4,276 builders_for_job, 4,
227 "[1] The total number of builders that can build the job in "277 "[1] The total number of builders that can build the job in "
228 "question is wrong.")278 "question is wrong.")
279 processor_fam = ProcessorFamilySet().getByName('x86')
280 x86_proc = processor_fam.processors[0]
229 self.assertEqual(281 self.assertEqual(
230 builder_stats[(1,False)], 4,282 builder_stats[(x86_proc.id, False)], 4,
231 "The number of native x86 builders is wrong")283 "The number of native x86 builders is wrong")
232 self.assertEqual(284 self.assertEqual(
233 builder_stats[(1,True)], 5,285 builder_stats[(x86_proc.id, True)], 5,
234 "The number of virtual x86 builders is wrong")286 "The number of virtual x86 builders is wrong")
287 processor_fam = ProcessorFamilySet().getByName('amd64')
288 amd_proc = processor_fam.processors[0]
235 self.assertEqual(289 self.assertEqual(
236 builder_stats[(2,False)], 2,290 builder_stats[(amd_proc.id, False)], 2,
237 "The number of native amd64 builders is wrong")291 "The number of native amd64 builders is wrong")
238 self.assertEqual(292 self.assertEqual(
239 builder_stats[(2,True)], 3,293 builder_stats[(amd_proc.id, True)], 3,
240 "The number of virtual amd64 builders is wrong")294 "The number of virtual amd64 builders is wrong")
295 processor_fam = ProcessorFamilySet().getByName('hppa')
296 hppa_proc = processor_fam.processors[0]
241 self.assertEqual(297 self.assertEqual(
242 builder_stats[(3,False)], 3,298 builder_stats[(hppa_proc.id, False)], 3,
243 "The number of native hppa builders is wrong")299 "The number of native hppa builders is wrong")
244 self.assertEqual(300 self.assertEqual(
245 builder_stats[(3,True)], 4,301 builder_stats[(hppa_proc.id, True)], 4,
246 "The number of virtual hppa builders is wrong")302 "The number of virtual hppa builders is wrong")
247 # Disable the native x86 builders.303 # Disable the native x86 builders.
248 for builder in self.builders[(1,False)]:304 for builder in self.builders[(x86_proc.id, False)]:
249 builder.builderok = False305 builder.builderok = False
250 # Get the builder statistics again.306 # Get the builder statistics again.
251 builder_data = bq._getBuilderData()307 builder_data = bq._getBuilderData()
@@ -257,7 +313,7 @@
257 "[2] The total number of builders that can build the job in "313 "[2] The total number of builders that can build the job in "
258 "question is wrong.")314 "question is wrong.")
259 # Re-enable one of them.315 # Re-enable one of them.
260 for builder in self.builders[(1,False)]:316 for builder in self.builders[(x86_proc.id, False)]:
261 builder.builderok = True317 builder.builderok = True
262 break318 break
263 # Get the builder statistics again.319 # Get the builder statistics again.
@@ -270,7 +326,7 @@
270 "question is wrong.")326 "question is wrong.")
271 # Disable the *virtual* x86 builders -- should not make any327 # Disable the *virtual* x86 builders -- should not make any
272 # difference.328 # difference.
273 for builder in self.builders[(1,True)]:329 for builder in self.builders[(x86_proc.id, True)]:
274 builder.builderok = False330 builder.builderok = False
275 # Get the builder statistics again.331 # Get the builder statistics again.
276 builder_data = bq._getBuilderData()332 builder_data = bq._getBuilderData()
@@ -295,7 +351,7 @@
295 builders_in_total, builders_for_job, builder_stats = builder_data351 builders_in_total, builders_for_job, builder_stats = builder_data
296 # We have 4 x86 native builders.352 # We have 4 x86 native builders.
297 self.assertEqual(353 self.assertEqual(
298 builder_stats[(proc_386.id,False)], 4,354 builder_stats[(proc_386.id, False)], 4,
299 "The number of native x86 builders is wrong")355 "The number of native x86 builders is wrong")
300 # Initially all 4 builders are free.356 # Initially all 4 builders are free.
301 free_count = bq._freeBuildersCount(357 free_count = bq._freeBuildersCount(
@@ -330,3 +386,279 @@
330 free_count = bq._freeBuildersCount(386 free_count = bq._freeBuildersCount(
331 build.processor, build.is_virtualized)387 build.processor, build.is_virtualized)
332 self.assertEqual(free_count, 1)388 self.assertEqual(free_count, 1)
389
390
391class TestMinTimeToNextBuilder(SingleArchBuildsBase):
392 """Test estimated time-to-builder with builds targetting a single
393 processor."""
394 def test_min_time_to_next_builder(self):
395 """When is the next builder capable of running the job at the head of
396 the queue becoming available?"""
397 # Test the estimation of the minimum time until a builder becomes
398 # available.
399
400 # The builds will be set up as follows:
401 #
402 # gedit, p: 386, v:False e:0:01:00 *** s: 1001
403 # firefox, p: 386, v:False e:0:02:00 *** s: 1002
404 # apg, p: 386, v:False e:0:03:00 *** s: 1003
405 # vim, p: 386, v:False e:0:04:00 *** s: 1004
406 # gcc, p: 386, v:False e:0:05:00 *** s: 1005
407 # bison, p: 386, v:False e:0:06:00 *** s: 1006
408 # flex, p: 386, v:False e:0:07:00 *** s: 1007
409 # postgres, p: 386, v:False e:0:08:00 *** s: 1008
410 #
411 # p=processor, v=virtualized, e=estimated_duration, s=score
412
413 processor_fam = ProcessorFamilySet().getByName('x86')
414 x86_proc = processor_fam.processors[0]
415 # This will be the job of interest.
416 apg_build, apg_job = find_job(self, 'apg')
417 # One of four builders for the 'apg' build is immediately available.
418 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)
419
420 # Assign the postgres job to a builder.
421 assign_to_builder(self, 'postgres', 1)
422 # Now one builder is gone. But there should still be a builder
423 # immediately available.
424 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)
425
426 assign_to_builder(self, 'flex', 2)
427 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)
428
429 assign_to_builder(self, 'bison', 3)
430 check_mintime_to_builder(self, apg_job, x86_proc, False, 0)
431
432 assign_to_builder(self, 'gcc', 4)
433 # Now that no builder is immediately available, the shortest
434 # remaing build time (based on the estimated duration) is returned:
435 # 300 seconds
436 # This is equivalent to the 'gcc' job's estimated duration.
437 check_mintime_to_builder(self, apg_job, x86_proc, False, 300)
438
439 # Now we pretend that the 'postgres' started 6 minutes ago. Its
440 # remaining execution time should be 2 minutes = 120 seconds and
441 # it now becomes the job whose builder becomes available next.
442 build, bq = find_job(self, 'postgres')
443 set_remaining_time_for_running_job(bq, 120)
444 check_mintime_to_builder(self, apg_job, x86_proc, False, 120)
445
446 # What happens when jobs overdraw the estimated duration? Let's
447 # pretend the 'flex' job started 8 minutes ago.
448 build, bq = find_job(self, 'flex')
449 set_remaining_time_for_running_job(bq, -60)
450 # In such a case we assume that the job will complete within 2
451 # minutes, this is a guess that has worked well so far.
452 check_mintime_to_builder(self, apg_job, x86_proc, False, 120)
453
454 # If there's a job that will complete within a shorter time then
455 # we expect to be given that time frame.
456 build, bq = find_job(self, 'postgres')
457 set_remaining_time_for_running_job(bq, 30)
458 check_mintime_to_builder(self, apg_job, x86_proc, False, 30)
459
460 # Disable the native x86 builders.
461 for builder in self.builders[(x86_proc.id, False)]:
462 builder.builderok = False
463
464 # No builders capable of running the job at hand are available now,
465 # this is indicated by a None value.
466 check_mintime_to_builder(self, apg_job, x86_proc, False, None)
467
468
469class MultiArchBuildsBase(TestBuildQueueBase):
470 """Set up a test environment with builds and multiple processors."""
471 def setUp(self):
472 """Set up some native x86 builds for the test archive."""
473 super(MultiArchBuildsBase, self).setUp()
474 # The builds will be set up as follows:
475 #
476 # gedit, p: hppa, v:False e:0:01:00 *** s: 1001
477 # gedit, p: 386, v:False e:0:02:00 *** s: 1002
478 # firefox, p: hppa, v:False e:0:03:00 *** s: 1003
479 # firefox, p: 386, v:False e:0:04:00 *** s: 1004
480 # apg, p: hppa, v:False e:0:05:00 *** s: 1005
481 # apg, p: 386, v:False e:0:06:00 *** s: 1006
482 # vim, p: hppa, v:False e:0:07:00 *** s: 1007
483 # vim, p: 386, v:False e:0:08:00 *** s: 1008
484 # gcc, p: hppa, v:False e:0:09:00 *** s: 1009
485 # gcc, p: 386, v:False e:0:10:00 *** s: 1010
486 # bison, p: hppa, v:False e:0:11:00 *** s: 1011
487 # bison, p: 386, v:False e:0:12:00 *** s: 1012
488 # flex, p: hppa, v:False e:0:13:00 *** s: 1013
489 # flex, p: 386, v:False e:0:14:00 *** s: 1014
490 # postgres, p: hppa, v:False e:0:15:00 *** s: 1015
491 # postgres, p: 386, v:False e:0:16:00 *** s: 1016
492 #
493 # p=processor, v=virtualized, e=estimated_duration, s=score
494
495 # First mark all builds in the sample data as already built.
496 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
497 sample_data = store.find(Build)
498 for build in sample_data:
499 build.buildstate = BuildStatus.FULLYBUILT
500 store.flush()
501
502 # We test builds that target a primary archive.
503 self.non_ppa = self.factory.makeArchive(
504 name="primary", purpose=ArchivePurpose.PRIMARY)
505 self.non_ppa.require_virtualized = False
506
507 self.builds = []
508 self.builds.extend(
509 self.publisher.getPubSource(
510 sourcename="gedit", status=PackagePublishingStatus.PUBLISHED,
511 archive=self.non_ppa,
512 architecturehintlist='any').createMissingBuilds())
513 self.builds.extend(
514 self.publisher.getPubSource(
515 sourcename="firefox",
516 status=PackagePublishingStatus.PUBLISHED,
517 archive=self.non_ppa,
518 architecturehintlist='any').createMissingBuilds())
519 self.builds.extend(
520 self.publisher.getPubSource(
521 sourcename="apg", status=PackagePublishingStatus.PUBLISHED,
522 archive=self.non_ppa,
523 architecturehintlist='any').createMissingBuilds())
524 self.builds.extend(
525 self.publisher.getPubSource(
526 sourcename="vim", status=PackagePublishingStatus.PUBLISHED,
527 archive=self.non_ppa,
528 architecturehintlist='any').createMissingBuilds())
529 self.builds.extend(
530 self.publisher.getPubSource(
531 sourcename="gcc", status=PackagePublishingStatus.PUBLISHED,
532 archive=self.non_ppa,
533 architecturehintlist='any').createMissingBuilds())
534 self.builds.extend(
535 self.publisher.getPubSource(
536 sourcename="bison", status=PackagePublishingStatus.PUBLISHED,
537 archive=self.non_ppa,
538 architecturehintlist='any').createMissingBuilds())
539 self.builds.extend(
540 self.publisher.getPubSource(
541 sourcename="flex", status=PackagePublishingStatus.PUBLISHED,
542 archive=self.non_ppa,
543 architecturehintlist='any').createMissingBuilds())
544 self.builds.extend(
545 self.publisher.getPubSource(
546 sourcename="postgres",
547 status=PackagePublishingStatus.PUBLISHED,
548 archive=self.non_ppa,
549 architecturehintlist='any').createMissingBuilds())
550 # Set up the builds for test.
551 score = 1000
552 duration = 0
553 for build in self.builds:
554 score += 1
555 duration += 60
556 bq = build.buildqueue_record
557 bq.lastscore = score
558 bq.estimated_duration = timedelta(seconds=duration)
559 # print_build_setup(self.builds)
560
561
562class TestMinTimeToNextBuilderMulti(MultiArchBuildsBase):
563 """Test estimated time-to-builder with builds and multiple processors."""
564 def test_min_time_to_next_builder(self):
565 """When is the next builder capable of running the job at the head of
566 the queue becoming available?"""
567 processor_fam = ProcessorFamilySet().getByName('hppa')
568 hppa_proc = processor_fam.processors[0]
569
570 # One of four builders for the 'apg' build is immediately available.
571 apg_build, apg_job = find_job(self, 'apg', 'hppa')
572 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)
573
574 # Assign the postgres job to a builder.
575 assign_to_builder(self, 'postgres', 1, 'hppa')
576 # Now one builder is gone. But there should still be a builder
577 # immediately available.
578 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)
579
580 assign_to_builder(self, 'flex', 2, 'hppa')
581 check_mintime_to_builder(self, apg_job, hppa_proc, False, 0)
582
583 assign_to_builder(self, 'bison', 3, 'hppa')
584 # Now that no builder is immediately available, the shortest
585 # remaing build time (based on the estimated duration) is returned:
586 # 660 seconds
587 # This is equivalent to the 'bison' job's estimated duration.
588 check_mintime_to_builder(self, apg_job, hppa_proc, False, 660)
589
590 # Now we pretend that the 'postgres' started 13 minutes ago. Its
591 # remaining execution time should be 2 minutes = 120 seconds and
592 # it now becomes the job whose builder becomes available next.
593 build, bq = find_job(self, 'postgres', 'hppa')
594 set_remaining_time_for_running_job(bq, 120)
595 check_mintime_to_builder(self, apg_job, hppa_proc, False, 120)
596
597 # What happens when jobs overdraw the estimated duration? Let's
598 # pretend the 'flex' job started 14 minutes ago.
599 build, bq = find_job(self, 'flex', 'hppa')
600 set_remaining_time_for_running_job(bq, -60)
601 # In such a case we assume that the job will complete within 2
602 # minutes, this is a guess that has worked well so far.
603 check_mintime_to_builder(self, apg_job, hppa_proc, False, 120)
604
605 # If there's a job that will complete within a shorter time then
606 # we expect to be given that time frame.
607 build, bq = find_job(self, 'postgres', 'hppa')
608 set_remaining_time_for_running_job(bq, 30)
609 check_mintime_to_builder(self, apg_job, hppa_proc, False, 30)
610
611 # Disable the native hppa builders.
612 for builder in self.builders[(hppa_proc.id, False)]:
613 builder.builderok = False
614
615 # No builders capable of running the job at hand are available now,
616 # this is indicated by a None value.
617 check_mintime_to_builder(self, apg_job, hppa_proc, False, None)
618
619 # Let's assume for the moment that the job at the head of the 'apg'
620 # build queue is processor independent. In that case we'd ask for
621 # *any* next available builder.
622 self.assertTrue(
623 bq._freeBuildersCount(None, None) > 0,
624 "Builders are immediately available for jobs that don't care "
625 "about processor architectures or virtualization")
626 check_mintime_to_builder(self, apg_job, None, None, 0)
627
628 # Let's disable all builders.
629 for builders in self.builders.itervalues():
630 for builder in builders:
631 builder.builderok = False
632
633 # There are no builders capable of running even the processor
634 # independent jobs now and that this is indicated by a None value.
635 check_mintime_to_builder(self, apg_job, None, None, None)
636
637 # Re-enable the native hppa builders.
638 for builder in self.builders[(hppa_proc.id, False)]:
639 builder.builderok = True
640
641 # The builder that's becoming available next is the one that's
642 # running the 'postgres' build.
643 check_mintime_to_builder(self, apg_job, None, None, 30)
644
645 # Make sure we'll find an x86 builder as well.
646 processor_fam = ProcessorFamilySet().getByName('x86')
647 x86_proc = processor_fam.processors[0]
648 builder = self.builders[(x86_proc.id, False)][0]
649 builder.builderok = True
650
651 # Now this builder is the one that becomes available next (29 minutes
652 # remaining build time).
653 assign_to_builder(self, 'gcc', 1, '386')
654 build, bq = find_job(self, 'gcc', '386')
655 set_remaining_time_for_running_job(bq, 29)
656
657 check_mintime_to_builder(self, apg_job, None, None, 29)
658
659 # Make a second, idle x86 builder available.
660 builder = self.builders[(x86_proc.id, False)][1]
661 builder.builderok = True
662
663 # That builder should be available immediately since it's idle.
664 check_mintime_to_builder(self, apg_job, None, None, 0)