Merge lp:~cjwatson/launchpad/snap-auto-build-code into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18118
Proposed branch: lp:~cjwatson/launchpad/snap-auto-build-code
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/snap-auto-build-model
Diff against target: 464 lines (+179/-18)
15 files modified
cronscripts/request_daily_builds.py (+7/-3)
database/schema/security.cfg (+7/-0)
lib/lp/code/configure.zcml (+3/-0)
lib/lp/code/interfaces/branch.py (+4/-1)
lib/lp/code/interfaces/gitrepository.py (+10/-2)
lib/lp/code/model/branch.py (+6/-2)
lib/lp/code/model/gitrepository.py (+7/-2)
lib/lp/code/model/tests/test_gitrepository.py (+38/-0)
lib/lp/code/scripts/tests/test_request_daily_builds.py (+31/-3)
lib/lp/code/subscribers/git.py (+1/-0)
lib/lp/codehosting/scanner/bzrsync.py (+5/-1)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+27/-0)
lib/lp/snappy/interfaces/snap.py (+7/-2)
lib/lp/snappy/model/snap.py (+5/-2)
lib/lp/snappy/tests/test_snap.py (+21/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-auto-build-code
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+297957@code.launchpad.net

Commit message

Mark snap packages stale when their source branches changed, and dispatch automatic builds for them.

Description of the change

Mark snap packages stale when their source branches changed, and dispatch automatic builds for them.

It's almost like recipes, only not quite because of model differences and an expected desire to have builds appear a bit more responsively. But it's close enough that we can reuse most of the infrastructure.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cronscripts/request_daily_builds.py'
2--- cronscripts/request_daily_builds.py 2013-01-07 02:40:55 +0000
3+++ cronscripts/request_daily_builds.py 2016-06-29 16:28:24 +0000
4@@ -1,9 +1,9 @@
5 #!/usr/bin/python -S
6 #
7-# Copyright 2010 Canonical Ltd. This software is licensed under the
8+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
9 # GNU Affero General Public License version 3 (see the file LICENSE).
10
11-"""Request builds for stale daily build recipes."""
12+"""Request builds for stale daily build recipes and snap packages."""
13
14 __metaclass__ = type
15
16@@ -18,6 +18,7 @@
17 from lp.services.config import config
18 from lp.services.scripts.base import LaunchpadCronScript
19 from lp.services.webapp.errorlog import globalErrorUtility
20+from lp.snappy.interfaces.snap import ISnapSet
21
22
23 class RequestDailyBuilds(LaunchpadCronScript):
24@@ -32,7 +33,10 @@
25 globalErrorUtility.configure(self.name)
26 source = getUtility(ISourcePackageRecipeBuildSource)
27 builds = source.makeDailyBuilds(self.logger)
28- self.logger.info('Requested %d daily builds.' % len(builds))
29+ self.logger.info('Requested %d daily recipe builds.' % len(builds))
30+ builds = getUtility(ISnapSet).makeAutoBuilds(self.logger)
31+ self.logger.info(
32+ 'Requested %d automatic snap package builds.' % len(builds))
33 transaction.commit()
34
35
36
37=== modified file 'database/schema/security.cfg'
38--- database/schema/security.cfg 2016-06-14 12:40:30 +0000
39+++ database/schema/security.cfg 2016-06-29 16:28:24 +0000
40@@ -708,6 +708,8 @@
41 public.revisionparent = SELECT, INSERT
42 public.revisionproperty = SELECT, INSERT
43 public.seriessourcepackagebranch = SELECT
44+public.snap = SELECT, UPDATE
45+public.snapbuild = SELECT
46 public.sourcepackagename = SELECT
47 public.sourcepackagerecipe = SELECT, UPDATE
48 public.sourcepackagerecipedata = SELECT
49@@ -827,8 +829,13 @@
50 public.distroarchseries = SELECT
51 public.distroseries = SELECT
52 public.job = SELECT, INSERT
53+public.libraryfilealias = SELECT
54 public.person = SELECT
55+public.pocketchroot = SELECT
56 public.processor = SELECT
57+public.snap = SELECT, UPDATE
58+public.snaparch = SELECT
59+public.snapbuild = SELECT, INSERT
60 public.sourcepackagename = SELECT
61 public.sourcepackagerecipe = SELECT, UPDATE
62 public.sourcepackagerecipebuild = SELECT, INSERT
63
64=== modified file 'lib/lp/code/configure.zcml'
65--- lib/lp/code/configure.zcml 2016-02-28 19:33:42 +0000
66+++ lib/lp/code/configure.zcml 2016-06-29 16:28:24 +0000
67@@ -423,6 +423,9 @@
68 handler="lp.codehosting.scanner.bzrsync.update_recipes"/>
69 <subscriber
70 for="lp.codehosting.scanner.events.ITipChanged"
71+ handler="lp.codehosting.scanner.bzrsync.update_snaps"/>
72+ <subscriber
73+ for="lp.codehosting.scanner.events.ITipChanged"
74 handler="lp.codehosting.scanner.bzrsync.trigger_webhooks"/>
75 <subscriber
76 for="lp.codehosting.scanner.events.IRevisionsRemoved"
77
78=== modified file 'lib/lp/code/interfaces/branch.py'
79--- lib/lp/code/interfaces/branch.py 2015-10-12 12:58:32 +0000
80+++ lib/lp/code/interfaces/branch.py 2016-06-29 16:28:24 +0000
81@@ -1,4 +1,4 @@
82-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
83+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
84 # GNU Affero General Public License version 3 (see the file LICENSE).
85
86 """Branch interfaces."""
87@@ -674,6 +674,9 @@
88 def markRecipesStale():
89 """Mark all recipes associated with this branch as stale."""
90
91+ def markSnapsStale():
92+ """Mark all snap packages associated with this branch as stale."""
93+
94 def getStackedBranches():
95 """The branches that are stacked on this one."""
96
97
98=== modified file 'lib/lp/code/interfaces/gitrepository.py'
99--- lib/lp/code/interfaces/gitrepository.py 2016-05-14 09:54:32 +0000
100+++ lib/lp/code/interfaces/gitrepository.py 2016-06-29 16:28:24 +0000
101@@ -520,8 +520,16 @@
102 """Mark recipes associated with this repository as stale.
103
104 :param paths: A list of reference paths. Any recipes that include
105- an entry that points to this repository and that has a `revspec`
106- that is one of these paths will be marked as stale.
107+ an entry that points to this repository and that have a
108+ `revspec` that is one of these paths will be marked as stale.
109+ """
110+
111+ def markSnapsStale(paths):
112+ """Mark snap packages associated with this repository as stale.
113+
114+ :param paths: A list of reference paths. Any snap packages that
115+ include an entry that points to this repository and that are
116+ based on one of these paths will be marked as stale.
117 """
118
119 def detectMerges(paths, logger=None):
120
121=== modified file 'lib/lp/code/model/branch.py'
122--- lib/lp/code/model/branch.py 2016-06-01 17:24:35 +0000
123+++ lib/lp/code/model/branch.py 2016-06-29 16:28:24 +0000
124@@ -184,6 +184,7 @@
125 from lp.services.webapp.interfaces import ILaunchBag
126 from lp.services.webhooks.interfaces import IWebhookSet
127 from lp.services.webhooks.model import WebhookTargetMixin
128+from lp.snappy.interfaces.snap import ISnapSet
129
130
131 @implementer(IBranch, IPrivacy, IInformationType)
132@@ -667,6 +668,11 @@
133 for recipe in self._recipes:
134 recipe.is_stale = True
135
136+ def markSnapsStale(self):
137+ """See `IBranch`."""
138+ for snap in getUtility(ISnapSet).findByBranch(self):
139+ snap.is_stale = True
140+
141 def addToLaunchBag(self, launchbag):
142 """See `IBranch`."""
143 launchbag.add(self.product)
144@@ -821,8 +827,6 @@
145 As well as the dictionaries, this method returns two list of callables
146 that may be called to perform the alterations and deletions needed.
147 """
148- from lp.snappy.interfaces.snap import ISnapSet
149-
150 alteration_operations = []
151 deletion_operations = []
152 # Merge proposals require their source and target branches to exist.
153
154=== modified file 'lib/lp/code/model/gitrepository.py'
155--- lib/lp/code/model/gitrepository.py 2016-05-14 09:54:32 +0000
156+++ lib/lp/code/model/gitrepository.py 2016-06-29 16:28:24 +0000
157@@ -152,6 +152,7 @@
158 from lp.services.webapp.authorization import available_with_permission
159 from lp.services.webhooks.interfaces import IWebhookSet
160 from lp.services.webhooks.model import WebhookTargetMixin
161+from lp.snappy.interfaces.snap import ISnapSet
162
163
164 object_type_map = {
165@@ -1004,6 +1005,12 @@
166 for recipe in self._getRecipes(paths):
167 recipe.is_stale = True
168
169+ def markSnapsStale(self, paths):
170+ """See `IGitRepository`."""
171+ snap_set = getUtility(ISnapSet)
172+ for snap in snap_set.findByGitRepository(self, paths=paths):
173+ snap.is_stale = True
174+
175 def _markProposalMerged(self, proposal, merged_revision_id, logger=None):
176 if logger is not None:
177 logger.info(
178@@ -1058,8 +1065,6 @@
179 As well as the dictionaries, this method returns two list of callables
180 that may be called to perform the alterations and deletions needed.
181 """
182- from lp.snappy.interfaces.snap import ISnapSet
183-
184 alteration_operations = []
185 deletion_operations = []
186 # Merge proposals require their source and target repositories to
187
188=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
189--- lib/lp/code/model/tests/test_gitrepository.py 2016-05-13 10:58:59 +0000
190+++ lib/lp/code/model/tests/test_gitrepository.py 2016-06-29 16:28:24 +0000
191@@ -1946,6 +1946,44 @@
192 self.assertFalse(recipe.is_stale)
193
194
195+class TestGitRepositoryMarkSnapsStale(TestCaseWithFactory):
196+
197+ layer = ZopelessDatabaseLayer
198+
199+ def setUp(self):
200+ super(TestGitRepositoryMarkSnapsStale, self).setUp()
201+ self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
202+
203+ def test_same_repository(self):
204+ # On ref changes, snap packages using this ref become stale.
205+ [ref] = self.factory.makeGitRefs()
206+ snap = self.factory.makeSnap(git_ref=ref)
207+ removeSecurityProxy(snap).is_stale = False
208+ ref.repository.createOrUpdateRefs(
209+ {ref.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
210+ self.assertTrue(snap.is_stale)
211+
212+ def test_same_repository_different_ref(self):
213+ # On ref changes, snap packages using a different ref in the same
214+ # repository are left alone.
215+ ref1, ref2 = self.factory.makeGitRefs(
216+ paths=[u"refs/heads/a", u"refs/heads/b"])
217+ snap = self.factory.makeSnap(git_ref=ref1)
218+ removeSecurityProxy(snap).is_stale = False
219+ ref1.repository.createOrUpdateRefs(
220+ {ref2.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
221+ self.assertFalse(snap.is_stale)
222+
223+ def test_different_repository(self):
224+ # On ref changes, unrelated snap packages are left alone.
225+ [ref] = self.factory.makeGitRefs()
226+ snap = self.factory.makeSnap(git_ref=self.factory.makeGitRefs()[0])
227+ removeSecurityProxy(snap).is_stale = False
228+ ref.repository.createOrUpdateRefs(
229+ {ref.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
230+ self.assertFalse(snap.is_stale)
231+
232+
233 class TestGitRepositoryDetectMerges(TestCaseWithFactory):
234
235 layer = LaunchpadZopelessLayer
236
237=== modified file 'lib/lp/code/scripts/tests/test_request_daily_builds.py'
238--- lib/lp/code/scripts/tests/test_request_daily_builds.py 2012-06-29 14:36:44 +0000
239+++ lib/lp/code/scripts/tests/test_request_daily_builds.py 2016-06-29 16:28:24 +0000
240@@ -1,11 +1,13 @@
241-# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
242+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
243 # GNU Affero General Public License version 3 (see the file LICENSE).
244
245 """Test the request_daily_builds script"""
246
247 import transaction
248
249+from lp.services.features.testing import FeatureFixture
250 from lp.services.scripts.tests import run_script
251+from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
252 from lp.soyuz.enums import ArchivePurpose
253 from lp.testing import TestCaseWithFactory
254 from lp.testing.layers import ZopelessAppServerLayer
255@@ -15,24 +17,49 @@
256
257 layer = ZopelessAppServerLayer
258
259+ def setUp(self):
260+ super(TestRequestDailyBuilds, self).setUp()
261+ self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
262+
263 def test_request_daily_builds(self):
264 """Ensure the request_daily_builds script requests daily builds."""
265+ processor = self.factory.makeProcessor(supports_virtualized=True)
266+ distroarchseries = self.factory.makeDistroArchSeries(
267+ processor=processor)
268+ fake_chroot = self.factory.makeLibraryFileAlias(
269+ filename="fake_chroot.tar.gz", db_only=True)
270+ distroarchseries.addOrUpdateChroot(fake_chroot)
271 prod_branch = self.factory.makeProductBranch()
272 prod_recipe = self.factory.makeSourcePackageRecipe(
273 build_daily=True, is_stale=True, branches=[prod_branch])
274+ prod_snap = self.factory.makeSnap(
275+ distroseries=distroarchseries.distroseries,
276+ processors=[distroarchseries.processor],
277+ auto_build=True, is_stale=True, branch=prod_branch)
278 pack_branch = self.factory.makePackageBranch()
279 pack_recipe = self.factory.makeSourcePackageRecipe(
280 build_daily=True, is_stale=True, branches=[pack_branch])
281+ pack_snap = self.factory.makeSnap(
282+ distroseries=distroarchseries.distroseries,
283+ processors=[distroarchseries.processor],
284+ auto_build=True, is_stale=True, branch=pack_branch)
285 self.assertEqual(0, prod_recipe.pending_builds.count())
286+ self.assertEqual(0, prod_snap.pending_builds.count())
287 self.assertEqual(0, pack_recipe.pending_builds.count())
288+ self.assertEqual(0, pack_snap.pending_builds.count())
289 transaction.commit()
290 retcode, stdout, stderr = run_script(
291 'cronscripts/request_daily_builds.py', [])
292- self.assertIn('Requested 2 daily builds.', stderr)
293+ self.assertIn('Requested 2 daily recipe builds.', stderr)
294+ self.assertIn('Requested 2 automatic snap package builds.', stderr)
295 self.assertEqual(1, prod_recipe.pending_builds.count())
296+ self.assertEqual(1, prod_snap.pending_builds.count())
297 self.assertEqual(1, pack_recipe.pending_builds.count())
298+ self.assertEqual(1, pack_snap.pending_builds.count())
299 self.assertFalse(prod_recipe.is_stale)
300+ self.assertFalse(prod_snap.is_stale)
301 self.assertFalse(pack_recipe.is_stale)
302+ self.assertFalse(pack_snap.is_stale)
303
304 def test_request_daily_builds_oops(self):
305 """Ensure errors are handled cleanly."""
306@@ -43,7 +70,8 @@
307 retcode, stdout, stderr = run_script(
308 'cronscripts/request_daily_builds.py', [])
309 self.assertEqual(0, recipe.pending_builds.count())
310- self.assertIn('Requested 0 daily builds.', stderr)
311+ self.assertIn('Requested 0 daily recipe builds.', stderr)
312+ self.assertIn('Requested 0 automatic snap package builds.', stderr)
313 self.oops_capture.sync()
314 self.assertEqual('NonPPABuildRequest', self.oopses[0]['type'])
315 self.assertEqual(
316
317=== modified file 'lib/lp/code/subscribers/git.py'
318--- lib/lp/code/subscribers/git.py 2016-01-12 04:34:09 +0000
319+++ lib/lp/code/subscribers/git.py 2016-06-29 16:28:24 +0000
320@@ -11,4 +11,5 @@
321 repository.updateMergeCommitIDs(event.paths)
322 repository.scheduleDiffUpdates(event.paths)
323 repository.markRecipesStale(event.paths)
324+ repository.markSnapsStale(event.paths)
325 repository.detectMerges(event.paths, logger=event.logger)
326
327=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
328--- lib/lp/codehosting/scanner/bzrsync.py 2016-05-24 05:29:48 +0000
329+++ lib/lp/codehosting/scanner/bzrsync.py 2016-06-29 16:28:24 +0000
330@@ -1,6 +1,6 @@
331 #!/usr/bin/python
332 #
333-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
334+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
335 # GNU Affero General Public License version 3 (see the file LICENSE).
336
337 """Import version control metadata from a Bazaar branch into the database."""
338@@ -333,6 +333,10 @@
339 tip_changed.db_branch.markRecipesStale()
340
341
342+def update_snaps(tip_changed):
343+ tip_changed.db_branch.markSnapsStale()
344+
345+
346 def trigger_webhooks(tip_changed):
347 old_revid = tip_changed.old_tip_revision_id
348 new_revid = tip_changed.new_tip_revision_id
349
350=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
351--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2016-03-02 21:21:26 +0000
352+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2016-06-29 16:28:24 +0000
353@@ -51,6 +51,7 @@
354 from lp.services.database.interfaces import IStore
355 from lp.services.features.testing import FeatureFixture
356 from lp.services.osutils import override_environ
357+from lp.snappy.interfaces.snap import SNAP_FEATURE_FLAG
358 from lp.testing import TestCaseWithFactory
359 from lp.testing.dbuser import (
360 dbuser,
361@@ -749,6 +750,32 @@
362 self.assertEqual(False, recipe.is_stale)
363
364
365+class TestMarkSnapsStale(BzrSyncTestCase):
366+ """Test that snap packages associated with the branch are marked stale."""
367+
368+ def setUp(self):
369+ super(TestMarkSnapsStale, self).setUp()
370+ self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
371+
372+ @run_as_db_user(config.launchpad.dbuser)
373+ def test_same_branch(self):
374+ # On tip change, snap packages using this branch become stale.
375+ snap = self.factory.makeSnap(branch=self.db_branch)
376+ removeSecurityProxy(snap).is_stale = False
377+ switch_dbuser("branchscanner")
378+ self.makeBzrSync(self.db_branch).syncBranchAndClose()
379+ self.assertTrue(snap.is_stale)
380+
381+ @run_as_db_user(config.launchpad.dbuser)
382+ def test_unrelated_branch(self):
383+ # On tip change, unrelated snap packages are left alone.
384+ snap = self.factory.makeSnap()
385+ removeSecurityProxy(snap).is_stale = False
386+ switch_dbuser("branchscanner")
387+ self.makeBzrSync(self.db_branch).syncBranchAndClose()
388+ self.assertFalse(snap.is_stale)
389+
390+
391 class TestTriggerWebhooks(BzrSyncTestCase):
392 """Test triggering of webhooks."""
393
394
395=== modified file 'lib/lp/snappy/interfaces/snap.py'
396--- lib/lp/snappy/interfaces/snap.py 2016-06-29 16:28:24 +0000
397+++ lib/lp/snappy/interfaces/snap.py 2016-06-29 16:28:24 +0000
398@@ -527,8 +527,13 @@
399 def findByBranch(branch):
400 """Return all snap packages for the given Bazaar branch."""
401
402- def findByGitRepository(repository):
403- """Return all snap packages for the given Git repository."""
404+ def findByGitRepository(repository, paths=None):
405+ """Return all snap packages for the given Git repository.
406+
407+ :param repository: An `IGitRepository`.
408+ :param paths: If not None, only return snap packages for one of
409+ these Git reference paths.
410+ """
411
412 def findByGitRef(ref):
413 """Return all snap packages for the given Git reference."""
414
415=== modified file 'lib/lp/snappy/model/snap.py'
416--- lib/lp/snappy/model/snap.py 2016-06-29 16:28:24 +0000
417+++ lib/lp/snappy/model/snap.py 2016-06-29 16:28:24 +0000
418@@ -628,9 +628,12 @@
419 """See `ISnapSet`."""
420 return IStore(Snap).find(Snap, Snap.branch == branch)
421
422- def findByGitRepository(self, repository):
423+ def findByGitRepository(self, repository, paths=None):
424 """See `ISnapSet`."""
425- return IStore(Snap).find(Snap, Snap.git_repository == repository)
426+ clauses = [Snap.git_repository == repository]
427+ if paths is not None:
428+ clauses.append(Snap.git_path.is_in(paths))
429+ return IStore(Snap).find(Snap, *clauses)
430
431 def findByGitRef(self, ref):
432 """See `ISnapSet`."""
433
434=== modified file 'lib/lp/snappy/tests/test_snap.py'
435--- lib/lp/snappy/tests/test_snap.py 2016-06-29 16:28:24 +0000
436+++ lib/lp/snappy/tests/test_snap.py 2016-06-29 16:28:24 +0000
437@@ -721,6 +721,27 @@
438 self.assertContentEqual(
439 snaps[2:], snap_set.findByGitRepository(repositories[1]))
440
441+ def test_findByGitRepository_paths(self):
442+ # ISnapSet.findByGitRepository can restrict by reference paths.
443+ repositories = [self.factory.makeGitRepository() for i in range(2)]
444+ snaps = []
445+ for repository in repositories:
446+ for i in range(3):
447+ [ref] = self.factory.makeGitRefs(repository=repository)
448+ snaps.append(self.factory.makeSnap(git_ref=ref))
449+ snap_set = getUtility(ISnapSet)
450+ self.assertContentEqual(
451+ [], snap_set.findByGitRepository(repositories[0], paths=[]))
452+ self.assertContentEqual(
453+ [snaps[0]],
454+ snap_set.findByGitRepository(
455+ repositories[0], paths=[snaps[0].git_ref.path]))
456+ self.assertContentEqual(
457+ snaps[:2],
458+ snap_set.findByGitRepository(
459+ repositories[0],
460+ paths=[snaps[0].git_ref.path, snaps[1].git_ref.path]))
461+
462 def test_findByGitRef(self):
463 # ISnapSet.findByGitRef returns all Snaps with the given Git
464 # reference.