Merge lp:~wgrant/launchpad/move-rescueiflost-tests into lp:launchpad
- move-rescueiflost-tests
- Merge into devel
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 |
Related bugs: |
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-
Description of the change
This branch moves buildd-
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 IdleBuildBehavi
I also rewrote the start of builder.txt, since its style offended me.
William Grant (wgrant) wrote : | # |
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/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -2,36 +2,34 @@
> > Builder Class
> > =============
> >
> > -This test aims to meet the requirements of
> > -<https:/
> > -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.
> > + >>> 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_
> > +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.
> > @@ -40,85 +38,77 @@
> > ... builder.
> > 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(
> > - 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.
> > -
> > -Check we can use the set as a utility:
> > -
> > >>> builderset = getUtility(
> > -
> > -Check if the instance returned as utility corresponds to its
> > -respective interface:
> > -
> > >>> verifyObject(
> > True
> >
> > -Check if the instance is iterable:
> > +Iterating over a BuilderSet yields all registered builders.
...
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 :)
Preview Diff
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") |
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' buildmaster/ doc/builder. txt 2010-03-24 10:24:22 +0000 buildmaster/ doc/builder. txt 2010-04-03 04:39:18 +0000 /launchpad. canonical. com/BasicTestCo verage> for the Builder class, model.builder import Builder
> --- lib/lp/
> +++ lib/lp/
> @@ -2,36 +2,34 @@
> Builder Class
> =============
>
> -This test aims to meet the requirements of
> -<https:/
> -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.
> + >>> builder = Builder.get(1)
> +
> +As expected, it implements IBuilder.
Nice changes.
> +Builders can take on different behaviors depending on the type of build build_behavior) to which all the build-type specific behavior
> +they are currently processing. Each builder provides an attribute
> +(current_
> +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 model.binarypac kagebuildbehavi or import ( current_ build_behavior, BinaryPackageBu ildBehavior) IBuilder, builder) interfaces. builder import IBuilderSet IBuilderSet) IBuilderSet, builderset)
> >>> from lp.soyuz.
> @@ -40,85 +38,77 @@
> ... builder.
> 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(
> - 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.
> -
> -Check we can use the set as a utility:
> -
> >>> builderset = getUtility(
> -
> -Check if the instance returned as utility corresponds to its
> -respective interface:
> -
> >>> verifyObject(
> 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:
> -
...