Merge lp:~wgrant/launchpad/move-rescueiflost-tests into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~wgrant/launchpad/move-rescueiflost-tests
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/more-buildmaster-cleanup
Diff against target: 702 lines (+204/-284)
6 files modified
lib/lp/buildmaster/doc/builder.txt (+135/-93)
lib/lp/buildmaster/doc/buildfarmjobbehavior.txt (+8/-0)
lib/lp/buildmaster/tests/test_manager.py (+3/-3)
lib/lp/code/tests/test_recipebuilder.py (+4/-4)
lib/lp/soyuz/doc/buildd-slavescanner.txt (+3/-84)
lib/lp/soyuz/tests/soyuzbuilddhelpers.py (+51/-100)
To merge this branch: bzr merge lp:~wgrant/launchpad/move-rescueiflost-tests
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Brad Crittenden (community) Needs Information
Review via email: mp+22737@code.launchpad.net

Commit message

Move the rescueIfLost tests out of buildd-slavescanner.txt into builder.txt, and simplify them considerably.

Description of the change

This branch moves buildd-slavescanner.txt's long-winded rescueIfLost tests to builder.txt, and refactors them to make them rather more succinct and less ugly.

It replaces a tonne of mock slaves with a couple of configurable ones, and fixes a couple of other tests around the tree that used the condemned mocks. It also replaces with a test of IdleBuildBehavior.verifySlaveBuildID the two tests that confirmed rescue of a BUILDING or WAITING slave marked as idle in the DB.

I also rewrote the start of builder.txt, since its style offended me.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (6.9 KiB)

Hi William,

This branch looks great. As we discussed on IRC please run it past a Soyuz team member and I'll approve it.

> === modified file 'lib/lp/buildmaster/doc/builder.txt'
> --- lib/lp/buildmaster/doc/builder.txt 2010-03-24 10:24:22 +0000
> +++ lib/lp/buildmaster/doc/builder.txt 2010-04-03 04:39:18 +0000
> @@ -2,36 +2,34 @@
> Builder Class
> =============
>
> -This test aims to meet the requirements of
> -<https://launchpad.canonical.com/BasicTestCoverage> for the Builder class,
> -which represents the Buildd Slave entity.
> -
> -Need auxiliar methods from zope toolchain:
> +The Builder class represents a slave machine in the build farm. These
> +slaves are used to execute untrusted code -- for example when building
> +packages.
> +
> +There are several builders in the sample data. Let's examine the first.
> +
> + >>> from lp.buildmaster.model.builder import Builder
> + >>> builder = Builder.get(1)
> +
> +As expected, it implements IBuilder.

Nice changes.

> +Builders can take on different behaviors depending on the type of build
> +they are currently processing. Each builder provides an attribute
> +(current_build_behavior) to which all the build-type specific behavior
> +for the current build is delegated. In the sample data, bob's current
> +behavior is the behavior for dealing with binary packages

How about:
"behavior is dealing with binary packages."
(Note full-stop.)

> >>> from zope.security.proxy import isinstance
> >>> from lp.soyuz.model.binarypackagebuildbehavior import (
> @@ -40,85 +38,77 @@
> ... builder.current_build_behavior, BinaryPackageBuildBehavior)
> True
>
> -Confirm we can get the slave xmlrpc interface
> +A builder has an XML-RPC proxy in the 'slave' attribute, which allows
> +us to easily call methods on the slave machines.
>
> >>> s = builder.slave
>
> -Confirm that the urlbase is correct in that slave. (If the protocol changes,
> -this may change too)
> +The base URL of the proxy matches the builder's URL.
>
> >>> s.urlbase == builder.url
> True
>
> -Check if the instance corresponds to the declared interface:
> -
> - >>> verifyObject(IBuilder, builder)
> - True
> -
>
> BuilderSet
> ==========
>
> -Now perform the tests for the Builder ContentSet class, BuilderSet.
> -
> -Check if it can be imported:
> -
> +Builders and groups thereof are managed through a utility, IBuilderSet.
> +
> + >>> from zope.component import getUtility
> >>> from lp.buildmaster.interfaces.builder import IBuilderSet
> -
> -Check we can use the set as a utility:
> -
> >>> builderset = getUtility(IBuilderSet)
> -
> -Check if the instance returned as utility corresponds to its
> -respective interface:
> -
> >>> verifyObject(IBuilderSet, builderset)
> True
>
> -Check if the instance is iterable:
> +Iterating over a BuilderSet yields all registered builders.
>
> >>> for b in builderset:
> - ... b.id
> - 1
> + ... print b.name
> + bob
> + frog
> +
> +count() return the number of builder instance we have stored:

typo: instances

> +
> + >>> builderset.count()
> 2
>
> -Check if the __getitem__ method:
> -
...

Read more...

review: Needs Information
Revision history for this message
William Grant (wgrant) wrote :
Download full text (7.6 KiB)

On Tue, 2010-04-06 at 15:51 +0000, Brad Crittenden wrote:
> Review: Needs Information
> Hi William,
>
> This branch looks great. As we discussed on IRC please run it past a Soyuz team member and I'll approve it.

Thanks for the review, Brad.

> > === modified file 'lib/lp/buildmaster/doc/builder.txt'
> > --- lib/lp/buildmaster/doc/builder.txt 2010-03-24 10:24:22 +0000
> > +++ lib/lp/buildmaster/doc/builder.txt 2010-04-03 04:39:18 +0000
> > @@ -2,36 +2,34 @@
> > Builder Class
> > =============
> >
> > -This test aims to meet the requirements of
> > -<https://launchpad.canonical.com/BasicTestCoverage> for the Builder class,
> > -which represents the Buildd Slave entity.
> > -
> > -Need auxiliar methods from zope toolchain:
> > +The Builder class represents a slave machine in the build farm. These
> > +slaves are used to execute untrusted code -- for example when building
> > +packages.
> > +
> > +There are several builders in the sample data. Let's examine the first.
> > +
> > + >>> from lp.buildmaster.model.builder import Builder
> > + >>> builder = Builder.get(1)
> > +
> > +As expected, it implements IBuilder.
>
> Nice changes.
>
> > +Builders can take on different behaviors depending on the type of build
> > +they are currently processing. Each builder provides an attribute
> > +(current_build_behavior) to which all the build-type specific behavior
> > +for the current build is delegated. In the sample data, bob's current
> > +behavior is the behavior for dealing with binary packages
>
> How about:
> "behavior is dealing with binary packages."
> (Note full-stop.)
>
> > >>> from zope.security.proxy import isinstance
> > >>> from lp.soyuz.model.binarypackagebuildbehavior import (
> > @@ -40,85 +38,77 @@
> > ... builder.current_build_behavior, BinaryPackageBuildBehavior)
> > True
> >
> > -Confirm we can get the slave xmlrpc interface
> > +A builder has an XML-RPC proxy in the 'slave' attribute, which allows
> > +us to easily call methods on the slave machines.
> >
> > >>> s = builder.slave
> >
> > -Confirm that the urlbase is correct in that slave. (If the protocol changes,
> > -this may change too)
> > +The base URL of the proxy matches the builder's URL.
> >
> > >>> s.urlbase == builder.url
> > True
> >
> > -Check if the instance corresponds to the declared interface:
> > -
> > - >>> verifyObject(IBuilder, builder)
> > - True
> > -
> >
> > BuilderSet
> > ==========
> >
> > -Now perform the tests for the Builder ContentSet class, BuilderSet.
> > -
> > -Check if it can be imported:
> > -
> > +Builders and groups thereof are managed through a utility, IBuilderSet.
> > +
> > + >>> from zope.component import getUtility
> > >>> from lp.buildmaster.interfaces.builder import IBuilderSet
> > -
> > -Check we can use the set as a utility:
> > -
> > >>> builderset = getUtility(IBuilderSet)
> > -
> > -Check if the instance returned as utility corresponds to its
> > -respective interface:
> > -
> > >>> verifyObject(IBuilderSet, builderset)
> > True
> >
> > -Check if the instance is iterable:
> > +Iterating over a BuilderSet yields all registered builders.
...

Read more...

Revision history for this message
Michael Nelson (michael.nelson) wrote :

I *think* Jelmer is actually working on refactoring a lot of buildd-slavescanner into unit-tests (but I can't see the branch), so it'd be worthwhile chatting with him about it (he might just handle the conflicts). Other than that, I'm all for it :)

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/buildmaster/doc/builder.txt'
2--- lib/lp/buildmaster/doc/builder.txt 2010-03-24 10:24:22 +0000
3+++ lib/lp/buildmaster/doc/builder.txt 2010-04-09 00:40:18 +0000
4@@ -2,36 +2,34 @@
5 Builder Class
6 =============
7
8-This test aims to meet the requirements of
9-<https://launchpad.canonical.com/BasicTestCoverage> for the Builder class,
10-which represents the Buildd Slave entity.
11-
12-Need auxiliar methods from zope toolchain:
13+The Builder class represents a slave machine in the build farm. These
14+slaves are used to execute untrusted code -- for example when building
15+packages.
16+
17+There are several builders in the sample data. Let's examine the first.
18+
19+ >>> from lp.buildmaster.model.builder import Builder
20+ >>> builder = Builder.get(1)
21+
22+As expected, it implements IBuilder.
23
24 >>> from canonical.launchpad.webapp.testing import verifyObject
25- >>> from zope.component import getUtility
26-
27-Importing Builder content class and its interface:
28-
29- >>> from lp.buildmaster.model.builder import Builder
30 >>> from lp.buildmaster.interfaces.builder import IBuilder
31-
32-Get an instance of Builder from the current sampledata:
33-
34- >>> builder = Builder.get(1)
35-
36-Test some attributes:
37+ >>> verifyObject(IBuilder, builder)
38+ True
39
40 >>> print builder.name
41 bob
42-
43- >>> builder.builderok = True
44- >>> builder.failnotes = None
45-
46-A builder provides a current_build_behavior attribute to which all the
47-build-type specific behavior for the current build is delegated. For
48-our sample data, the behavior is specifically a behavior for dealing
49-with binary packages
50+ >>> print builder.builderok
51+ True
52+ >>> print builder.failnotes
53+ None
54+
55+Builders can take on different behaviors depending on the type of build
56+they are currently processing. Each builder provides an attribute
57+(current_build_behavior) to which all the build-type specific behavior
58+for the current build is delegated. In the sample data, bob's current
59+behavior is dealing with binary packages.
60
61 >>> from zope.security.proxy import isinstance
62 >>> from lp.soyuz.model.binarypackagebuildbehavior import (
63@@ -40,86 +38,78 @@
64 ... builder.current_build_behavior, BinaryPackageBuildBehavior)
65 True
66
67-Confirm we can get the slave xmlrpc interface
68+A builder has an XML-RPC proxy in the 'slave' attribute, which allows
69+us to easily call methods on the slave machines.
70
71 >>> s = builder.slave
72
73-Confirm that the urlbase is correct in that slave. (If the protocol changes,
74-this may change too)
75+The base URL of the proxy matches the builder's URL.
76
77 >>> s.urlbase == builder.url
78 True
79
80-Check if the instance corresponds to the declared interface:
81-
82- >>> verifyObject(IBuilder, builder)
83- True
84-
85
86 BuilderSet
87 ==========
88
89-Now perform the tests for the Builder ContentSet class, BuilderSet.
90-
91-Check if it can be imported:
92-
93+Builders and groups thereof are managed through a utility, IBuilderSet.
94+
95+ >>> from zope.component import getUtility
96 >>> from lp.buildmaster.interfaces.builder import IBuilderSet
97-
98-Check we can use the set as a utility:
99-
100 >>> builderset = getUtility(IBuilderSet)
101-
102-Check if the instance returned as utility corresponds to its
103-respective interface:
104-
105 >>> verifyObject(IBuilderSet, builderset)
106 True
107
108-Check if the instance is iterable:
109+Iterating over a BuilderSet yields all registered builders.
110
111 >>> for b in builderset:
112- ... b.id
113- 1
114+ ... print b.name
115+ bob
116+ frog
117+
118+count() return the number of builders registered:
119+
120+ >>> builderset.count()
121 2
122
123-Check if the __getitem__ method:
124-
125- >>> builderset['bob'].name
126- u'bob'
127-
128-Check now the specific method in the utility as new():
129+Builders can be retrieved by name.
130+
131+ >>> print builderset['bob'].name
132+ bob
133+ >>> print builderset['bad']
134+ None
135+
136+And also by ID.
137+
138+ >>> print builderset.get(2).name
139+ frog
140+ >>> print builderset.get(100).name
141+ Traceback (most recent call last):
142+ ...
143+ SQLObjectNotFound: Object not found
144+
145+The 'new' method will create a new builder in the database.
146
147 >>> bnew = builderset.new(1, 'http://dummy.com:8221/', 'dummy',
148 ... 'Dummy Title', 'eh ?', 1)
149 >>> bnew.name
150 u'dummy'
151
152-Check get() which returns a correspondent Builder instance to a given id:
153-
154- >>> builderset.get(bnew.id).name
155- u'dummy'
156-
157-Or raises an SQLObjectNotFound exception:
158-
159- >>> builderset.get(100)
160- Traceback (most recent call last):
161- ...
162- SQLObjectNotFound: Object not found
163-
164-count() return the number of builder instance we have stored:
165-
166- >>> builderset.count()
167- 3
168-
169-getBuilder() method returns all the builders available. It seems the
170-same than the own instance but we have plans to turn it aware of some
171-attributes of builder instance as: builderok and trust.
172-
173- >>> for b in builderset.getBuilders():
174- ... b.name
175- u'bob'
176- u'dummy'
177- u'frog'
178+'getBuilders' returns builders with the 'active' flag set, ordered by
179+virtualization status, architecture, then name.
180+
181+ >>> for b in builderset.getBuilders():
182+ ... print b.name
183+ bob
184+ dummy
185+ frog
186+ >>> login('foo.bar@canonical.com')
187+ >>> bnew.active = False
188+ >>> login(ANONYMOUS)
189+ >>> for b in builderset.getBuilders():
190+ ... print b.name
191+ bob
192+ frog
193
194 'getBuildQueueSizeForProcessor' returns the number of pending builds
195 for a given Processor. The callsites can also control which build-farm
196@@ -246,24 +236,76 @@
197 A fictitious i386 variant is rejected, since there are no DASes with that
198 tag.
199
200- >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
201- >>> bob.setSlaveForTesting(OkSlave('i387'))
202- >>> bob.checkSlaveArchitecture()
203- Traceback (most recent call last):
204- ...
205- BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86)
206+ >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
207+ >>> bob.setSlaveForTesting(OkSlave('i387'))
208+ >>> bob.checkSlaveArchitecture()
209+ Traceback (most recent call last):
210+ ...
211+ BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86)
212
213 hppa isn't in the x86 family, so it too is rejected.
214
215- >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
216- >>> bob.setSlaveForTesting(OkSlave('hppa'))
217- >>> bob.checkSlaveArchitecture()
218- Traceback (most recent call last):
219- ...
220- BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86)
221+ >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
222+ >>> bob.setSlaveForTesting(OkSlave('hppa'))
223+ >>> bob.checkSlaveArchitecture()
224+ Traceback (most recent call last):
225+ ...
226+ BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86)
227
228 But i386, a real x86 variant, passes without objection.
229
230- >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
231- >>> bob.setSlaveForTesting(OkSlave('i386'))
232- >>> bob.checkSlaveArchitecture()
233+ >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
234+ >>> bob.setSlaveForTesting(OkSlave('i386'))
235+ >>> bob.checkSlaveArchitecture()
236+
237+
238+Rescuing lost slaves
239+====================
240+
241+Builder.rescueIfLost() checks the build ID reported in the slave status
242+against the database. If it isn't building what we think it should be,
243+the current build will be aborted and the slave cleaned in preparation
244+for a new task. The decision about the slave's correctness is left up
245+to IBuildFarmJobBehavior.verifySlaveBuildID -- for these examples we
246+will use a special behavior that just checks if the slave ID is 'good'.
247+
248+ >>> import logging
249+ >>> from lp.buildmaster.interfaces.builder import CorruptBuildID
250+ >>> from lp.soyuz.tests.soyuzbuilddhelpers import (
251+ ... BuildingSlave, MockBuilder, OkSlave, WaitingSlave)
252+
253+ >>> class TestBuildBehavior:
254+ ... def verifySlaveBuildID(self, build_id):
255+ ... if build_id != 'good':
256+ ... raise CorruptBuildID('Bad value')
257+
258+ >>> def rescue_slave_if_lost(slave):
259+ ... builder = MockBuilder('mock', slave, TestBuildBehavior())
260+ ... builder.rescueIfLost(logging.getLogger())
261+
262+An idle slave is not rescued.
263+
264+ >>> rescue_slave_if_lost(OkSlave())
265+
266+Slaves building or having built the correct build are not rescued
267+either.
268+
269+ >>> rescue_slave_if_lost(BuildingSlave(build_id='good'))
270+ >>> rescue_slave_if_lost(WaitingSlave(build_id='good'))
271+
272+But if a slave is building the wrong ID, it is declared lost and
273+an abort is attempted. MockSlave prints out a message when it is aborted
274+or cleaned.
275+
276+ >>> rescue_slave_if_lost(BuildingSlave(build_id='bad'))
277+ Aborting slave
278+ WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value'
279+
280+Slaves having completed an incorrect build are also declared lost,
281+but there's no need to abort a completed build. Such builders are
282+instead simply cleaned, ready for the next build.
283+
284+ >>> rescue_slave_if_lost(WaitingSlave(build_id='bad'))
285+ Cleaning slave
286+ WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value'
287+
288
289=== modified file 'lib/lp/buildmaster/doc/buildfarmjobbehavior.txt'
290--- lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-01-13 20:19:43 +0000
291+++ lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-04-09 00:40:18 +0000
292@@ -111,3 +111,11 @@
293 ...
294 BuildBehaviorMismatch: Builder was idle when asked to log the start of a
295 build.
296+
297+If a slave is working on a job while we think it is idle, it will always be
298+aborted.
299+
300+ >>> bob.current_build_behavior.verifySlaveBuildID('foo')
301+ Traceback (most recent call last):
302+ ...
303+ CorruptBuildID: No job assigned to builder
304
305=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
306--- lib/lp/buildmaster/tests/test_manager.py 2010-04-08 04:00:30 +0000
307+++ lib/lp/buildmaster/tests/test_manager.py 2010-04-09 00:40:18 +0000
308@@ -31,7 +31,7 @@
309 from lp.buildmaster.tests.harness import BuilddManagerTestSetup
310 from lp.registry.interfaces.distribution import IDistributionSet
311 from lp.soyuz.interfaces.build import IBuildSet
312-from lp.soyuz.tests.soyuzbuilddhelpers import SaneBuildingSlave
313+from lp.soyuz.tests.soyuzbuilddhelpers import BuildingSlave
314 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
315
316
317@@ -671,7 +671,7 @@
318 self.assertTrue(builder.builderok)
319
320 job = getUtility(IBuildQueueSet).get(job.id)
321- self.assertBuildingJob(job, builder, logtail='Doing something ...')
322+ self.assertBuildingJob(job, builder, logtail='This is a build log')
323
324 def testScanUpdatesBuildingJobs(self):
325 # The job assigned to a broken builder is rescued.
326@@ -682,7 +682,7 @@
327
328 login('foo.bar@canonical.com')
329 builder.builderok = True
330- builder.setSlaveForTesting(SaneBuildingSlave())
331+ builder.setSlaveForTesting(BuildingSlave(build_id='8-1'))
332 transaction.commit()
333 login(ANONYMOUS)
334
335
336=== modified file 'lib/lp/code/tests/test_recipebuilder.py'
337--- lib/lp/code/tests/test_recipebuilder.py 2010-03-25 18:34:07 +0000
338+++ lib/lp/code/tests/test_recipebuilder.py 2010-04-09 00:40:18 +0000
339@@ -20,8 +20,8 @@
340 SourcePackageRecipeBuild)
341 from lp.soyuz.adapters.archivedependencies import get_sources_list_for_building
342 from lp.soyuz.model.processor import ProcessorFamilySet
343-from lp.soyuz.tests.soyuzbuilddhelpers import (MockBuilder,
344- SaneBuildingSlave,)
345+from lp.soyuz.tests.soyuzbuilddhelpers import (
346+ MockBuilder, OkSlave)
347 from lp.soyuz.tests.test_binarypackagebuildbehavior import (
348 BaseTestVerifySlaveBuildID)
349 from lp.soyuz.tests.test_publishing import (
350@@ -84,7 +84,7 @@
351 # VerifyBuildRequest won't raise any exceptions when called with a
352 # valid builder set.
353 job = self.makeJob()
354- builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave())
355+ builder = MockBuilder("bob-de-bouwer", OkSlave())
356 job.setBuilder(builder)
357 logger = BufferLogger()
358 job.verifyBuildRequest(logger)
359@@ -136,7 +136,7 @@
360 # dispatchBuildToSlave will fail when there is not chroot tarball
361 # available for the distroseries to build for.
362 job = self.makeJob()
363- builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave())
364+ builder = MockBuilder("bob-de-bouwer", OkSlave())
365 processorfamily = ProcessorFamilySet().getByProcessorName('386')
366 builder.processor = processorfamily.processors[0]
367 job.setBuilder(builder)
368
369=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
370--- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-08 15:29:39 +0000
371+++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-09 00:40:18 +0000
372@@ -32,90 +32,8 @@
373 Import MockBuilder and a series of MockSlaves to be used in this test.
374
375 >>> from lp.soyuz.tests.soyuzbuilddhelpers import (
376- ... AbortedSlave, AbortingSlave, BuildingSlave, InsaneWaitingSlave,
377- ... LostBuildingBrokenSlave, LostBuildingSlave, LostWaitingSlave,
378- ... MockBuilder, OkSlave, SaneBuildingSlave, SaneWaitingSlave,
379- ... WaitingSlave)
380-
381-Let's play with a Builder method designed to rescue build slaves
382-that are processing unknown jobs. In real conditions, this situation
383-only happens if the slave is processing deleted or modified BuildQueue
384-entry, since Build entries are never removed. It might be caused by
385-exceptions in slavescanner or queuebuilder scripts.
386-
387-When we figured this situation out, the procedure to rescue is to
388-request the slave XMLRPC method 'clean', reseting the slave completely.
389-
390-We figured out if the building information is correct and sane by
391-checking the job identifier field from status message information,
392-which consists of "<Build.id>-<BuildQueue.id>".
393-
394-First let's emulate a sane and a lost slave. The SaneSlave returns a
395-job identifier that exists in our sampledata, but the LostSlave
396-returns a completely bogus one.
397-
398-The the mock slave.clean() method is modified to print a message for
399-testing purposes.
400-
401-Initializing the sane_builder. It was not rescued, since the job
402-identifier is sane (Build.id == 8 and BuildQueue.id == 1 exist):
403-
404- >>> sanebuilding_builder = MockBuilder(
405- ... 'Sane Building Slave', SaneBuildingSlave())
406- >>> sanebuilding_builder.rescueIfLost(logger) is None
407- True
408-
409-A sane WAITING slave:
410-
411- >>> sanewaiting_builder = MockBuilder(
412- ... 'Sane Waiting Slave', SaneWaitingSlave())
413- >>> sanewaiting_builder.rescueIfLost(logger) is None
414- True
415-
416-A insane WAITING slave, with wrong BuildQueue/Build relation:
417-
418- >>> insanewaiting_builder = MockBuilder(
419- ... 'Insane Waiting Slave', InsaneWaitingSlave())
420- >>> insanewaiting_builder.rescueIfLost(logger)
421- WARNING:root:Builder 'Insane Waiting Slave' rescued from '7-1': 'Job build entry mismatch'
422-
423-It was rescued because the BuildQueue.id == 1 isn't related to
424-Build.id == 7, so this pair relation is wrong.
425-
426-Let's test slaves with job identifier pointing non-existent
427-Build/BuildQueue entries. first a lost slave in status 'BUILDING':
428-
429- >>> lostbuilding_builder = MockBuilder(
430- ... 'Lost Building Slave', LostBuildingSlave())
431- >>> lostbuilding_builder.rescueIfLost(logger)
432- WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000':
433- 'Build 1000 is not available: 'Object not found''
434-
435-Then a lost slave in status 'WAITING':
436-
437- >>> lostwaiting_builder = MockBuilder(
438- ... 'Lost Waiting Slave', LostWaitingSlave())
439- >>> lostwaiting_builder.rescueIfLost(logger)
440- WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000':
441- 'Build 1000 is not available: 'Object not found''
442-
443-Both got rescued, as expected.
444-
445-A BUILDING or WAITING slave without a build assigned in the DB will also be rescued.
446-
447- >>> from lp.buildmaster.model.buildfarmjobbehavior import IdleBuildBehavior
448-
449- >>> lostbuilding_builder = MockBuilder(
450- ... 'Lost Building Slave', LostBuildingSlave())
451- >>> lostbuilding_builder.current_build_behavior = IdleBuildBehavior()
452- >>> lostbuilding_builder.rescueIfLost(logger)
453- WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': 'No job assigned to builder'
454-
455- >>> lostwaiting_builder = MockBuilder(
456- ... 'Lost Waiting Slave', LostWaitingSlave())
457- >>> lostwaiting_builder.current_build_behavior = IdleBuildBehavior()
458- >>> lostwaiting_builder.rescueIfLost(logger)
459- WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'No job assigned to builder'
460+ ... AbortedSlave, AbortingSlave, BuildingSlave,
461+ ... LostBuildingBrokenSlave, MockBuilder, OkSlave, WaitingSlave)
462
463 Slave-scanner will deactivate a 'lost-building' builder that could not
464 be aborted appropriately.
465@@ -124,6 +42,7 @@
466 ... 'Lost Building Broken Slave', LostBuildingBrokenSlave())
467
468 >>> lostbuilding_builder.updateStatus(logger)
469+ Aborting slave
470 WARNING:root:Lost Building Broken Slave (http://fake:0000) marked as failed due to: <Fault 8002: 'Could not abort'>
471 Traceback (most recent call last):
472 ...
473
474=== modified file 'lib/lp/soyuz/tests/soyuzbuilddhelpers.py'
475--- lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-03 03:38:55 +0000
476+++ lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-09 00:40:18 +0000
477@@ -7,11 +7,6 @@
478
479 __all__ = [
480 'MockBuilder',
481- 'SaneBuildingSlave',
482- 'SaneWaitingSlave',
483- 'InsaneWaitingSlave',
484- 'LostBuildingSlave',
485- 'LostWaitingSlave',
486 'LostBuildingBrokenSlave',
487 'BrokenSlave',
488 'OkSlave',
489@@ -36,9 +31,12 @@
490 class MockBuilder:
491 """Emulates a IBuilder class."""
492
493- current_build_behavior = BinaryPackageBuildBehavior(None)
494+ def __init__(self, name, slave, behavior=None):
495+ if behavior is None:
496+ self.current_build_behavior = BinaryPackageBuildBehavior(None)
497+ else:
498+ self.current_build_behavior = behavior
499
500- def __init__(self, name, slave):
501 self.slave = slave
502 self.builderok = True
503 self.manual = False
504@@ -58,9 +56,11 @@
505 return self.current_build_behavior.verifySlaveBuildID(slave_build_id)
506
507 def cleanSlave(self):
508+ print 'Cleaning slave'
509 return self.slave.clean()
510
511 def requestAbort(self):
512+ print 'Aborting slave'
513 return self.slave.abort()
514
515 def resumeSlave(self, logger):
516@@ -79,88 +79,6 @@
517 updateBuilderStatus(self, logger)
518
519
520-class SaneBuildingSlave:
521- """A mock slave that is currently building build 8 and buildqueue 1."""
522-
523- def status(self):
524- return ('BuilderStatus.BUILDING', '8-1', 'Doing something ...')
525-
526- def clean(self):
527- print 'Rescuing SaneSlave'
528-
529- def echo(self, *args):
530- return args
531-
532- def info(self):
533- return ['1.0', 'i386', ['debian']]
534-
535- def build(self, buildid, builder_type, chroot_sha1, filemap, args):
536- return ('BuildStatus.Building', buildid)
537-
538-
539-class SaneWaitingSlave:
540- """A mock slave that is currently waiting.
541-
542- Uses build 8 and buildqueue 1.
543- """
544-
545- def status(self):
546- return ('BuilderStatus.WAITING', 'BuildStatus.OK', '8-1')
547-
548- def clean(self):
549- print 'Rescuing SaneSlave'
550-
551-
552-class InsaneWaitingSlave:
553- """A mock slave waiting with a bogus Build/BuildQueue relation."""
554-
555- def status(self):
556- return ('BuilderStatus.WAITING', 'BuildStatus.OK', '7-1')
557-
558- def clean(self):
559- pass
560-
561-
562-class LostBuildingSlave:
563- """A mock slave building bogus Build/BuildQueue IDs."""
564-
565- def status(self):
566- return ('BuilderStatus.BUILDING', '1000-10000')
567-
568- def abort(self):
569- pass
570-
571-
572-class LostWaitingSlave:
573- """A mock slave waiting with bogus Build/BuildQueue IDs."""
574-
575- def status(self):
576- return ('BuilderStatus.WAITING', 'BuildStatus.OK', '1000-10000')
577-
578- def clean(self):
579- pass
580-
581-
582-class LostBuildingBrokenSlave:
583- """A mock slave building bogus Build/BuildQueue IDs that can't be aborted.
584-
585- When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort')
586- """
587-
588- def status(self):
589- return ('BuilderStatus.BUILDING', '1000-10000')
590-
591- def abort(self):
592- raise xmlrpclib.Fault(8002, "Could not abort")
593-
594-
595-class BrokenSlave:
596- """A mock slave that reports that it is broken."""
597-
598- def status(self):
599- raise xmlrpclib.Fault(8001, "Broken slave")
600-
601-
602 class OkSlave:
603 """An idle mock slave that prints information about itself.
604
605@@ -196,9 +114,15 @@
606 def fetchlogtail(self, size):
607 return 'BOGUS'
608
609+ def echo(self, *args):
610+ return args
611+
612 def clean(self):
613 pass
614
615+ def abort(self):
616+ pass
617+
618 def info(self):
619 return ('1.0', self.arch_tag, 'debian')
620
621@@ -223,9 +147,13 @@
622 class BuildingSlave(OkSlave):
623 """A mock slave that looks like it's currently building."""
624
625+ def __init__(self, build_id='1-1'):
626+ super(BuildingSlave, self).__init__()
627+ self.build_id = build_id
628+
629 def status(self):
630 buildlog = xmlrpclib.Binary("This is a build log")
631- return ('BuilderStatus.BUILDING', '1-1', buildlog)
632+ return ('BuilderStatus.BUILDING', self.build_id, buildlog)
633
634 def getFile(self, sum):
635 if sum == "buildlog":
636@@ -234,24 +162,19 @@
637 return s
638
639
640-class AbortedSlave(OkSlave):
641- """A mock slave that looks like it's aborted."""
642-
643- def status(self):
644- return ('BuilderStatus.ABORTED', '1-1')
645-
646-
647 class WaitingSlave(OkSlave):
648 """A mock slave that looks like it's currently waiting."""
649
650- def __init__(self, state, dependencies=None):
651+ def __init__(self, state='BuildStatus.OK', dependencies=None,
652+ build_id='1-1'):
653 super(WaitingSlave, self).__init__()
654 self.state = state
655 self.dependencies = dependencies
656+ self.build_id = build_id
657
658 def status(self):
659- return ('BuilderStatus.WAITING', self.state, '1-1', {},
660- self.dependencies )
661+ return ('BuilderStatus.WAITING', self.state, self.build_id, {},
662+ self.dependencies)
663
664 def getFile(self, sum):
665 if sum == "buildlog":
666@@ -259,8 +182,36 @@
667 s.headers = {'content-length':19}
668 return s
669
670+
671 class AbortingSlave(OkSlave):
672 """A mock slave that looks like it's in the process of aborting."""
673
674 def status(self):
675 return ('BuilderStatus.ABORTING', '1-1')
676+
677+
678+class AbortedSlave(OkSlave):
679+ """A mock slave that looks like it's aborted."""
680+
681+ def status(self):
682+ return ('BuilderStatus.ABORTED', '1-1')
683+
684+
685+class LostBuildingBrokenSlave:
686+ """A mock slave building bogus Build/BuildQueue IDs that can't be aborted.
687+
688+ When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort')
689+ """
690+
691+ def status(self):
692+ return ('BuilderStatus.BUILDING', '1000-10000')
693+
694+ def abort(self):
695+ raise xmlrpclib.Fault(8002, "Could not abort")
696+
697+
698+class BrokenSlave:
699+ """A mock slave that reports that it is broken."""
700+
701+ def status(self):
702+ raise xmlrpclib.Fault(8001, "Broken slave")