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
=== 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-09 00:40:18 +0000
@@ -2,36 +2,34 @@
2Builder Class2Builder Class
3=============3=============
44
5This test aims to meet the requirements of5The Builder class represents a slave machine in the build farm. These
6<https://launchpad.canonical.com/BasicTestCoverage> for the Builder class,6slaves are used to execute untrusted code -- for example when building
7which represents the Buildd Slave entity.7packages.
88
9Need auxiliar methods from zope toolchain:9There are several builders in the sample data. Let's examine the first.
10
11 >>> from lp.buildmaster.model.builder import Builder
12 >>> builder = Builder.get(1)
13
14As expected, it implements IBuilder.
1015
11 >>> from canonical.launchpad.webapp.testing import verifyObject16 >>> from canonical.launchpad.webapp.testing import verifyObject
12 >>> from zope.component import getUtility
13
14Importing Builder content class and its interface:
15
16 >>> from lp.buildmaster.model.builder import Builder
17 >>> from lp.buildmaster.interfaces.builder import IBuilder17 >>> from lp.buildmaster.interfaces.builder import IBuilder
1818 >>> verifyObject(IBuilder, builder)
19Get an instance of Builder from the current sampledata:19 True
20
21 >>> builder = Builder.get(1)
22
23Test some attributes:
2420
25 >>> print builder.name21 >>> print builder.name
26 bob22 bob
2723 >>> print builder.builderok
28 >>> builder.builderok = True24 True
29 >>> builder.failnotes = None25 >>> print builder.failnotes
3026 None
31A builder provides a current_build_behavior attribute to which all the27
32build-type specific behavior for the current build is delegated. For28Builders can take on different behaviors depending on the type of build
33our sample data, the behavior is specifically a behavior for dealing29they are currently processing. Each builder provides an attribute
34with binary packages30(current_build_behavior) to which all the build-type specific behavior
31for the current build is delegated. In the sample data, bob's current
32behavior is dealing with binary packages.
3533
36 >>> from zope.security.proxy import isinstance34 >>> from zope.security.proxy import isinstance
37 >>> from lp.soyuz.model.binarypackagebuildbehavior import (35 >>> from lp.soyuz.model.binarypackagebuildbehavior import (
@@ -40,86 +38,78 @@
40 ... builder.current_build_behavior, BinaryPackageBuildBehavior)38 ... builder.current_build_behavior, BinaryPackageBuildBehavior)
41 True39 True
4240
43Confirm we can get the slave xmlrpc interface41A builder has an XML-RPC proxy in the 'slave' attribute, which allows
42us to easily call methods on the slave machines.
4443
45 >>> s = builder.slave44 >>> s = builder.slave
4645
47Confirm that the urlbase is correct in that slave. (If the protocol changes,46The base URL of the proxy matches the builder's URL.
48this may change too)
4947
50 >>> s.urlbase == builder.url48 >>> s.urlbase == builder.url
51 True49 True
5250
53Check if the instance corresponds to the declared interface:
54
55 >>> verifyObject(IBuilder, builder)
56 True
57
5851
59BuilderSet52BuilderSet
60==========53==========
6154
62Now perform the tests for the Builder ContentSet class, BuilderSet.55Builders and groups thereof are managed through a utility, IBuilderSet.
6356
64Check if it can be imported:57 >>> from zope.component import getUtility
65
66 >>> from lp.buildmaster.interfaces.builder import IBuilderSet58 >>> from lp.buildmaster.interfaces.builder import IBuilderSet
67
68Check we can use the set as a utility:
69
70 >>> builderset = getUtility(IBuilderSet)59 >>> builderset = getUtility(IBuilderSet)
71
72Check if the instance returned as utility corresponds to its
73respective interface:
74
75 >>> verifyObject(IBuilderSet, builderset)60 >>> verifyObject(IBuilderSet, builderset)
76 True61 True
7762
78Check if the instance is iterable:63Iterating over a BuilderSet yields all registered builders.
7964
80 >>> for b in builderset:65 >>> for b in builderset:
81 ... b.id66 ... print b.name
82 167 bob
68 frog
69
70count() return the number of builders registered:
71
72 >>> builderset.count()
83 273 2
8474
85Check if the __getitem__ method:75Builders can be retrieved by name.
8676
87 >>> builderset['bob'].name77 >>> print builderset['bob'].name
88 u'bob'78 bob
8979 >>> print builderset['bad']
90Check now the specific method in the utility as new():80 None
81
82And also by ID.
83
84 >>> print builderset.get(2).name
85 frog
86 >>> print builderset.get(100).name
87 Traceback (most recent call last):
88 ...
89 SQLObjectNotFound: Object not found
90
91The 'new' method will create a new builder in the database.
9192
92 >>> bnew = builderset.new(1, 'http://dummy.com:8221/', 'dummy',93 >>> bnew = builderset.new(1, 'http://dummy.com:8221/', 'dummy',
93 ... 'Dummy Title', 'eh ?', 1)94 ... 'Dummy Title', 'eh ?', 1)
94 >>> bnew.name95 >>> bnew.name
95 u'dummy'96 u'dummy'
9697
97Check get() which returns a correspondent Builder instance to a given id:98'getBuilders' returns builders with the 'active' flag set, ordered by
9899virtualization status, architecture, then name.
99 >>> builderset.get(bnew.id).name100
100 u'dummy'101 >>> for b in builderset.getBuilders():
101102 ... print b.name
102Or raises an SQLObjectNotFound exception:103 bob
103104 dummy
104 >>> builderset.get(100)105 frog
105 Traceback (most recent call last):106 >>> login('foo.bar@canonical.com')
106 ...107 >>> bnew.active = False
107 SQLObjectNotFound: Object not found108 >>> login(ANONYMOUS)
108109 >>> for b in builderset.getBuilders():
109count() return the number of builder instance we have stored:110 ... print b.name
110111 bob
111 >>> builderset.count()112 frog
112 3
113
114getBuilder() method returns all the builders available. It seems the
115same than the own instance but we have plans to turn it aware of some
116attributes of builder instance as: builderok and trust.
117
118 >>> for b in builderset.getBuilders():
119 ... b.name
120 u'bob'
121 u'dummy'
122 u'frog'
123113
124'getBuildQueueSizeForProcessor' returns the number of pending builds114'getBuildQueueSizeForProcessor' returns the number of pending builds
125for a given Processor. The callsites can also control which build-farm115for a given Processor. The callsites can also control which build-farm
@@ -246,24 +236,76 @@
246A fictitious i386 variant is rejected, since there are no DASes with that236A fictitious i386 variant is rejected, since there are no DASes with that
247tag.237tag.
248238
249 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave239 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
250 >>> bob.setSlaveForTesting(OkSlave('i387'))240 >>> bob.setSlaveForTesting(OkSlave('i387'))
251 >>> bob.checkSlaveArchitecture()241 >>> bob.checkSlaveArchitecture()
252 Traceback (most recent call last):242 Traceback (most recent call last):
253 ...243 ...
254 BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86)244 BuildDaemonError: Bad slave architecture tag: i387 (registered family: x86)
255245
256hppa isn't in the x86 family, so it too is rejected.246hppa isn't in the x86 family, so it too is rejected.
257247
258 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave248 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
259 >>> bob.setSlaveForTesting(OkSlave('hppa'))249 >>> bob.setSlaveForTesting(OkSlave('hppa'))
260 >>> bob.checkSlaveArchitecture()250 >>> bob.checkSlaveArchitecture()
261 Traceback (most recent call last):251 Traceback (most recent call last):
262 ...252 ...
263 BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86)253 BuildDaemonError: Bad slave architecture tag: hppa (registered family: x86)
264254
265But i386, a real x86 variant, passes without objection.255But i386, a real x86 variant, passes without objection.
266256
267 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave257 >>> from lp.soyuz.tests.soyuzbuilddhelpers import OkSlave
268 >>> bob.setSlaveForTesting(OkSlave('i386'))258 >>> bob.setSlaveForTesting(OkSlave('i386'))
269 >>> bob.checkSlaveArchitecture()259 >>> bob.checkSlaveArchitecture()
260
261
262Rescuing lost slaves
263====================
264
265Builder.rescueIfLost() checks the build ID reported in the slave status
266against the database. If it isn't building what we think it should be,
267the current build will be aborted and the slave cleaned in preparation
268for a new task. The decision about the slave's correctness is left up
269to IBuildFarmJobBehavior.verifySlaveBuildID -- for these examples we
270will use a special behavior that just checks if the slave ID is 'good'.
271
272 >>> import logging
273 >>> from lp.buildmaster.interfaces.builder import CorruptBuildID
274 >>> from lp.soyuz.tests.soyuzbuilddhelpers import (
275 ... BuildingSlave, MockBuilder, OkSlave, WaitingSlave)
276
277 >>> class TestBuildBehavior:
278 ... def verifySlaveBuildID(self, build_id):
279 ... if build_id != 'good':
280 ... raise CorruptBuildID('Bad value')
281
282 >>> def rescue_slave_if_lost(slave):
283 ... builder = MockBuilder('mock', slave, TestBuildBehavior())
284 ... builder.rescueIfLost(logging.getLogger())
285
286An idle slave is not rescued.
287
288 >>> rescue_slave_if_lost(OkSlave())
289
290Slaves building or having built the correct build are not rescued
291either.
292
293 >>> rescue_slave_if_lost(BuildingSlave(build_id='good'))
294 >>> rescue_slave_if_lost(WaitingSlave(build_id='good'))
295
296But if a slave is building the wrong ID, it is declared lost and
297an abort is attempted. MockSlave prints out a message when it is aborted
298or cleaned.
299
300 >>> rescue_slave_if_lost(BuildingSlave(build_id='bad'))
301 Aborting slave
302 WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value'
303
304Slaves having completed an incorrect build are also declared lost,
305but there's no need to abort a completed build. Such builders are
306instead simply cleaned, ready for the next build.
307
308 >>> rescue_slave_if_lost(WaitingSlave(build_id='bad'))
309 Cleaning slave
310 WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value'
311
270312
=== modified file 'lib/lp/buildmaster/doc/buildfarmjobbehavior.txt'
--- lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-01-13 20:19:43 +0000
+++ lib/lp/buildmaster/doc/buildfarmjobbehavior.txt 2010-04-09 00:40:18 +0000
@@ -111,3 +111,11 @@
111 ...111 ...
112 BuildBehaviorMismatch: Builder was idle when asked to log the start of a112 BuildBehaviorMismatch: Builder was idle when asked to log the start of a
113 build.113 build.
114
115If a slave is working on a job while we think it is idle, it will always be
116aborted.
117
118 >>> bob.current_build_behavior.verifySlaveBuildID('foo')
119 Traceback (most recent call last):
120 ...
121 CorruptBuildID: No job assigned to builder
114122
=== modified file 'lib/lp/buildmaster/tests/test_manager.py'
--- lib/lp/buildmaster/tests/test_manager.py 2010-04-08 04:00:30 +0000
+++ lib/lp/buildmaster/tests/test_manager.py 2010-04-09 00:40:18 +0000
@@ -31,7 +31,7 @@
31from lp.buildmaster.tests.harness import BuilddManagerTestSetup31from lp.buildmaster.tests.harness import BuilddManagerTestSetup
32from lp.registry.interfaces.distribution import IDistributionSet32from lp.registry.interfaces.distribution import IDistributionSet
33from lp.soyuz.interfaces.build import IBuildSet33from lp.soyuz.interfaces.build import IBuildSet
34from lp.soyuz.tests.soyuzbuilddhelpers import SaneBuildingSlave34from lp.soyuz.tests.soyuzbuilddhelpers import BuildingSlave
35from lp.soyuz.tests.test_publishing import SoyuzTestPublisher35from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
3636
3737
@@ -671,7 +671,7 @@
671 self.assertTrue(builder.builderok)671 self.assertTrue(builder.builderok)
672672
673 job = getUtility(IBuildQueueSet).get(job.id)673 job = getUtility(IBuildQueueSet).get(job.id)
674 self.assertBuildingJob(job, builder, logtail='Doing something ...')674 self.assertBuildingJob(job, builder, logtail='This is a build log')
675675
676 def testScanUpdatesBuildingJobs(self):676 def testScanUpdatesBuildingJobs(self):
677 # The job assigned to a broken builder is rescued.677 # The job assigned to a broken builder is rescued.
@@ -682,7 +682,7 @@
682682
683 login('foo.bar@canonical.com')683 login('foo.bar@canonical.com')
684 builder.builderok = True684 builder.builderok = True
685 builder.setSlaveForTesting(SaneBuildingSlave())685 builder.setSlaveForTesting(BuildingSlave(build_id='8-1'))
686 transaction.commit()686 transaction.commit()
687 login(ANONYMOUS)687 login(ANONYMOUS)
688688
689689
=== modified file 'lib/lp/code/tests/test_recipebuilder.py'
--- lib/lp/code/tests/test_recipebuilder.py 2010-03-25 18:34:07 +0000
+++ lib/lp/code/tests/test_recipebuilder.py 2010-04-09 00:40:18 +0000
@@ -20,8 +20,8 @@
20 SourcePackageRecipeBuild)20 SourcePackageRecipeBuild)
21from lp.soyuz.adapters.archivedependencies import get_sources_list_for_building21from lp.soyuz.adapters.archivedependencies import get_sources_list_for_building
22from lp.soyuz.model.processor import ProcessorFamilySet22from lp.soyuz.model.processor import ProcessorFamilySet
23from lp.soyuz.tests.soyuzbuilddhelpers import (MockBuilder,23from lp.soyuz.tests.soyuzbuilddhelpers import (
24 SaneBuildingSlave,)24 MockBuilder, OkSlave)
25from lp.soyuz.tests.test_binarypackagebuildbehavior import (25from lp.soyuz.tests.test_binarypackagebuildbehavior import (
26 BaseTestVerifySlaveBuildID)26 BaseTestVerifySlaveBuildID)
27from lp.soyuz.tests.test_publishing import (27from lp.soyuz.tests.test_publishing import (
@@ -84,7 +84,7 @@
84 # VerifyBuildRequest won't raise any exceptions when called with a84 # VerifyBuildRequest won't raise any exceptions when called with a
85 # valid builder set.85 # valid builder set.
86 job = self.makeJob()86 job = self.makeJob()
87 builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave())87 builder = MockBuilder("bob-de-bouwer", OkSlave())
88 job.setBuilder(builder)88 job.setBuilder(builder)
89 logger = BufferLogger()89 logger = BufferLogger()
90 job.verifyBuildRequest(logger)90 job.verifyBuildRequest(logger)
@@ -136,7 +136,7 @@
136 # dispatchBuildToSlave will fail when there is not chroot tarball136 # dispatchBuildToSlave will fail when there is not chroot tarball
137 # available for the distroseries to build for.137 # available for the distroseries to build for.
138 job = self.makeJob()138 job = self.makeJob()
139 builder = MockBuilder("bob-de-bouwer", SaneBuildingSlave())139 builder = MockBuilder("bob-de-bouwer", OkSlave())
140 processorfamily = ProcessorFamilySet().getByProcessorName('386')140 processorfamily = ProcessorFamilySet().getByProcessorName('386')
141 builder.processor = processorfamily.processors[0]141 builder.processor = processorfamily.processors[0]
142 job.setBuilder(builder)142 job.setBuilder(builder)
143143
=== modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt'
--- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-08 15:29:39 +0000
+++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-04-09 00:40:18 +0000
@@ -32,90 +32,8 @@
32Import MockBuilder and a series of MockSlaves to be used in this test.32Import MockBuilder and a series of MockSlaves to be used in this test.
3333
34 >>> from lp.soyuz.tests.soyuzbuilddhelpers import (34 >>> from lp.soyuz.tests.soyuzbuilddhelpers import (
35 ... AbortedSlave, AbortingSlave, BuildingSlave, InsaneWaitingSlave,35 ... AbortedSlave, AbortingSlave, BuildingSlave,
36 ... LostBuildingBrokenSlave, LostBuildingSlave, LostWaitingSlave,36 ... LostBuildingBrokenSlave, MockBuilder, OkSlave, WaitingSlave)
37 ... MockBuilder, OkSlave, SaneBuildingSlave, SaneWaitingSlave,
38 ... WaitingSlave)
39
40Let's play with a Builder method designed to rescue build slaves
41that are processing unknown jobs. In real conditions, this situation
42only happens if the slave is processing deleted or modified BuildQueue
43entry, since Build entries are never removed. It might be caused by
44exceptions in slavescanner or queuebuilder scripts.
45
46When we figured this situation out, the procedure to rescue is to
47request the slave XMLRPC method 'clean', reseting the slave completely.
48
49We figured out if the building information is correct and sane by
50checking the job identifier field from status message information,
51which consists of "<Build.id>-<BuildQueue.id>".
52
53First let's emulate a sane and a lost slave. The SaneSlave returns a
54job identifier that exists in our sampledata, but the LostSlave
55returns a completely bogus one.
56
57The the mock slave.clean() method is modified to print a message for
58testing purposes.
59
60Initializing the sane_builder. It was not rescued, since the job
61identifier is sane (Build.id == 8 and BuildQueue.id == 1 exist):
62
63 >>> sanebuilding_builder = MockBuilder(
64 ... 'Sane Building Slave', SaneBuildingSlave())
65 >>> sanebuilding_builder.rescueIfLost(logger) is None
66 True
67
68A sane WAITING slave:
69
70 >>> sanewaiting_builder = MockBuilder(
71 ... 'Sane Waiting Slave', SaneWaitingSlave())
72 >>> sanewaiting_builder.rescueIfLost(logger) is None
73 True
74
75A insane WAITING slave, with wrong BuildQueue/Build relation:
76
77 >>> insanewaiting_builder = MockBuilder(
78 ... 'Insane Waiting Slave', InsaneWaitingSlave())
79 >>> insanewaiting_builder.rescueIfLost(logger)
80 WARNING:root:Builder 'Insane Waiting Slave' rescued from '7-1': 'Job build entry mismatch'
81
82It was rescued because the BuildQueue.id == 1 isn't related to
83Build.id == 7, so this pair relation is wrong.
84
85Let's test slaves with job identifier pointing non-existent
86Build/BuildQueue entries. first a lost slave in status 'BUILDING':
87
88 >>> lostbuilding_builder = MockBuilder(
89 ... 'Lost Building Slave', LostBuildingSlave())
90 >>> lostbuilding_builder.rescueIfLost(logger)
91 WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000':
92 'Build 1000 is not available: 'Object not found''
93
94Then a lost slave in status 'WAITING':
95
96 >>> lostwaiting_builder = MockBuilder(
97 ... 'Lost Waiting Slave', LostWaitingSlave())
98 >>> lostwaiting_builder.rescueIfLost(logger)
99 WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000':
100 'Build 1000 is not available: 'Object not found''
101
102Both got rescued, as expected.
103
104A BUILDING or WAITING slave without a build assigned in the DB will also be rescued.
105
106 >>> from lp.buildmaster.model.buildfarmjobbehavior import IdleBuildBehavior
107
108 >>> lostbuilding_builder = MockBuilder(
109 ... 'Lost Building Slave', LostBuildingSlave())
110 >>> lostbuilding_builder.current_build_behavior = IdleBuildBehavior()
111 >>> lostbuilding_builder.rescueIfLost(logger)
112 WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': 'No job assigned to builder'
113
114 >>> lostwaiting_builder = MockBuilder(
115 ... 'Lost Waiting Slave', LostWaitingSlave())
116 >>> lostwaiting_builder.current_build_behavior = IdleBuildBehavior()
117 >>> lostwaiting_builder.rescueIfLost(logger)
118 WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'No job assigned to builder'
11937
120Slave-scanner will deactivate a 'lost-building' builder that could not38Slave-scanner will deactivate a 'lost-building' builder that could not
121be aborted appropriately.39be aborted appropriately.
@@ -124,6 +42,7 @@
124 ... 'Lost Building Broken Slave', LostBuildingBrokenSlave())42 ... 'Lost Building Broken Slave', LostBuildingBrokenSlave())
12543
126 >>> lostbuilding_builder.updateStatus(logger)44 >>> lostbuilding_builder.updateStatus(logger)
45 Aborting slave
127 WARNING:root:Lost Building Broken Slave (http://fake:0000) marked as failed due to: <Fault 8002: 'Could not abort'>46 WARNING:root:Lost Building Broken Slave (http://fake:0000) marked as failed due to: <Fault 8002: 'Could not abort'>
128 Traceback (most recent call last):47 Traceback (most recent call last):
129 ...48 ...
13049
=== modified file 'lib/lp/soyuz/tests/soyuzbuilddhelpers.py'
--- lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-03 03:38:55 +0000
+++ lib/lp/soyuz/tests/soyuzbuilddhelpers.py 2010-04-09 00:40:18 +0000
@@ -7,11 +7,6 @@
77
8__all__ = [8__all__ = [
9 'MockBuilder',9 'MockBuilder',
10 'SaneBuildingSlave',
11 'SaneWaitingSlave',
12 'InsaneWaitingSlave',
13 'LostBuildingSlave',
14 'LostWaitingSlave',
15 'LostBuildingBrokenSlave',10 'LostBuildingBrokenSlave',
16 'BrokenSlave',11 'BrokenSlave',
17 'OkSlave',12 'OkSlave',
@@ -36,9 +31,12 @@
36class MockBuilder:31class MockBuilder:
37 """Emulates a IBuilder class."""32 """Emulates a IBuilder class."""
3833
39 current_build_behavior = BinaryPackageBuildBehavior(None)34 def __init__(self, name, slave, behavior=None):
35 if behavior is None:
36 self.current_build_behavior = BinaryPackageBuildBehavior(None)
37 else:
38 self.current_build_behavior = behavior
4039
41 def __init__(self, name, slave):
42 self.slave = slave40 self.slave = slave
43 self.builderok = True41 self.builderok = True
44 self.manual = False42 self.manual = False
@@ -58,9 +56,11 @@
58 return self.current_build_behavior.verifySlaveBuildID(slave_build_id)56 return self.current_build_behavior.verifySlaveBuildID(slave_build_id)
5957
60 def cleanSlave(self):58 def cleanSlave(self):
59 print 'Cleaning slave'
61 return self.slave.clean()60 return self.slave.clean()
6261
63 def requestAbort(self):62 def requestAbort(self):
63 print 'Aborting slave'
64 return self.slave.abort()64 return self.slave.abort()
6565
66 def resumeSlave(self, logger):66 def resumeSlave(self, logger):
@@ -79,88 +79,6 @@
79 updateBuilderStatus(self, logger)79 updateBuilderStatus(self, logger)
8080
8181
82class SaneBuildingSlave:
83 """A mock slave that is currently building build 8 and buildqueue 1."""
84
85 def status(self):
86 return ('BuilderStatus.BUILDING', '8-1', 'Doing something ...')
87
88 def clean(self):
89 print 'Rescuing SaneSlave'
90
91 def echo(self, *args):
92 return args
93
94 def info(self):
95 return ['1.0', 'i386', ['debian']]
96
97 def build(self, buildid, builder_type, chroot_sha1, filemap, args):
98 return ('BuildStatus.Building', buildid)
99
100
101class SaneWaitingSlave:
102 """A mock slave that is currently waiting.
103
104 Uses build 8 and buildqueue 1.
105 """
106
107 def status(self):
108 return ('BuilderStatus.WAITING', 'BuildStatus.OK', '8-1')
109
110 def clean(self):
111 print 'Rescuing SaneSlave'
112
113
114class InsaneWaitingSlave:
115 """A mock slave waiting with a bogus Build/BuildQueue relation."""
116
117 def status(self):
118 return ('BuilderStatus.WAITING', 'BuildStatus.OK', '7-1')
119
120 def clean(self):
121 pass
122
123
124class LostBuildingSlave:
125 """A mock slave building bogus Build/BuildQueue IDs."""
126
127 def status(self):
128 return ('BuilderStatus.BUILDING', '1000-10000')
129
130 def abort(self):
131 pass
132
133
134class LostWaitingSlave:
135 """A mock slave waiting with bogus Build/BuildQueue IDs."""
136
137 def status(self):
138 return ('BuilderStatus.WAITING', 'BuildStatus.OK', '1000-10000')
139
140 def clean(self):
141 pass
142
143
144class LostBuildingBrokenSlave:
145 """A mock slave building bogus Build/BuildQueue IDs that can't be aborted.
146
147 When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort')
148 """
149
150 def status(self):
151 return ('BuilderStatus.BUILDING', '1000-10000')
152
153 def abort(self):
154 raise xmlrpclib.Fault(8002, "Could not abort")
155
156
157class BrokenSlave:
158 """A mock slave that reports that it is broken."""
159
160 def status(self):
161 raise xmlrpclib.Fault(8001, "Broken slave")
162
163
164class OkSlave:82class OkSlave:
165 """An idle mock slave that prints information about itself.83 """An idle mock slave that prints information about itself.
16684
@@ -196,9 +114,15 @@
196 def fetchlogtail(self, size):114 def fetchlogtail(self, size):
197 return 'BOGUS'115 return 'BOGUS'
198116
117 def echo(self, *args):
118 return args
119
199 def clean(self):120 def clean(self):
200 pass121 pass
201122
123 def abort(self):
124 pass
125
202 def info(self):126 def info(self):
203 return ('1.0', self.arch_tag, 'debian')127 return ('1.0', self.arch_tag, 'debian')
204128
@@ -223,9 +147,13 @@
223class BuildingSlave(OkSlave):147class BuildingSlave(OkSlave):
224 """A mock slave that looks like it's currently building."""148 """A mock slave that looks like it's currently building."""
225149
150 def __init__(self, build_id='1-1'):
151 super(BuildingSlave, self).__init__()
152 self.build_id = build_id
153
226 def status(self):154 def status(self):
227 buildlog = xmlrpclib.Binary("This is a build log")155 buildlog = xmlrpclib.Binary("This is a build log")
228 return ('BuilderStatus.BUILDING', '1-1', buildlog)156 return ('BuilderStatus.BUILDING', self.build_id, buildlog)
229157
230 def getFile(self, sum):158 def getFile(self, sum):
231 if sum == "buildlog":159 if sum == "buildlog":
@@ -234,24 +162,19 @@
234 return s162 return s
235163
236164
237class AbortedSlave(OkSlave):
238 """A mock slave that looks like it's aborted."""
239
240 def status(self):
241 return ('BuilderStatus.ABORTED', '1-1')
242
243
244class WaitingSlave(OkSlave):165class WaitingSlave(OkSlave):
245 """A mock slave that looks like it's currently waiting."""166 """A mock slave that looks like it's currently waiting."""
246167
247 def __init__(self, state, dependencies=None):168 def __init__(self, state='BuildStatus.OK', dependencies=None,
169 build_id='1-1'):
248 super(WaitingSlave, self).__init__()170 super(WaitingSlave, self).__init__()
249 self.state = state171 self.state = state
250 self.dependencies = dependencies172 self.dependencies = dependencies
173 self.build_id = build_id
251174
252 def status(self):175 def status(self):
253 return ('BuilderStatus.WAITING', self.state, '1-1', {},176 return ('BuilderStatus.WAITING', self.state, self.build_id, {},
254 self.dependencies )177 self.dependencies)
255178
256 def getFile(self, sum):179 def getFile(self, sum):
257 if sum == "buildlog":180 if sum == "buildlog":
@@ -259,8 +182,36 @@
259 s.headers = {'content-length':19}182 s.headers = {'content-length':19}
260 return s183 return s
261184
185
262class AbortingSlave(OkSlave):186class AbortingSlave(OkSlave):
263 """A mock slave that looks like it's in the process of aborting."""187 """A mock slave that looks like it's in the process of aborting."""
264188
265 def status(self):189 def status(self):
266 return ('BuilderStatus.ABORTING', '1-1')190 return ('BuilderStatus.ABORTING', '1-1')
191
192
193class AbortedSlave(OkSlave):
194 """A mock slave that looks like it's aborted."""
195
196 def status(self):
197 return ('BuilderStatus.ABORTED', '1-1')
198
199
200class LostBuildingBrokenSlave:
201 """A mock slave building bogus Build/BuildQueue IDs that can't be aborted.
202
203 When 'aborted' it raises an xmlrpclib.Fault(8002, 'Could not abort')
204 """
205
206 def status(self):
207 return ('BuilderStatus.BUILDING', '1000-10000')
208
209 def abort(self):
210 raise xmlrpclib.Fault(8002, "Could not abort")
211
212
213class BrokenSlave:
214 """A mock slave that reports that it is broken."""
215
216 def status(self):
217 raise xmlrpclib.Fault(8001, "Broken slave")