Merge lp:~stevenk/launchpad/queue-recipes-better into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12298
Proposed branch: lp:~stevenk/launchpad/queue-recipes-better
Merge into: lp:launchpad
Diff against target: 316 lines (+128/-43)
4 files modified
lib/lp/code/model/sourcepackagerecipe.py (+30/-13)
lib/lp/code/model/sourcepackagerecipebuild.py (+7/-4)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+16/-0)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+75/-26)
To merge this branch: bzr merge lp:~stevenk/launchpad/queue-recipes-better
Reviewer Review Type Date Requested Status
William Grant code* Approve
Robert Collins (community) Approve
Review via email: mp+47358@code.launchpad.net

Commit message

[r=lifeless,wgrant][ui=none][bug=710435] Teach makeDailyBuilds() to not create a build if the recipe has built into its daily build archive within the past 24 hours.

Description of the change

Change findStaleDailyBuilds() to only return builds which are interesting for a particular run of request_daily_builds -- that is, only returns recipes that either don't have a build, or have one that is older than 24 hours. I have also done a few drive-bys.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

So, whats this intended to achieve? As I read it it will result in builds building once every two days. That seems undesirable and confusing.

review: Needs Information
Revision history for this message
Steve Kowalik (stevenk) wrote :

The point of this branch is to foster work to getting the request_daily_builds script running more than once per day. To do so we need to skip daily builds which aren't old enough.

Revision history for this message
Robert Collins (lifeless) wrote :

On Tue, Jan 25, 2011 at 6:43 PM, Steve Kowalik <email address hidden> wrote:
> The point of this branch is to foster work to getting the request_daily_builds script running more than once per day. To do so we need to skip daily builds which aren't old enough.

That implies that we'll be considering builds which are not
interesting to build, which is inefficient. I don't think this is a
useful step towards where you want to get. Better to only iterate
recipes which are interesting to build on a given run - which won't
need any of the changes of this patch, and will be sufficient in and
of itself.

Seperately, remember than changing something takes time - a 24 hour
interval isn't 24 hours, its really 24 hours - the time we take to do
stuff - otherwise folk will get built one day, not the next, and so
on.

-Rob

Revision history for this message
William Grant (wgrant) wrote :

Thanks for this change, Steve. However, findStaleDailyBuilds is too liberal in what it considers to have been built within the last day: it needs to check that it was built for all daily series, and into the daily build archive.

A few other largely stylistic comments:

 - requestBuild should use Archive.default_component, not IComponentSet directly.
 - The join is unreadable; could you please fix up the indentation?
 - Why use a RightJoin instead of a LeftJoin? There is not a single other RightJoin callsite in the tree.
 - There is some trailing whitespace in findStaleDailyBuilds, and possibly other places too. 'make lint' should pick this up.

review: Needs Fixing
Revision history for this message
Robert Collins (lifeless) wrote :

Mentor review.

I think this is fine to land with a few small tweaks.

William: meta on the review - we try not to escalate the work done in a branch except to avoid regressions; in particular the defects about series and archive are already present in the tree; they are neutral as far as this branch is concerned. I agree that they are defects, but not new to the branch and so deferrable. When possible suggest actual fixes with diffs/something to copy and paste in. Lastly some of the prose could be improved - comments in particular can bitrot so its worth making sure they really are clear.

As for the right join, thats because this is appropriate; we have a really weird one-object,three-table split in the DB at the moment, and we need to treat the composed table of all three things as a single object: we can't left join with it because the condition for restricting interesting builds is to the right of one of the partitions. RightJoin is appropriate here from a SQL perspective - the generated query runs in 4-6ms on qastaging.

Steven: I agree about the join indentation. How about this:

+ joins = RightJoin(
+ Join(
+ Join(SourcePackageRecipeBuild, PackageBuild,
+ PackageBuild.id == SourcePackageRecipeBuild.package_build_id),
+ BuildFarmJob,
+ And(BuildFarmJob.id == PackageBuild.build_farm_job_id,
+ BuildFarmJob.date_created > one_day_ago)),
+ SourcePackageRecipe,
+ SourcePackageRecipeBuild.recipe == SourcePackageRecipe.id)

200 + # makeDailyBuilds() won't create a build if the recipe isn't stale.

->

200 + # only stale builds cause makeDailyBuilds() to make new builds

review: Approve
Revision history for this message
William Grant (wgrant) wrote :

The defects are new, since requesting a single build did not previously unset is_stale.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Jan 31, 2011 at 4:13 PM, William Grant <email address hidden> wrote:
> The defects are new, since requesting a single build did not previously unset is_stale.

Good catch, we do need to fix them - sorry that I misunderstood.

Revision history for this message
William Grant (wgrant) wrote :

I'd personally prefer it to be indented like http://pastebin.ubuntu.com/560770/. But this looks fine to land.

review: Approve (code*)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2011-01-14 05:46:14 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-01 01:40:41 +0000
@@ -11,7 +11,17 @@
11 'SourcePackageRecipe',11 'SourcePackageRecipe',
12 ]12 ]
1313
14from datetime import (
15 datetime,
16 timedelta,
17 )
14from lazr.delegates import delegates18from lazr.delegates import delegates
19from pytz import utc
20from storm.expr import (
21 And,
22 Join,
23 RightJoin,
24 )
15from storm.locals import (25from storm.locals import (
16 Bool,26 Bool,
17 Desc,27 Desc,
@@ -56,7 +66,6 @@
56from lp.registry.interfaces.distroseries import IDistroSeriesSet66from lp.registry.interfaces.distroseries import IDistroSeriesSet
57from lp.registry.model.distroseries import DistroSeries67from lp.registry.model.distroseries import DistroSeries
58from lp.soyuz.interfaces.archive import IArchiveSet68from lp.soyuz.interfaces.archive import IArchiveSet
59from lp.soyuz.interfaces.component import IComponentSet
6069
6170
62def get_buildable_distroseries_set(user):71def get_buildable_distroseries_set(user):
@@ -178,8 +187,22 @@
178187
179 @classmethod188 @classmethod
180 def findStaleDailyBuilds(cls):189 def findStaleDailyBuilds(cls):
181 store = IStore(cls)190 one_day_ago = datetime.now(utc) - timedelta(hours=23, minutes=50)
182 return store.find(cls, cls.is_stale == True, cls.build_daily == True)191 joins = RightJoin(
192 Join(
193 Join(SourcePackageRecipeBuild, PackageBuild,
194 PackageBuild.id ==
195 SourcePackageRecipeBuild.package_build_id),
196 BuildFarmJob,
197 And(BuildFarmJob.id == PackageBuild.build_farm_job_id,
198 BuildFarmJob.date_created > one_day_ago)),
199 SourcePackageRecipe,
200 And(SourcePackageRecipeBuild.recipe == SourcePackageRecipe.id,
201 SourcePackageRecipe.daily_build_archive_id ==
202 PackageBuild.archive_id))
203 return IStore(cls).using(joins).find(
204 cls, cls.is_stale == True, cls.build_daily == True,
205 BuildFarmJob.date_created == None).config(distinct=True)
183206
184 @staticmethod207 @staticmethod
185 def exists(owner, name):208 def exists(owner, name):
@@ -216,14 +239,14 @@
216 raise ValueError('Source package recipe builds disabled.')239 raise ValueError('Source package recipe builds disabled.')
217 if not archive.is_ppa:240 if not archive.is_ppa:
218 raise NonPPABuildRequest241 raise NonPPABuildRequest
219 component = getUtility(IComponentSet)["multiverse"]
220242
221 buildable_distros = get_buildable_distroseries_set(archive.owner)243 buildable_distros = get_buildable_distroseries_set(archive.owner)
222 if distroseries not in buildable_distros:244 if distroseries not in buildable_distros:
223 raise BuildNotAllowedForDistro(self, distroseries)245 raise BuildNotAllowedForDistro(self, distroseries)
224246
225 reject_reason = archive.checkUpload(247 reject_reason = archive.checkUpload(
226 requester, self.distroseries, None, component, pocket)248 requester, self.distroseries, None, archive.default_component,
249 pocket)
227 if reject_reason is not None:250 if reject_reason is not None:
228 raise reject_reason251 raise reject_reason
229 if self.isOverQuota(requester, distroseries):252 if self.isOverQuota(requester, distroseries):
@@ -271,14 +294,8 @@
271294
272 def getLastBuild(self):295 def getLastBuild(self):
273 """See `ISourcePackageRecipeBuild`."""296 """See `ISourcePackageRecipeBuild`."""
274 store = Store.of(self)297 return self._getBuilds(
275 result = store.find(298 True, Desc(BuildFarmJob.date_finished)).first()
276 (SourcePackageRecipeBuild),
277 SourcePackageRecipeBuild.recipe == self,
278 SourcePackageRecipeBuild.package_build_id == PackageBuild.id,
279 PackageBuild.build_farm_job_id == BuildFarmJob.id)
280 result.order_by(Desc(BuildFarmJob.date_finished))
281 return result.first()
282299
283 def getMedianBuildDuration(self):300 def getMedianBuildDuration(self):
284 """Return the median duration of builds of this recipe."""301 """Return the median duration of builds of this recipe."""
285302
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2011-01-20 22:01:29 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2011-02-01 01:40:41 +0000
@@ -10,7 +10,10 @@
10 'SourcePackageRecipeBuild',10 'SourcePackageRecipeBuild',
11 ]11 ]
1212
13import datetime13from datetime import (
14 datetime,
15 timedelta,
16 )
14import logging17import logging
15import sys18import sys
1619
@@ -259,9 +262,9 @@
259 def getRecentBuilds(cls, requester, recipe, distroseries, _now=None):262 def getRecentBuilds(cls, requester, recipe, distroseries, _now=None):
260 from lp.buildmaster.model.buildfarmjob import BuildFarmJob263 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
261 if _now is None:264 if _now is None:
262 _now = datetime.datetime.now(utc)265 _now = datetime.now(utc)
263 store = IMasterStore(SourcePackageRecipeBuild)266 store = IMasterStore(SourcePackageRecipeBuild)
264 old_threshold = _now - datetime.timedelta(days=1)267 old_threshold = _now - timedelta(days=1)
265 return store.find(cls, cls.distroseries_id == distroseries.id,268 return store.find(cls, cls.distroseries_id == distroseries.id,
266 cls.requester_id == requester.id, cls.recipe_id == recipe.id,269 cls.requester_id == requester.id, cls.recipe_id == recipe.id,
267 BuildFarmJob.date_created > old_threshold,270 BuildFarmJob.date_created > old_threshold,
@@ -282,7 +285,7 @@
282 median = self.recipe.getMedianBuildDuration()285 median = self.recipe.getMedianBuildDuration()
283 if median is not None:286 if median is not None:
284 return median287 return median
285 return datetime.timedelta(minutes=10)288 return timedelta(minutes=10)
286289
287 def verifySuccessfulUpload(self):290 def verifySuccessfulUpload(self):
288 return self.source_package_release is not None291 return self.source_package_release is not None
289292
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-01-13 03:53:53 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-01 01:40:41 +0000
@@ -561,6 +561,22 @@
561 self.assertContentEqual([stale_daily],561 self.assertContentEqual([stale_daily],
562 SourcePackageRecipe.findStaleDailyBuilds())562 SourcePackageRecipe.findStaleDailyBuilds())
563563
564 def test_findStaleDailyBuildsDistinct(self):
565 # If a recipe has 2 builds due to 2 distroseries, it only returns
566 # one recipe.
567 recipe = self.factory.makeSourcePackageRecipe(
568 build_daily=True, is_stale=True)
569 hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")
570 recipe.distroseries.add(hoary)
571 for series in recipe.distroseries:
572 build = recipe.requestBuild(
573 recipe.daily_build_archive, recipe.owner,
574 series, PackagePublishingPocket.RELEASE)
575 removeSecurityProxy(build).date_created = (
576 datetime.now(UTC) - timedelta(hours=24, seconds=1))
577 stale_recipes = SourcePackageRecipe.findStaleDailyBuilds()
578 self.assertEqual([recipe], list(stale_recipes))
579
564 def test_getMedianBuildDuration(self):580 def test_getMedianBuildDuration(self):
565581
566 def set_duration(build, minutes):582 def set_duration(build, minutes):
567583
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-01-20 22:01:29 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2011-02-01 01:40:41 +0000
@@ -5,9 +5,12 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import datetime8from datetime import (
9 datetime,
10 timedelta,
11 )
12from pytz import utc
9import re13import re
10import unittest
1114
12from storm.locals import Store15from storm.locals import Store
13import transaction16import transaction
@@ -177,16 +180,13 @@
177 # If there are no successful builds, estimate 10 minutes.180 # If there are no successful builds, estimate 10 minutes.
178 spb = self.makeSourcePackageRecipeBuild()181 spb = self.makeSourcePackageRecipeBuild()
179 cur_date = self.factory.getUniqueDate()182 cur_date = self.factory.getUniqueDate()
180 self.assertEqual(183 self.assertEqual(timedelta(minutes=10), spb.estimateDuration())
181 datetime.timedelta(minutes=10), spb.estimateDuration())
182 for minutes in [20, 5, 1]:184 for minutes in [20, 5, 1]:
183 build = removeSecurityProxy(185 build = removeSecurityProxy(
184 self.factory.makeSourcePackageRecipeBuild(recipe=spb.recipe))186 self.factory.makeSourcePackageRecipeBuild(recipe=spb.recipe))
185 build.date_started = cur_date187 build.date_started = cur_date
186 build.date_finished = (188 build.date_finished = cur_date + timedelta(minutes=minutes)
187 cur_date + datetime.timedelta(minutes=minutes))189 self.assertEqual(timedelta(minutes=5), spb.estimateDuration())
188 self.assertEqual(
189 datetime.timedelta(minutes=5), spb.estimateDuration())
190190
191 def test_getFileByName(self):191 def test_getFileByName(self):
192 """getFileByName returns the logs when requested by name."""192 """getFileByName returns the logs when requested by name."""
@@ -269,20 +269,74 @@
269 SourcePackageRecipeBuild.makeDailyBuilds()[0]269 SourcePackageRecipeBuild.makeDailyBuilds()[0]
270 self.assertFalse(recipe.is_stale)270 self.assertFalse(recipe.is_stale)
271271
272 def test_makeDailyBuilds_skips_pending(self):272 def test_makeDailyBuilds_skips_if_built_in_last_24_hours(self):
273 """When creating daily builds, skip ones that are already pending."""273 # We won't create a build during makeDailyBuilds() if the recipe
274 # has been built in the last 24 hours.
274 recipe = self.factory.makeSourcePackageRecipe(275 recipe = self.factory.makeSourcePackageRecipe(
275 build_daily=True, is_stale=True)276 build_daily=True, is_stale=True)
276 first_distroseries = list(recipe.distroseries)[0]
277 recipe.requestBuild(277 recipe.requestBuild(
278 recipe.daily_build_archive, recipe.owner, first_distroseries,278 recipe.daily_build_archive, recipe.owner,
279 list(recipe.distroseries)[0], PackagePublishingPocket.RELEASE)
280 daily_builds = SourcePackageRecipeBuild.makeDailyBuilds()
281 self.assertEqual([], daily_builds)
282
283 def test_makeDailyBuilds_skips_non_stale_builds(self):
284 # If the recipe isn't stale, makeDailyBuilds() won't create a build.
285 self.factory.makeSourcePackageRecipe(
286 build_daily=True, is_stale=False)
287 daily_builds = SourcePackageRecipeBuild.makeDailyBuilds()
288 self.assertEqual([], daily_builds)
289
290 def test_makeDailyBuilds_with_an_older_build(self):
291 # If a previous build is more than 24 hours old, and the recipe is
292 # stale, we'll fire another off.
293 recipe = self.factory.makeSourcePackageRecipe(
294 build_daily=True, is_stale=True)
295 build = recipe.requestBuild(
296 recipe.daily_build_archive, recipe.owner,
297 list(recipe.distroseries)[0], PackagePublishingPocket.RELEASE)
298 nb = removeSecurityProxy(build)
299 nb.date_created = datetime.now(utc) - timedelta(hours=24, seconds=1)
300 # The build also needs to be completed
301 nb.status = BuildStatus.FULLYBUILT
302 daily_builds = SourcePackageRecipeBuild.makeDailyBuilds()
303 self.assertEquals(1, len(daily_builds))
304 actual_title = [b.title for b in daily_builds]
305 self.assertEquals([build.title], actual_title)
306
307 def test_makeDailyBuilds_with_an_older_and_newer_build(self):
308 # If a recipe has been built twice, and the most recent build is
309 # within 24 hours, makeDailyBuilds() won't create a build.
310 recipe = self.factory.makeSourcePackageRecipe(
311 build_daily=True, is_stale=True)
312 for timediff in (timedelta(hours=24, seconds=1), timedelta(hours=8)):
313 build = recipe.requestBuild(
314 recipe.daily_build_archive, recipe.owner,
315 list(recipe.distroseries)[0],
316 PackagePublishingPocket.RELEASE)
317 nb = removeSecurityProxy(build)
318 nb.date_created = datetime.now(utc) - timediff
319 # The build also needs to be completed
320 nb.status = BuildStatus.FULLYBUILT
321 daily_builds = SourcePackageRecipeBuild.makeDailyBuilds()
322 self.assertEquals([], list(daily_builds))
323
324 def test_makeDailyBuilds_with_new_build_different_archive(self):
325 # If a recipe has been built into an archive that isn't the
326 # daily_build_archive, we will create a build.
327 recipe = self.factory.makeSourcePackageRecipe(
328 build_daily=True, is_stale=True)
329 archive = self.factory.makeArchive(owner=recipe.owner)
330 build = recipe.requestBuild(
331 archive, recipe.owner, list(recipe.distroseries)[0],
279 PackagePublishingPocket.RELEASE)332 PackagePublishingPocket.RELEASE)
280 second_distroseries = \333 nb = removeSecurityProxy(build)
281 self.factory.makeSourcePackageRecipeDistroseries("hoary")334 nb.date_created = datetime.now(utc) - timedelta(hours=8)
282 recipe.distroseries.add(second_distroseries)335 # The build also needs to be completed
283 builds = SourcePackageRecipeBuild.makeDailyBuilds()336 nb.status = BuildStatus.FULLYBUILT
284 self.assertEqual(337 daily_builds = SourcePackageRecipeBuild.makeDailyBuilds()
285 [second_distroseries], [build.distroseries for build in builds])338 actual_title = [b.title for b in daily_builds]
339 self.assertEquals([build.title], actual_title)
286340
287 def test_getRecentBuilds(self):341 def test_getRecentBuilds(self):
288 """Recent builds match the same person, series and receipe.342 """Recent builds match the same person, series and receipe.
@@ -306,12 +360,12 @@
306 return SourcePackageRecipeBuild.getRecentBuilds(360 return SourcePackageRecipeBuild.getRecentBuilds(
307 requester, recipe, series, _now=now)361 requester, recipe, series, _now=now)
308 self.assertContentEqual([], get_recent())362 self.assertContentEqual([], get_recent())
309 yesterday = now - datetime.timedelta(days=1)363 yesterday = now - timedelta(days=1)
310 recent_build = self.factory.makeSourcePackageRecipeBuild(364 recent_build = self.factory.makeSourcePackageRecipeBuild(
311 recipe=recipe, distroseries=series, requester=requester,365 recipe=recipe, distroseries=series, requester=requester,
312 date_created=yesterday)366 date_created=yesterday)
313 self.assertContentEqual([], get_recent())367 self.assertContentEqual([], get_recent())
314 a_second = datetime.timedelta(seconds=1)368 a_second = timedelta(seconds=1)
315 removeSecurityProxy(recent_build).date_created += a_second369 removeSecurityProxy(recent_build).date_created += a_second
316 self.assertContentEqual([recent_build], get_recent())370 self.assertContentEqual([recent_build], get_recent())
317371
@@ -363,7 +417,6 @@
363 # getSpecificJob returns the SourcePackageRecipeBuild417 # getSpecificJob returns the SourcePackageRecipeBuild
364 sprb = self.makeSourcePackageRecipeBuild()418 sprb = self.makeSourcePackageRecipeBuild()
365 Store.of(sprb).flush()419 Store.of(sprb).flush()
366 build = sprb.build_farm_job
367 job = sprb.build_farm_job.getSpecificJob()420 job = sprb.build_farm_job.getSpecificJob()
368 self.assertEqual(sprb, job)421 self.assertEqual(sprb, job)
369422
@@ -443,7 +496,7 @@
443 build = self.factory.makeSourcePackageRecipeBuild(496 build = self.factory.makeSourcePackageRecipeBuild(
444 distroseries=distroseries,497 distroseries=distroseries,
445 status=BuildStatus.FULLYBUILT,498 status=BuildStatus.FULLYBUILT,
446 duration=datetime.timedelta(minutes=5))499 duration=timedelta(minutes=5))
447 build.queueBuild(build)500 build.queueBuild(build)
448 return build501 return build
449502
@@ -456,7 +509,3 @@
456class TestHandleStatusForSPRBuild(509class TestHandleStatusForSPRBuild(
457 MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase):510 MakeSPRecipeBuildMixin, TestHandleStatusMixin, TrialTestCase):
458 """IPackageBuild.handleStatus works with SPRecipe builds."""511 """IPackageBuild.handleStatus works with SPRecipe builds."""
459
460
461def test_suite():
462 return unittest.TestLoader().loadTestsFromName(__name__)