Merge lp:~cjwatson/launchpad/snap-revision-id into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18366
Proposed branch: lp:~cjwatson/launchpad/snap-revision-id
Merge into: lp:launchpad
Diff against target: 254 lines (+96/-18)
9 files modified
lib/lp/buildmaster/interactor.py (+7/-10)
lib/lp/buildmaster/interfaces/buildqueue.py (+4/-1)
lib/lp/buildmaster/model/buildqueue.py (+14/-1)
lib/lp/buildmaster/tests/test_manager.py (+37/-6)
lib/lp/snappy/browser/tests/test_snapbuild.py (+10/-0)
lib/lp/snappy/interfaces/snapbuild.py (+6/-0)
lib/lp/snappy/model/snapbuild.py (+6/-0)
lib/lp/snappy/templates/snapbuild-index.pt (+3/-0)
lib/lp/snappy/tests/test_snapbuild.py (+9/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-revision-id
Reviewer Review Type Date Requested Status
William Grant (community) code Approve
Review via email: mp+321690@code.launchpad.net

Commit message

Populate SnapBuild.revision_id from information returned by the builder.

To post a comment you must log in.
Revision history for this message
Adam Collard (adam-collard) :
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/buildmaster/interactor.py'
2--- lib/lp/buildmaster/interactor.py 2016-06-06 12:55:36 +0000
3+++ lib/lp/buildmaster/interactor.py 2017-04-27 15:55:40 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 __metaclass__ = type
10@@ -536,15 +536,12 @@
11 # matches the DB, and this method isn't called unless the DB
12 # says there's a job.
13 builder_status = slave_status['builder_status']
14- if builder_status == 'BuilderStatus.BUILDING':
15- # Build still building, collect the logtail.
16- vitals.build_queue.logtail = str(
17- slave_status.get('logtail')).decode('UTF-8', errors='replace')
18- transaction.commit()
19- elif builder_status == 'BuilderStatus.ABORTING':
20- # Build is being aborted.
21- vitals.build_queue.logtail = (
22- "Waiting for slave process to be terminated")
23+ if builder_status in (
24+ 'BuilderStatus.BUILDING', 'BuilderStatus.ABORTING'):
25+ vitals.build_queue.collectStatus(slave_status)
26+ vitals.build_queue.specific_build.updateStatus(
27+ vitals.build_queue.specific_build.status,
28+ slave_status=slave_status)
29 transaction.commit()
30 elif builder_status == 'BuilderStatus.WAITING':
31 # Build has finished. Delegate handling to the build itself.
32
33=== modified file 'lib/lp/buildmaster/interfaces/buildqueue.py'
34--- lib/lp/buildmaster/interfaces/buildqueue.py 2015-04-20 09:48:57 +0000
35+++ lib/lp/buildmaster/interfaces/buildqueue.py 2017-04-27 15:55:40 +0000
36@@ -1,4 +1,4 @@
37-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
38+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
39 # GNU Affero General Public License version 3 (see the file LICENSE).
40
41 """Build interfaces."""
42@@ -86,6 +86,9 @@
43 def markAsBuilding(builder):
44 """Set this queue item to a 'building' state."""
45
46+ def collectStatus(slave_status):
47+ """Collect status information from the builder."""
48+
49 def suspend():
50 """Suspend this waiting job, removing it from the active queue."""
51
52
53=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
54--- lib/lp/buildmaster/model/buildqueue.py 2016-05-14 00:25:07 +0000
55+++ lib/lp/buildmaster/model/buildqueue.py 2017-04-27 15:55:40 +0000
56@@ -1,4 +1,4 @@
57-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
58+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
59 # GNU Affero General Public License version 3 (see the file LICENSE).
60
61 __metaclass__ = type
62@@ -180,6 +180,19 @@
63 if builder is not None:
64 del get_property_cache(builder).currentjob
65
66+ def collectStatus(self, slave_status):
67+ """See `IBuildQueue`."""
68+ builder_status = slave_status["builder_status"]
69+ if builder_status == "BuilderStatus.ABORTING":
70+ self.logtail = "Waiting for slave process to be terminated"
71+ elif slave_status.get("logtail") is not None:
72+ # slave_status["logtail"] is normally an xmlrpclib.Binary
73+ # instance, and the contents might include invalid UTF-8 due to
74+ # being a fixed number of bytes from the tail of the log. Turn
75+ # it into Unicode as best we can.
76+ self.logtail = str(
77+ slave_status.get("logtail")).decode("UTF-8", errors="replace")
78+
79 def suspend(self):
80 """See `IBuildQueue`."""
81 if self.status != BuildQueueStatus.WAITING:
82
83=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
84--- lib/lp/buildmaster/tests/test_manager.py 2016-06-06 12:50:55 +0000
85+++ lib/lp/buildmaster/tests/test_manager.py 2017-04-27 15:55:40 +0000
86@@ -2,7 +2,7 @@
87 # NOTE: The first line above must stay first; do not move the copyright
88 # notice to the top. See http://www.python.org/dev/peps/pep-0263/.
89 #
90-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
91+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
92 # GNU Affero General Public License version 3 (see the file LICENSE).
93
94 """Tests for the renovated slave scanner aka BuilddManager."""
95@@ -113,11 +113,11 @@
96 """
97 super(TestSlaveScannerScan, self).setUp()
98 # Creating the required chroots needed for dispatching.
99- test_publisher = make_publisher()
100+ self.test_publisher = make_publisher()
101 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
102 hoary = ubuntu.getSeries('hoary')
103- test_publisher.setUpDefaultDistroSeries(hoary)
104- test_publisher.addFakeChroots(db_only=True)
105+ self.test_publisher.setUpDefaultDistroSeries(hoary)
106+ self.test_publisher.addFakeChroots(db_only=True)
107
108 def _resetBuilder(self, builder):
109 """Reset the given builder and its job."""
110@@ -139,8 +139,7 @@
111 self.assertEqual(job.builder, builder)
112 self.assertTrue(job.date_started is not None)
113 self.assertEqual(job.status, BuildQueueStatus.RUNNING)
114- build = getUtility(IBinaryPackageBuildSet).getByQueueEntry(job)
115- self.assertEqual(build.status, BuildStatus.BUILDING)
116+ self.assertEqual(job.specific_build.status, BuildStatus.BUILDING)
117 self.assertEqual(job.logtail, logtail)
118
119 def _getScanner(self, builder_name=None, clock=None, builder_factory=None):
120@@ -397,6 +396,38 @@
121 self.assertEqual(2, fake_scan.call_count)
122
123 @defer.inlineCallbacks
124+ def test_scan_of_snap_build(self):
125+ # Snap builds return additional status information, which the scan
126+ # collects.
127+ class SnapBuildingSlave(BuildingSlave):
128+ revision_id = None
129+
130+ @defer.inlineCallbacks
131+ def status(self):
132+ status = yield super(SnapBuildingSlave, self).status()
133+ status["revision_id"] = self.revision_id
134+ defer.returnValue(status)
135+
136+ build = self.factory.makeSnapBuild(
137+ distroarchseries=self.test_publisher.distroseries.architectures[0])
138+ job = build.queueBuild()
139+ builder = self.factory.makeBuilder(
140+ processors=[job.processor], vm_host="fake_vm_host")
141+ job.markAsBuilding(builder)
142+ slave = SnapBuildingSlave(build_id="SNAPBUILD-%d" % build.id)
143+ self.patch(BuilderSlave, "makeBuilderSlave", FakeMethod(slave))
144+ transaction.commit()
145+ scanner = self._getScanner(builder_name=builder.name)
146+ yield scanner.scan()
147+ self.assertBuildingJob(job, builder, logtail="This is a build log: 0")
148+ self.assertIsNone(build.revision_id)
149+ slave.revision_id = "dummy"
150+ scanner = self._getScanner(builder_name=builder.name)
151+ yield scanner.scan()
152+ self.assertBuildingJob(job, builder, logtail="This is a build log: 1")
153+ self.assertEqual("dummy", build.revision_id)
154+
155+ @defer.inlineCallbacks
156 def _assertFailureCounting(self, builder_count, job_count,
157 expected_builder_count, expected_job_count):
158 # If scan() fails with an exception, failure_counts should be
159
160=== modified file 'lib/lp/snappy/browser/tests/test_snapbuild.py'
161--- lib/lp/snappy/browser/tests/test_snapbuild.py 2017-01-27 12:44:41 +0000
162+++ lib/lp/snappy/browser/tests/test_snapbuild.py 2017-04-27 15:55:40 +0000
163@@ -77,6 +77,16 @@
164 build_view = create_initialized_view(build, "+index")
165 self.assertEqual([], build_view.files)
166
167+ def test_revision_id(self):
168+ build = self.factory.makeSnapBuild()
169+ build.updateStatus(
170+ BuildStatus.FULLYBUILT, slave_status={"revision_id": "dummy"})
171+ build_view = create_initialized_view(build, "+index")
172+ self.assertThat(build_view(), soupmatchers.HTMLContains(
173+ soupmatchers.Tag(
174+ "revision ID", "li", attrs={"id": "revision-id"},
175+ text=re.compile(r"^\s*Revision: dummy\s*$"))))
176+
177 def test_store_upload_status_in_progress(self):
178 build = self.factory.makeSnapBuild(status=BuildStatus.FULLYBUILT)
179 getUtility(ISnapStoreUploadJobSource).create(build)
180
181=== modified file 'lib/lp/snappy/interfaces/snapbuild.py'
182--- lib/lp/snappy/interfaces/snapbuild.py 2017-04-03 14:43:22 +0000
183+++ lib/lp/snappy/interfaces/snapbuild.py 2017-04-27 15:55:40 +0000
184@@ -175,6 +175,12 @@
185 title=_("The date when the build completed or is estimated to "
186 "complete."), readonly=True)
187
188+ revision_id = exported(TextLine(
189+ title=_("Revision ID"), required=False, readonly=True,
190+ description=_(
191+ "The revision ID of the branch used for this build, if "
192+ "available.")))
193+
194 store_upload_jobs = CollectionField(
195 title=_("Store upload jobs for this build."),
196 # Really ISnapStoreUploadJob.
197
198=== modified file 'lib/lp/snappy/model/snapbuild.py'
199--- lib/lp/snappy/model/snapbuild.py 2017-04-03 14:43:22 +0000
200+++ lib/lp/snappy/model/snapbuild.py 2017-04-27 15:55:40 +0000
201@@ -158,6 +158,8 @@
202
203 status = DBEnum(name='status', enum=BuildStatus, allow_none=False)
204
205+ revision_id = Unicode(name='revision_id')
206+
207 log_id = Int(name='log')
208 log = Reference(log_id, 'LibraryFileAlias.id')
209
210@@ -337,6 +339,10 @@
211 status, builder=builder, slave_status=slave_status,
212 date_started=date_started, date_finished=date_finished,
213 force_invalid_transition=force_invalid_transition)
214+ if slave_status is not None:
215+ revision_id = slave_status.get("revision_id")
216+ if revision_id is not None:
217+ self.revision_id = unicode(revision_id)
218 notify(SnapBuildStatusChangedEvent(self))
219
220 def notify(self, extra_info=None):
221
222=== modified file 'lib/lp/snappy/templates/snapbuild-index.pt'
223--- lib/lp/snappy/templates/snapbuild-index.pt 2017-02-27 18:46:38 +0000
224+++ lib/lp/snappy/templates/snapbuild-index.pt 2017-04-27 15:55:40 +0000
225@@ -112,6 +112,9 @@
226 </p>
227
228 <ul>
229+ <li id="revision-id" tal:condition="context/revision_id">
230+ Revision: <span tal:replace="context/revision_id" />
231+ </li>
232 <li tal:condition="context/dependencies">
233 Missing build dependencies: <em tal:content="context/dependencies"/>
234 </li>
235
236=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
237--- lib/lp/snappy/tests/test_snapbuild.py 2017-03-20 00:03:52 +0000
238+++ lib/lp/snappy/tests/test_snapbuild.py 2017-04-27 15:55:40 +0000
239@@ -218,6 +218,15 @@
240 self.factory.makeSnapFile(snapbuild=self.build)
241 self.assertTrue(self.build.verifySuccessfulUpload())
242
243+ def test_updateStatus_stores_revision_id(self):
244+ # If the builder reports a revision_id, updateStatus saves it.
245+ self.assertIsNone(self.build.revision_id)
246+ self.build.updateStatus(BuildStatus.BUILDING, slave_status={})
247+ self.assertIsNone(self.build.revision_id)
248+ self.build.updateStatus(
249+ BuildStatus.BUILDING, slave_status={"revision_id": "dummy"})
250+ self.assertEqual("dummy", self.build.revision_id)
251+
252 def test_updateStatus_triggers_webhooks(self):
253 # Updating the status of a SnapBuild triggers webhooks on the
254 # corresponding Snap.