Merge lp:~jml/launchpad/buildd-deferred-fo-shizzle into lp:launchpad
- buildd-deferred-fo-shizzle
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Merged at revision: | 11801 |
Proposed branch: | lp:~jml/launchpad/buildd-deferred-fo-shizzle |
Merge into: | lp:launchpad |
Prerequisite: | lp:~jml/launchpad/buildd-deferred |
Diff against target: |
816 lines (+305/-193) 9 files modified
lib/lp/buildmaster/manager.py (+25/-19) lib/lp/buildmaster/model/builder.py (+38/-17) lib/lp/buildmaster/tests/test_builder.py (+138/-43) lib/lp/code/model/recipebuilder.py (+30/-27) lib/lp/soyuz/doc/buildd-slave.txt (+1/-37) lib/lp/soyuz/model/binarypackagebuildbehavior.py (+57/-40) lib/lp/soyuz/scripts/buildd.py (+2/-0) lib/lp/soyuz/tests/soyuzbuilddhelpers.py (+2/-0) lib/lp/translations/model/translationtemplatesbuildbehavior.py (+12/-10) |
To merge this branch: | bzr merge lp:~jml/launchpad/buildd-deferred-fo-shizzle |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+36037@code.launchpad.net |
Commit message
Description of the change
Actually starting to make BuilderSlave have asynchronous APIs.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/buildmaster/manager.py' |
2 | --- lib/lp/buildmaster/manager.py 2010-09-20 10:21:32 +0000 |
3 | +++ lib/lp/buildmaster/manager.py 2010-09-21 09:54:50 +0000 |
4 | @@ -283,15 +283,15 @@ |
5 | """Scan the builder and dispatch to it or deal with failures.""" |
6 | self.logger.debug("Scanning builder: %s" % self.builder_name) |
7 | |
8 | - try: |
9 | - slave = self.scan() |
10 | + d = self.scan() |
11 | + |
12 | + def got_slave(slave): |
13 | if slave is None: |
14 | - self.scheduleNextScanCycle() |
15 | + return self.scheduleNextScanCycle() |
16 | else: |
17 | - # XXX: Ought to return Deferred. |
18 | - self.resumeAndDispatch(slave) |
19 | - except: |
20 | - error = Failure() |
21 | + return self.resumeAndDispatch(slave) |
22 | + |
23 | + def disaster(error): |
24 | self.logger.info("Scanning failed with: %s\n%s" % |
25 | (error.getErrorMessage(), error.getTraceback())) |
26 | |
27 | @@ -307,7 +307,11 @@ |
28 | assessFailureCounts(builder, error.getErrorMessage()) |
29 | transaction.commit() |
30 | |
31 | - self.scheduleNextScanCycle() |
32 | + return self.scheduleNextScanCycle() |
33 | + |
34 | + d.addCallback(got_slave) |
35 | + d.addErrback(disaster) |
36 | + return d |
37 | |
38 | @write_transaction |
39 | def scan(self): |
40 | @@ -346,7 +350,7 @@ |
41 | if self.builder.manual: |
42 | self.logger.debug( |
43 | '%s is in manual mode, not dispatching.' % self.builder.name) |
44 | - return None |
45 | + return defer.succeed(None) |
46 | |
47 | # If the builder is marked unavailable, don't dispatch anything. |
48 | # Additionaly, because builders can be removed from the pool at |
49 | @@ -362,7 +366,7 @@ |
50 | "job" % self.builder.name) |
51 | job.reset() |
52 | transaction.commit() |
53 | - return None |
54 | + return defer.succeed(None) |
55 | |
56 | # See if there is a job we can dispatch to the builder slave. |
57 | |
58 | @@ -374,15 +378,17 @@ |
59 | self.builder.name, self.builder.url, self.builder.vm_host) |
60 | # XXX: Passing buildd_slave=slave overwrites the 'slave' property of |
61 | # self.builder. Not sure why this is needed yet. |
62 | - self.builder.findAndStartJob(buildd_slave=slave) |
63 | - if self.builder.currentjob is not None: |
64 | - # After a successful dispatch we can reset the |
65 | - # failure_count. |
66 | - self.builder.resetFailureCount() |
67 | - transaction.commit() |
68 | - return slave |
69 | - |
70 | - return None |
71 | + d = self.builder.findAndStartJob(buildd_slave=slave) |
72 | + def job_started(candidate): |
73 | + if self.builder.currentjob is not None: |
74 | + # After a successful dispatch we can reset the |
75 | + # failure_count. |
76 | + self.builder.resetFailureCount() |
77 | + transaction.commit() |
78 | + return slave |
79 | + else: |
80 | + return None |
81 | + return d.addCallback(job_started) |
82 | |
83 | def resumeAndDispatch(self, slave): |
84 | """Chain the resume and dispatching Deferreds.""" |
85 | |
86 | === modified file 'lib/lp/buildmaster/model/builder.py' |
87 | --- lib/lp/buildmaster/model/builder.py 2010-09-21 09:05:34 +0000 |
88 | +++ lib/lp/buildmaster/model/builder.py 2010-09-21 09:54:50 +0000 |
89 | @@ -34,6 +34,7 @@ |
90 | Count, |
91 | Sum, |
92 | ) |
93 | +from twisted.internet import defer |
94 | from zope.component import getUtility |
95 | from zope.interface import implements |
96 | |
97 | @@ -161,8 +162,10 @@ |
98 | return self._server.status() |
99 | |
100 | def ensurepresent(self, sha1sum, url, username, password): |
101 | + # XXX: Nothing external calls this. Make it private. |
102 | """Attempt to ensure the given file is present.""" |
103 | - return self._server.ensurepresent(sha1sum, url, username, password) |
104 | + return defer.succeed( |
105 | + self._server.ensurepresent(sha1sum, url, username, password)) |
106 | |
107 | def getFile(self, sha_sum): |
108 | """Construct a file-like object to return the named file.""" |
109 | @@ -201,13 +204,15 @@ |
110 | logger.debug("Asking builder on %s to ensure it has file %s " |
111 | "(%s, %s)" % (self.urlbase, libraryfilealias.filename, |
112 | url, libraryfilealias.content.sha1)) |
113 | - self.sendFileToSlave(libraryfilealias.content.sha1, url) |
114 | + return self.sendFileToSlave(libraryfilealias.content.sha1, url) |
115 | |
116 | def sendFileToSlave(self, sha1, url, username="", password=""): |
117 | """Helper to send the file at 'url' with 'sha1' to this builder.""" |
118 | - present, info = self.ensurepresent(sha1, url, username, password) |
119 | - if not present: |
120 | - raise CannotFetchFile(url, info) |
121 | + d = self.ensurepresent(sha1, url, username, password) |
122 | + def check_present((present, info)): |
123 | + if not present: |
124 | + raise CannotFetchFile(url, info) |
125 | + return d.addCallback(check_present) |
126 | |
127 | def build(self, buildid, builder_type, chroot_sha1, filemap, args): |
128 | """Build a thing on this build slave. |
129 | @@ -464,14 +469,19 @@ |
130 | |
131 | # Do it. |
132 | build_queue_item.markAsBuilding(self) |
133 | - try: |
134 | - self.current_build_behavior.dispatchBuildToSlave( |
135 | - build_queue_item.id, logger) |
136 | - except BuildSlaveFailure, e: |
137 | - logger.debug("Disabling builder: %s" % self.url, exc_info=1) |
138 | + |
139 | + d = self.current_build_behavior.dispatchBuildToSlave( |
140 | + build_queue_item.id, logger) |
141 | + |
142 | + def eb_slave_failure(failure): |
143 | + failure.trap(BuildSlaveFailure) |
144 | + e = failure.value |
145 | self.failBuilder( |
146 | "Exception (%s) when setting up to new job" % (e,)) |
147 | - except CannotFetchFile, e: |
148 | + |
149 | + def eb_cannot_fetch_file(failure): |
150 | + failure.trap(CannotFetchFile) |
151 | + e = failure.value |
152 | message = """Slave '%s' (%s) was unable to fetch file. |
153 | ****** URL ******** |
154 | %s |
155 | @@ -480,11 +490,19 @@ |
156 | ******************* |
157 | """ % (self.name, self.url, e.file_url, e.error_information) |
158 | raise BuildDaemonError(message) |
159 | - except socket.error, e: |
160 | + |
161 | + def eb_socket_error(failure): |
162 | + failure.trap(socket.error) |
163 | + e = failure.value |
164 | error_message = "Exception (%s) when setting up new job" % (e,) |
165 | self.handleTimeout(logger, error_message) |
166 | raise BuildSlaveFailure |
167 | |
168 | + d.addErrback(eb_slave_failure) |
169 | + d.addErrback(eb_cannot_fetch_file) |
170 | + d.addErrback(eb_socket_error) |
171 | + return d |
172 | + |
173 | def failBuilder(self, reason): |
174 | """See IBuilder""" |
175 | # XXX cprov 2007-04-17: ideally we should be able to notify the |
176 | @@ -677,10 +695,13 @@ |
177 | :param candidate: The job to dispatch. |
178 | """ |
179 | logger = self._getSlaveScannerLogger() |
180 | - try: |
181 | - self.startBuild(candidate, logger) |
182 | - except (BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch), err: |
183 | + d = self.startBuild(candidate, logger) |
184 | + def warn_on_error(failure): |
185 | + failure.trap( |
186 | + BuildSlaveFailure, CannotBuild, BuildBehaviorMismatch) |
187 | + err = failure.value |
188 | logger.warn('Could not build: %s' % err) |
189 | + return d.addErrback(warn_on_error) |
190 | |
191 | def handleTimeout(self, logger, error_message): |
192 | """See IBuilder.""" |
193 | @@ -721,8 +742,8 @@ |
194 | if buildd_slave is not None: |
195 | self.setSlaveForTesting(buildd_slave) |
196 | |
197 | - self._dispatchBuildCandidate(candidate) |
198 | - return candidate |
199 | + d = self._dispatchBuildCandidate(candidate) |
200 | + return d.addCallback(lambda ignored: candidate) |
201 | |
202 | def getBuildQueue(self): |
203 | """See `IBuilder`.""" |
204 | |
205 | === modified file 'lib/lp/buildmaster/tests/test_builder.py' |
206 | --- lib/lp/buildmaster/tests/test_builder.py 2010-09-21 09:05:34 +0000 |
207 | +++ lib/lp/buildmaster/tests/test_builder.py 2010-09-21 09:54:50 +0000 |
208 | @@ -8,8 +8,10 @@ |
209 | import socket |
210 | import xmlrpclib |
211 | |
212 | -from testtools.content import Content |
213 | -from testtools.content_type import UTF8_TEXT |
214 | +import fixtures |
215 | + |
216 | +from twisted.trial.unittest import TestCase as TrialTestCase |
217 | +from twisted.web.client import getPage |
218 | |
219 | from zope.component import getUtility |
220 | from zope.security.proxy import removeSecurityProxy |
221 | @@ -24,10 +26,16 @@ |
222 | ) |
223 | from canonical.testing.layers import ( |
224 | DatabaseFunctionalLayer, |
225 | - LaunchpadZopelessLayer |
226 | + LaunchpadZopelessLayer, |
227 | + TwistedLaunchpadZopelessLayer, |
228 | + TwistedLayer, |
229 | ) |
230 | from lp.buildmaster.enums import BuildStatus |
231 | -from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet |
232 | +from lp.buildmaster.interfaces.builder import ( |
233 | + CannotFetchFile, |
234 | + IBuilder, |
235 | + IBuilderSet, |
236 | + ) |
237 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
238 | IBuildFarmJobBehavior, |
239 | ) |
240 | @@ -49,9 +57,12 @@ |
241 | ) |
242 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
243 | from lp.testing import ( |
244 | - TestCase, |
245 | + ANONYMOUS, |
246 | + login_as, |
247 | + logout, |
248 | TestCaseWithFactory, |
249 | ) |
250 | +from lp.testing.factory import LaunchpadObjectFactory |
251 | from lp.testing.fakemethod import FakeMethod |
252 | |
253 | |
254 | @@ -467,19 +478,11 @@ |
255 | self.builder.current_build_behavior, BinaryPackageBuildBehavior) |
256 | |
257 | |
258 | -class TestSlave(TestCase): |
259 | - """ |
260 | - Integration tests for BuilderSlave that verify how it works against a |
261 | - real slave server. |
262 | - """ |
263 | - |
264 | - # XXX: JonathanLange 2010-09-20 bug=643521: There are also tests for |
265 | - # BuilderSlave in buildd-slave.txt and in other places. The tests here |
266 | - # ought to become the canonical tests for BuilderSlave vs running buildd |
267 | - # XML-RPC server interaction. |
268 | +class SlaveTestHelpers(fixtures.Fixture): |
269 | |
270 | # The URL for the XML-RPC service set up by `BuilddSlaveTestSetup`. |
271 | - TEST_URL = 'http://localhost:8221/rpc/' |
272 | + BASE_URL = 'http://localhost:8221' |
273 | + TEST_URL = '%s/rpc/' % (BASE_URL,) |
274 | |
275 | def getServerSlave(self): |
276 | """Set up a test build slave server. |
277 | @@ -488,11 +491,14 @@ |
278 | """ |
279 | tachandler = BuilddSlaveTestSetup() |
280 | tachandler.setUp() |
281 | - def addLogFile(exc_info): |
282 | - self.addDetail( |
283 | - 'xmlrpc-log-file', |
284 | - Content(UTF8_TEXT, lambda: open(tachandler.logfile, 'r').read())) |
285 | - self.addOnException(addLogFile) |
286 | + # Basically impossible to do this w/ TrialTestCase. But it would be |
287 | + # really nice to keep it. |
288 | + # |
289 | + # def addLogFile(exc_info): |
290 | + # self.addDetail( |
291 | + # 'xmlrpc-log-file', |
292 | + # Content(UTF8_TEXT, lambda: open(tachandler.logfile, 'r').read())) |
293 | + # self.addOnException(addLogFile) |
294 | self.addCleanup(tachandler.tearDown) |
295 | return tachandler |
296 | |
297 | @@ -527,7 +533,7 @@ |
298 | :return: The build id returned by the slave. |
299 | """ |
300 | if build_id is None: |
301 | - build_id = self.getUniqueString() |
302 | + build_id = 'random-build-id' |
303 | tachandler = self.getServerSlave() |
304 | chroot_file = 'fake-chroot' |
305 | dsc_file = 'thing' |
306 | @@ -537,10 +543,30 @@ |
307 | build_id, 'debian', chroot_file, {'.dsc': dsc_file}, |
308 | {'ogrecomponent': 'main'}) |
309 | |
310 | + |
311 | +class TestSlave(TrialTestCase): |
312 | + """ |
313 | + Integration tests for BuilderSlave that verify how it works against a |
314 | + real slave server. |
315 | + """ |
316 | + |
317 | + layer = TwistedLayer |
318 | + |
319 | + def setUp(self): |
320 | + super(TestSlave, self).setUp() |
321 | + self.slave_helper = SlaveTestHelpers() |
322 | + self.slave_helper.setUp() |
323 | + self.addCleanup(self.slave_helper.cleanUp) |
324 | + |
325 | + # XXX: JonathanLange 2010-09-20 bug=643521: There are also tests for |
326 | + # BuilderSlave in buildd-slave.txt and in other places. The tests here |
327 | + # ought to become the canonical tests for BuilderSlave vs running buildd |
328 | + # XML-RPC server interaction. |
329 | + |
330 | def test_abort(self): |
331 | - slave = self.getClientSlave() |
332 | + slave = self.slave_helper.getClientSlave() |
333 | # We need to be in a BUILDING state before we can abort. |
334 | - self.triggerGoodBuild(slave) |
335 | + self.slave_helper.triggerGoodBuild(slave) |
336 | result = slave.abort() |
337 | self.assertEqual(result, BuilderStatus.ABORTING) |
338 | |
339 | @@ -549,8 +575,8 @@ |
340 | # valid chroot & filemaps works and returns a BuilderStatus of |
341 | # BUILDING. |
342 | build_id = 'some-id' |
343 | - slave = self.getClientSlave() |
344 | - result = self.triggerGoodBuild(slave, build_id) |
345 | + slave = self.slave_helper.getClientSlave() |
346 | + result = self.slave_helper.triggerGoodBuild(slave, build_id) |
347 | self.assertEqual([BuilderStatus.BUILDING, build_id], result) |
348 | |
349 | def test_clean(self): |
350 | @@ -564,15 +590,15 @@ |
351 | def test_echo(self): |
352 | # Calling 'echo' contacts the server which returns the arguments we |
353 | # gave it. |
354 | - self.getServerSlave() |
355 | - slave = self.getClientSlave() |
356 | + self.slave_helper.getServerSlave() |
357 | + slave = self.slave_helper.getClientSlave() |
358 | result = slave.echo('foo', 'bar', 42) |
359 | self.assertEqual(['foo', 'bar', 42], result) |
360 | |
361 | def test_info(self): |
362 | # Calling 'info' gets some information about the slave. |
363 | - self.getServerSlave() |
364 | - slave = self.getClientSlave() |
365 | + self.slave_helper.getServerSlave() |
366 | + slave = self.slave_helper.getClientSlave() |
367 | result = slave.info() |
368 | # We're testing the hard-coded values, since the version is hard-coded |
369 | # into the remote slave, the supported build managers are hard-coded |
370 | @@ -588,17 +614,17 @@ |
371 | def test_initial_status(self): |
372 | # Calling 'status' returns the current status of the slave. The |
373 | # initial status is IDLE. |
374 | - self.getServerSlave() |
375 | - slave = self.getClientSlave() |
376 | + self.slave_helper.getServerSlave() |
377 | + slave = self.slave_helper.getClientSlave() |
378 | status = slave.status() |
379 | self.assertEqual([BuilderStatus.IDLE, ''], status) |
380 | |
381 | def test_status_after_build(self): |
382 | # Calling 'status' returns the current status of the slave. After a |
383 | # build has been triggered, the status is BUILDING. |
384 | - slave = self.getClientSlave() |
385 | + slave = self.slave_helper.getClientSlave() |
386 | build_id = 'status-build-id' |
387 | - self.triggerGoodBuild(slave, build_id) |
388 | + self.slave_helper.triggerGoodBuild(slave, build_id) |
389 | status = slave.status() |
390 | self.assertEqual([BuilderStatus.BUILDING, build_id], status[:2]) |
391 | [log_file] = status[2:] |
392 | @@ -606,15 +632,84 @@ |
393 | |
394 | def test_ensurepresent_not_there(self): |
395 | # ensurepresent checks to see if a file is there. |
396 | - self.getServerSlave() |
397 | - slave = self.getClientSlave() |
398 | - result = slave.ensurepresent('blahblah', None, None, None) |
399 | - self.assertEqual([False, 'No URL'], result) |
400 | + self.slave_helper.getServerSlave() |
401 | + slave = self.slave_helper.getClientSlave() |
402 | + d = slave.ensurepresent('blahblah', None, None, None) |
403 | + d.addCallback(self.assertEqual, [False, 'No URL']) |
404 | + return d |
405 | |
406 | def test_ensurepresent_actually_there(self): |
407 | # ensurepresent checks to see if a file is there. |
408 | - tachandler = self.getServerSlave() |
409 | - slave = self.getClientSlave() |
410 | - self.makeCacheFile(tachandler, 'blahblah') |
411 | - result = slave.ensurepresent('blahblah', None, None, None) |
412 | - self.assertEqual([True, 'No URL'], result) |
413 | + tachandler = self.slave_helper.getServerSlave() |
414 | + slave = self.slave_helper.getClientSlave() |
415 | + self.slave_helper.makeCacheFile(tachandler, 'blahblah') |
416 | + d = slave.ensurepresent('blahblah', None, None, None) |
417 | + d.addCallback(self.assertEqual, [True, 'No URL']) |
418 | + return d |
419 | + |
420 | + def test_sendFileToSlave_not_there(self): |
421 | + self.slave_helper.getServerSlave() |
422 | + slave = self.slave_helper.getClientSlave() |
423 | + d = slave.sendFileToSlave('blahblah', None, None, None) |
424 | + return self.assertFailure(d, CannotFetchFile) |
425 | + |
426 | + def test_sendFileToSlave_actually_there(self): |
427 | + tachandler = self.slave_helper.getServerSlave() |
428 | + slave = self.slave_helper.getClientSlave() |
429 | + self.slave_helper.makeCacheFile(tachandler, 'blahblah') |
430 | + d = slave.sendFileToSlave('blahblah', None, None, None) |
431 | + def check_present(ignored): |
432 | + d = slave.ensurepresent('blahblah', None, None, None) |
433 | + return d.addCallback(self.assertEqual, [True, 'No URL']) |
434 | + d.addCallback(check_present) |
435 | + return d |
436 | + |
437 | + |
438 | +class TestSlaveWithLibrarian(TrialTestCase): |
439 | + """Tests that need more of Launchpad to run.""" |
440 | + |
441 | + layer = TwistedLaunchpadZopelessLayer |
442 | + |
443 | + def setUp(self): |
444 | + super(TestSlaveWithLibrarian, self) |
445 | + self.slave_helper = SlaveTestHelpers() |
446 | + self.slave_helper.setUp() |
447 | + self.addCleanup(self.slave_helper.cleanUp) |
448 | + self.factory = LaunchpadObjectFactory() |
449 | + login_as(ANONYMOUS) |
450 | + self.addCleanup(logout) |
451 | + |
452 | + def test_ensurepresent_librarian(self): |
453 | + # ensurepresent, when given an http URL for a file will download the |
454 | + # file from that URL and report that the file is present, and it was |
455 | + # downloaded. |
456 | + |
457 | + # Use the Librarian because it's a "convenient" web server. |
458 | + lf = self.factory.makeLibraryFileAlias( |
459 | + 'HelloWorld.txt', content="Hello World") |
460 | + self.layer.txn.commit() |
461 | + self.slave_helper.getServerSlave() |
462 | + slave = self.slave_helper.getClientSlave() |
463 | + d = slave.ensurepresent( |
464 | + lf.content.sha1, lf.http_url, "", "") |
465 | + d.addCallback(self.assertEqual, [True, 'Download']) |
466 | + return d |
467 | + |
468 | + def test_retrieve_files_from_filecache(self): |
469 | + # Files that are present on the slave can be downloaded with a |
470 | + # filename made from the sha1 of the content underneath the |
471 | + # 'filecache' directory. |
472 | + content = "Hello World" |
473 | + lf = self.factory.makeLibraryFileAlias( |
474 | + 'HelloWorld.txt', content=content) |
475 | + self.layer.txn.commit() |
476 | + expected_url = '%s/filecache/%s' % ( |
477 | + self.slave_helper.BASE_URL, lf.content.sha1) |
478 | + self.slave_helper.getServerSlave() |
479 | + slave = self.slave_helper.getClientSlave() |
480 | + d = slave.ensurepresent( |
481 | + lf.content.sha1, lf.http_url, "", "") |
482 | + def check_file(ignored): |
483 | + d = getPage(expected_url.encode('utf8')) |
484 | + return d.addCallback(self.assertEqual, content) |
485 | + return d.addCallback(check_file) |
486 | |
487 | === modified file 'lib/lp/code/model/recipebuilder.py' |
488 | --- lib/lp/code/model/recipebuilder.py 2010-08-20 20:31:18 +0000 |
489 | +++ lib/lp/code/model/recipebuilder.py 2010-09-21 09:54:50 +0000 |
490 | @@ -122,33 +122,36 @@ |
491 | if chroot is None: |
492 | raise CannotBuild("Unable to find a chroot for %s" % |
493 | distroarchseries.displayname) |
494 | - self._builder.slave.cacheFile(logger, chroot) |
495 | - |
496 | - # Generate a string which can be used to cross-check when obtaining |
497 | - # results so we know we are referring to the right database object in |
498 | - # subsequent runs. |
499 | - buildid = "%s-%s" % (self.build.id, build_queue_id) |
500 | - cookie = self.buildfarmjob.generateSlaveBuildCookie() |
501 | - chroot_sha1 = chroot.content.sha1 |
502 | - logger.debug( |
503 | - "Initiating build %s on %s" % (buildid, self._builder.url)) |
504 | - |
505 | - args = self._extraBuildArgs(distroarchseries, logger) |
506 | - status, info = self._builder.slave.build( |
507 | - cookie, "sourcepackagerecipe", chroot_sha1, {}, args) |
508 | - message = """%s (%s): |
509 | - ***** RESULT ***** |
510 | - %s |
511 | - %s: %s |
512 | - ****************** |
513 | - """ % ( |
514 | - self._builder.name, |
515 | - self._builder.url, |
516 | - args, |
517 | - status, |
518 | - info, |
519 | - ) |
520 | - logger.info(message) |
521 | + d = self._builder.slave.cacheFile(logger, chroot) |
522 | + |
523 | + def got_cache_file(ignored): |
524 | + # Generate a string which can be used to cross-check when obtaining |
525 | + # results so we know we are referring to the right database object in |
526 | + # subsequent runs. |
527 | + buildid = "%s-%s" % (self.build.id, build_queue_id) |
528 | + cookie = self.buildfarmjob.generateSlaveBuildCookie() |
529 | + chroot_sha1 = chroot.content.sha1 |
530 | + logger.debug( |
531 | + "Initiating build %s on %s" % (buildid, self._builder.url)) |
532 | + |
533 | + args = self._extraBuildArgs(distroarchseries, logger) |
534 | + # XXX: Soon to be async |
535 | + status, info = self._builder.slave.build( |
536 | + cookie, "sourcepackagerecipe", chroot_sha1, {}, args) |
537 | + message = """%s (%s): |
538 | + ***** RESULT ***** |
539 | + %s |
540 | + %s: %s |
541 | + ****************** |
542 | + """ % ( |
543 | + self._builder.name, |
544 | + self._builder.url, |
545 | + args, |
546 | + status, |
547 | + info, |
548 | + ) |
549 | + logger.info(message) |
550 | + return d.addCallback(got_cache_file) |
551 | |
552 | def verifyBuildRequest(self, logger): |
553 | """Assert some pre-build checks. |
554 | |
555 | === modified file 'lib/lp/soyuz/doc/buildd-slave.txt' |
556 | --- lib/lp/soyuz/doc/buildd-slave.txt 2010-04-30 10:00:34 +0000 |
557 | +++ lib/lp/soyuz/doc/buildd-slave.txt 2010-09-21 09:54:50 +0000 |
558 | @@ -13,43 +13,6 @@ |
559 | >>> from canonical.buildd.tests import BuilddSlaveTestSetup |
560 | >>> BuilddSlaveTestSetup().setUp() |
561 | |
562 | -Use simple xmlrpclib client to certify the BuildSlave is running |
563 | - |
564 | - >>> import xmlrpclib |
565 | - >>> slave = xmlrpclib.Server('http://localhost:8221/rpc/') |
566 | - >>> slave.echo('Hello World') |
567 | - ['Hello World'] |
568 | - |
569 | -With slave protocol v1.0new, the only way to get files to the slave is to |
570 | -put them in the librarian first... |
571 | - |
572 | - >>> from canonical.librarian.client import LibrarianClient |
573 | - >>> from StringIO import StringIO |
574 | - >>> from canonical.launchpad.database import LibraryFileAlias |
575 | - >>> import transaction |
576 | - >>> lc = LibrarianClient() |
577 | - >>> helloworld = "Hello World" |
578 | - >>> hw_sio = StringIO(helloworld) |
579 | - >>> alias = lc.addFile("HelloWorld.txt", len(helloworld), |
580 | - ... hw_sio, "text/plain") |
581 | - >>> transaction.commit() |
582 | - >>> lf = LibraryFileAlias.get(alias) |
583 | - >>> present, info = slave.ensurepresent( |
584 | - ... lf.content.sha1, lf.http_url, "", "") |
585 | - >>> present, info |
586 | - (True, 'Download') |
587 | - |
588 | -As of slave protocol v1.0new, /filecache/SHA1SUM is *THE* way |
589 | -to retrieve files from the slave. Verify it works... |
590 | - |
591 | - >>> from urllib2 import urlopen |
592 | - >>> f = urlopen("http://localhost:8221/filecache/" + lf.content.sha1) |
593 | - >>> hw_str = f.read() |
594 | - >>> f.close() |
595 | - >>> hw_str == helloworld |
596 | - True |
597 | - |
598 | - |
599 | == BuilderSet polling operations == |
600 | |
601 | >>> import logging |
602 | @@ -90,6 +53,7 @@ |
603 | |
604 | At this point the buildd-slave is not accessible anymore. |
605 | |
606 | + >>> import xmlrpclib |
607 | >>> s = xmlrpclib.Server('http://localhost:8221/rpc/') |
608 | >>> s.info() |
609 | Traceback (most recent call last): |
610 | |
611 | === modified file 'lib/lp/soyuz/model/binarypackagebuildbehavior.py' |
612 | --- lib/lp/soyuz/model/binarypackagebuildbehavior.py 2010-08-23 16:51:11 +0000 |
613 | +++ lib/lp/soyuz/model/binarypackagebuildbehavior.py 2010-09-21 09:54:50 +0000 |
614 | @@ -11,6 +11,7 @@ |
615 | 'BinaryPackageBuildBehavior', |
616 | ] |
617 | |
618 | +from twisted.internet import defer |
619 | from zope.interface import implements |
620 | |
621 | from canonical.launchpad.webapp import urlappend |
622 | @@ -38,56 +39,67 @@ |
623 | logger.info("startBuild(%s, %s, %s, %s)", self._builder.url, |
624 | spr.name, spr.version, self.build.pocket.title) |
625 | |
626 | - def dispatchBuildToSlave(self, build_queue_id, logger): |
627 | - """See `IBuildFarmJobBehavior`.""" |
628 | - |
629 | - # Start the binary package build on the slave builder. First |
630 | - # we send the chroot. |
631 | - chroot = self.build.distro_arch_series.getChroot() |
632 | - self._builder.slave.cacheFile(logger, chroot) |
633 | - |
634 | + def _buildFilemapStructure(self, logger): |
635 | # Build filemap structure with the files required in this build |
636 | # and send them to the slave. |
637 | # If the build is private we tell the slave to get the files from the |
638 | # archive instead of the librarian because the slaves cannot |
639 | # access the restricted librarian. |
640 | + dl = [] |
641 | private = self.build.archive.private |
642 | if private: |
643 | - self._cachePrivateSourceOnSlave(logger) |
644 | + dl.extend(self._cachePrivateSourceOnSlave(logger)) |
645 | filemap = {} |
646 | for source_file in self.build.source_package_release.files: |
647 | lfa = source_file.libraryfile |
648 | filemap[lfa.filename] = lfa.content.sha1 |
649 | if not private: |
650 | - self._builder.slave.cacheFile(logger, source_file.libraryfile) |
651 | - |
652 | - # Generate a string which can be used to cross-check when obtaining |
653 | - # results so we know we are referring to the right database object in |
654 | - # subsequent runs. |
655 | - buildid = "%s-%s" % (self.build.id, build_queue_id) |
656 | - cookie = self.buildfarmjob.generateSlaveBuildCookie() |
657 | - chroot_sha1 = chroot.content.sha1 |
658 | - logger.debug( |
659 | - "Initiating build %s on %s" % (buildid, self._builder.url)) |
660 | - |
661 | - args = self._extraBuildArgs(self.build) |
662 | - status, info = self._builder.slave.build( |
663 | - cookie, "binarypackage", chroot_sha1, filemap, args) |
664 | - message = """%s (%s): |
665 | - ***** RESULT ***** |
666 | - %s |
667 | - %s |
668 | - %s: %s |
669 | - ****************** |
670 | - """ % ( |
671 | - self._builder.name, |
672 | - self._builder.url, |
673 | - filemap, |
674 | - args, |
675 | - status, |
676 | - info, |
677 | - ) |
678 | - logger.info(message) |
679 | + dl.append( |
680 | + self._builder.slave.cacheFile( |
681 | + logger, source_file.libraryfile)) |
682 | + d = defer.gatherResults(dl) |
683 | + return d.addCallback(lambda ignored: filemap) |
684 | + |
685 | + def dispatchBuildToSlave(self, build_queue_id, logger): |
686 | + """See `IBuildFarmJobBehavior`.""" |
687 | + |
688 | + # Start the binary package build on the slave builder. First |
689 | + # we send the chroot. |
690 | + chroot = self.build.distro_arch_series.getChroot() |
691 | + self._builder.slave.cacheFile(logger, chroot) |
692 | + |
693 | + d = self._buildFilemapStructure(logger) |
694 | + |
695 | + def got_filemap(filemap): |
696 | + # Generate a string which can be used to cross-check when |
697 | + # obtaining results so we know we are referring to the right |
698 | + # database object in subsequent runs. |
699 | + buildid = "%s-%s" % (self.build.id, build_queue_id) |
700 | + cookie = self.buildfarmjob.generateSlaveBuildCookie() |
701 | + chroot_sha1 = chroot.content.sha1 |
702 | + logger.debug( |
703 | + "Initiating build %s on %s" % (buildid, self._builder.url)) |
704 | + |
705 | + args = self._extraBuildArgs(self.build) |
706 | + status, info = self._builder.slave.build( |
707 | + cookie, "binarypackage", chroot_sha1, filemap, args) |
708 | + message = """%s (%s): |
709 | + ***** RESULT ***** |
710 | + %s |
711 | + %s |
712 | + %s: %s |
713 | + ****************** |
714 | + """ % ( |
715 | + self._builder.name, |
716 | + self._builder.url, |
717 | + filemap, |
718 | + args, |
719 | + status, |
720 | + info, |
721 | + ) |
722 | + logger.info(message) |
723 | + |
724 | + return d.addCallback(got_filemap) |
725 | |
726 | def verifyBuildRequest(self, logger): |
727 | """Assert some pre-build checks. |
728 | @@ -153,6 +165,8 @@ |
729 | """Ask the slave to download source files for a private build. |
730 | |
731 | :param logger: A logger used for providing debug information. |
732 | + :return: A list of Deferreds, each of which represents a request |
733 | + to cache a file. |
734 | """ |
735 | # The URL to the file in the archive consists of these parts: |
736 | # archive_url / makePoolPath() / filename |
737 | @@ -164,6 +178,7 @@ |
738 | archive = self.build.archive |
739 | archive_url = archive.archive_url |
740 | component_name = self.build.current_component.name |
741 | + dl = [] |
742 | for source_file in self.build.source_package_release.files: |
743 | file_name = source_file.libraryfile.filename |
744 | sha1 = source_file.libraryfile.content.sha1 |
745 | @@ -174,8 +189,10 @@ |
746 | logger.debug("Asking builder on %s to ensure it has file %s " |
747 | "(%s, %s)" % ( |
748 | self._builder.url, file_name, url, sha1)) |
749 | - self._builder.slave.sendFileToSlave( |
750 | - sha1, url, "buildd", archive.buildd_secret) |
751 | + dl.append( |
752 | + self._builder.slave.sendFileToSlave( |
753 | + sha1, url, "buildd", archive.buildd_secret)) |
754 | + return dl |
755 | |
756 | def _extraBuildArgs(self, build): |
757 | """ |
758 | |
759 | === modified file 'lib/lp/soyuz/scripts/buildd.py' |
760 | --- lib/lp/soyuz/scripts/buildd.py 2010-08-27 11:19:54 +0000 |
761 | +++ lib/lp/soyuz/scripts/buildd.py 2010-09-21 09:54:50 +0000 |
762 | @@ -276,6 +276,8 @@ |
763 | self.txn.commit() |
764 | |
765 | |
766 | +# XXX: This is the old slave scanner. Julian says it's not running on |
767 | +# production. We should either delete it or update it to use the async apis. |
768 | class SlaveScanner(LaunchpadCronScript): |
769 | |
770 | def main(self): |
771 | |
772 | === modified file 'lib/lp/soyuz/tests/soyuzbuilddhelpers.py' |
773 | --- lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-09-20 11:52:51 +0000 |
774 | +++ lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-09-21 09:54:50 +0000 |
775 | @@ -31,6 +31,8 @@ |
776 | ) |
777 | from lp.testing.sampledata import I386_ARCHITECTURE_NAME |
778 | |
779 | +# XXX: Almost everything in this module will need to be revisited & perhaps |
780 | +# deleted now that we have new APIs for BuilderSlave. |
781 | |
782 | class MockBuilder: |
783 | """Emulates a IBuilder class.""" |
784 | |
785 | === modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py' |
786 | --- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-08-20 20:31:18 +0000 |
787 | +++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-09-21 09:54:50 +0000 |
788 | @@ -41,16 +41,18 @@ |
789 | """See `IBuildFarmJobBehavior`.""" |
790 | chroot = self._getChroot() |
791 | chroot_sha1 = chroot.content.sha1 |
792 | - self._builder.slave.cacheFile(logger, chroot) |
793 | - cookie = self.buildfarmjob.generateSlaveBuildCookie() |
794 | - |
795 | - args = {'arch_tag': self._getDistroArchSeries().architecturetag} |
796 | - args.update(self.buildfarmjob.metadata) |
797 | - |
798 | - filemap = {} |
799 | - |
800 | - self._builder.slave.build( |
801 | - cookie, self.build_type, chroot_sha1, filemap, args) |
802 | + d = self._builder.slave.cacheFile(logger, chroot) |
803 | + def got_cache_file(ignored): |
804 | + cookie = self.buildfarmjob.generateSlaveBuildCookie() |
805 | + |
806 | + args = {'arch_tag': self._getDistroArchSeries().architecturetag} |
807 | + args.update(self.buildfarmjob.metadata) |
808 | + |
809 | + filemap = {} |
810 | + |
811 | + return self._builder.slave.build( |
812 | + cookie, self.build_type, chroot_sha1, filemap, args) |
813 | + return d.addCallback(got_cache_file) |
814 | |
815 | def _getChroot(self): |
816 | return self._getDistroArchSeries().getChroot() |