Merge lp:~jtv/launchpad/bug-537866 into lp:launchpad
- bug-537866
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~jtv/launchpad/bug-537866 |
Merge into: | lp:launchpad |
Diff against target: |
695 lines (+372/-77) 9 files modified
lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py (+1/-1) lib/lp/buildmaster/model/buildfarmjobbehavior.py (+94/-10) lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py (+80/-8) lib/lp/code/model/recipebuilder.py (+11/-2) lib/lp/soyuz/doc/buildd-slavescanner.txt (+4/-3) lib/lp/testing/factory.py (+87/-40) lib/lp/translations/doc/translationtemplatesbuildbehavior.txt (+41/-12) lib/lp/translations/model/translationtemplatesbuildbehavior.py (+17/-1) lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py (+37/-0) |
To merge this branch: | bzr merge lp:~jtv/launchpad/bug-537866 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guilherme Salgado (community) | Approve | ||
Review via email: mp+21238@code.launchpad.net |
Commit message
Fix branch_url passed to the slave; fix slave build id verification for TranslationTemp
Description of the change
= Bug 537866 =
Two bugs with generating translation templates in the build farm based on bzr branches are fixed here:
* Despite the TranslationTemp
* BuildFarmJobBeh
The first of these is a simple matter of using the stored parameters instead of recomputing them. Technically it may be nicer to compute the branch URL in TranslationTemp
The second happens because the base BuildFarmJobBeh
In order to avoid code duplication in doing so, I extracted two helpers from the base verifySlaveBuildID: one verifies the Build id contained in the name, and the other the BuildQueue id. The main method just extracts those two parts, calls the helpers, and finally checks that the objects are properly matched. The two helpers ended up so similar that I couldn't help creating yet a third helper that implements either, depending on some customizing parameters.
Implementing the tests required me to produce a realistic Build object. This hurts, but a _relatively_ easy way to do it was to create a SourcePackageRe
To test:
{{{
./bin/test -vv -t test_translatio
}}}
For Q/A, go through the instructions at https:/
No lint.
Jeroen
Jeroen T. Vermeulen (jtv) wrote : | # |
This is scary. Some arbitrary characters seem to have changed in the code after I last ran "make lint" and the tests. The duplicate test method names (where the word "Queue" was missing in two places) may have been an omission on my part as I continued adding tests at a late stage, though I think these were from an earlier state that I also ran through lint. The "utem" and missing spaces definitely passed lint since I last worked on them.
I hope it's simply because of the mouse cursor moving and activating a different window while I'm typing (as it likes to do for some reason—I know there's a setting for that but I guess my touchpad driver doesn't obey it) but in the past few hours I've also had unexpected characters appearing when copy/pasting. I'll reboot as soon as possible.
Anyway, I've removed the two helper methods from the interface and updated the doc strings.
Guilherme Salgado (salgado) wrote : | # |
I was doing a second pass on this but then you told me you've got some
more things you need to change here, so I'm sending what I have so far.
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -3,10 +3,18 @@
>
> """Unit tests for BuildFarmJobBeh
>
> -from unittest import TestCase, TestLoader
> +from unittest import TestLoader
> +
> +from zope.component import getUtility
> +
> +from canonical.
> +from lp.testing import TestCaseWithFactory
>
> from lp.buildmaster.
> +from lp.buildmaster.
> from lp.buildmaster.
> +from lp.registry.
> +from lp.soyuz.
>
>
> class FakeBuildFarmJob:
> @@ -14,27 +22,90 @@
> pass
>
>
> -class TestBuildFarmJo
> +class TestBuildFarmJo
> """Test very small, basic bits of BuildFarmJobBeh
>
> - def setUp(self):
> - self.behavior = BuildFarmJobBeh
> + layer = ZopelessDatabas
> +
> + def _makeBehavior(self, buildfarmjob=None):
> + """Create a `BuildFarmJobBe
> + if buildfarmjob is None:
> + buildfarmjob = FakeBuildFarmJob()
> + return BuildFarmJobBeh
> +
> + def _makeBuild(self):
> + """Create a `Build` object."""
> + x86 = getUtility(
> + distroarchseries = self.factory.
> + architecturetag
> + distroseries = distroarchserie
> + archive = self.factory.
> + distribution=
> + pocket = PackagePublishi
> + spr = self.factory.
> +
> + return spr.createBuild(
> + distroarchserie
> +
> + def _makeBuildQueue
> + """Create a `BuildQueue` object."""
> + return self.factory.
>
> def test_extractBui
> # extractBuildStatus picks the name of the build status out of a
> # dict describing the slave's status.
> slave_status = {'build_status': 'BuildStatus.
> + behavior = self._makeBehav
> self.assertEqual(
> BuildStatus.
> - self.behavior.
> + behavior.
>
> def test_extractBui
> # extractBuildStatus errors out when the status string is not
> # of the form it expects.
> slave_status = ...
Jeroen T. Vermeulen (jtv) wrote : | # |
> I was doing a second pass on this but then you told me you've got some
> more things you need to change here, so I'm sending what I have so far.
That's right. The latest change was in the translationtemp
> > === modified file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -2007,46 +2007,35 @@
> > def getAnySourcePac
> > return SourcePackageUr
> >
> > - def makeSourcePacka
> > - distroseries=None,
> maintainer=None,
> > - creator=None, component=None,
> > - section=None, urgency=None,
> > - version=None, archive=None,
> > - builddepends=None,
> > - builddependsind
> > - build_conflicts
> > - build_conflicts
> > - architecturehin
> > - dateremoved=None,
> > - date_uploaded=
> > - pocket=None,
> > - status=None,
> > - scheduleddeleti
> > - dsc_standards_
> > - dsc_format='1.0',
> > - dsc_binaries=
> > - ):
> > - if sourcepackagename is None:
> > - sourcepackagename = self.makeSource
> > - spn = sourcepackagename
> > + def makeSPR(self, archive=None, sourcepackagena
> distroseries=None,
>
> Why not makeSourcePacka
> existing ones and avoids the abbreviation, which are not good anyway.
Sure. It's only 5 lines extra; I thought it would be worse. I admit it: I abbreviated to avoid having to line up the parameters on all the following lines behind the opening parenthesis.
> > + if sourcepackagename is None:
> > + sourcepackagename = self.makeSource
> > + spn = sourcepackagename
>
> Would you mind dropping the line above and using 'sourcepackagename' in the
> single place that uses it below?
Not at all. I was just copying over the original code, but now that it's broken down into more manageable pieces, might as well clean up a bit more.
> > @@ -2064,12 +2053,13 @@
> > if creator is None:
> > creator = self.makePerson()
> >
> > + if dscsigningkey is None:
> > + gpg_key = self.makeGPGKey
>
> This is broken now -- gpg_key is no longer used, so y...
Guilherme Salgado (salgado) wrote : | # |
On Fri, 2010-03-12 at 20:24 +0000, Jeroen T. Vermeulen wrote:
> > > + def makeSourcePacka
> > > + distroseries=None,
> > maintainer=None,
> > > + creator=None, component=None,
> > > + section=None, urgency=None,
> > > + version=None, archive=None,
> > > + builddepends=None,
> > > + builddependsind
> > > + build_conflicts
> > > + build_conflicts
> > > + architecturehin
> > > + dateremoved=None,
> > > + date_uploaded=
> > > + pocket=None,
> > > + status=None,
> > > + scheduleddeleti
> > > + dsc_standards_
> > > + dsc_format='1.0',
> > > + dsc_binaries=
> > > + ):
> > > + if pocket is None:
> > > + pocket = self.getAnyPocket()
> > > +
> > > + if status is None:
> > > + status = PackagePublishi
> > > +
> > > + spr = self.makeSPR(
> > > + archive=archive,
> > > + sourcepackagena
> > > + distroseries=
> > > + maintainer=
> > > + creator=creator, component=
> > > + section=section,
> > > + urgency=urgency,
> > > + version=version,
> > > + builddepends=
> > > + builddependsind
> > > + build_conflicts
> > > + build_conflicts
> > > + architecturehin
> > > + dsc_standards_
> > > + dsc_format=
> > > + dsc_binaries=
> > > + date_uploaded=
> > >
> > > sspph = SourcePackagePu
> > > - distroseries=
> > > + distroseries=
> >
> > Is this change needed? Couldn't you keep using the distroseries passed as
> > argument?
>
> Again, you're right. I just need to create a distroseries here if I don't already have one, so it adds a tiny bit of duplication.
>
Oh, it wasn't clear to me that you were doing that because makeSPR would
create the distro when None was given to it. If there was a comment I
wouldn't have complained here.
Anyway, I'm happy if you want to leave this tiny bit of duplication but
I'd also be happy with what you had before, a...
Preview Diff
1 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py' |
2 | --- lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-01-21 05:03:16 +0000 |
3 | +++ lib/lp/buildmaster/interfaces/buildfarmjobbehavior.py 2010-03-13 10:43:28 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | # pylint: disable-msg=E0211,E0213 |
10 | |
11 | === modified file 'lib/lp/buildmaster/model/buildfarmjobbehavior.py' |
12 | --- lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-05 13:52:32 +0000 |
13 | +++ lib/lp/buildmaster/model/buildfarmjobbehavior.py 2010-03-13 10:43:28 +0000 |
14 | @@ -1,4 +1,4 @@ |
15 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
16 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
17 | # GNU Affero General Public License version 3 (see the file LICENSE). |
18 | |
19 | # pylint: disable-msg=E0211,E0213 |
20 | @@ -17,17 +17,22 @@ |
21 | import xmlrpclib |
22 | |
23 | from sqlobject import SQLObjectNotFound |
24 | + |
25 | from zope.component import getUtility |
26 | from zope.interface import implements |
27 | -from zope.security.proxy import removeSecurityProxy |
28 | +from zope.security.proxy import removeSecurityProxy, isinstance as zisinstance |
29 | |
30 | from canonical import encoding |
31 | from canonical.librarian.interfaces import ILibrarianClient |
32 | + |
33 | +from canonical.launchpad.webapp.interfaces import NotFoundError |
34 | from lp.buildmaster.interfaces.builder import CorruptBuildID |
35 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
36 | BuildBehaviorMismatch, IBuildFarmJobBehavior) |
37 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
38 | +from lp.buildmaster.model.buildqueue import BuildQueue |
39 | from lp.services.job.interfaces.job import JobStatus |
40 | +from lp.soyuz.interfaces.build import IBuildSet |
41 | |
42 | |
43 | class BuildFarmJobBehaviorBase: |
44 | @@ -59,22 +64,101 @@ |
45 | The default behavior is that we don't add any extra values.""" |
46 | return {} |
47 | |
48 | + def _helpVerifyBuildIDComponent(self, raw_id, item_type, finder): |
49 | + """Helper for verifying parts of a `BuildFarmJob` name. |
50 | + |
51 | + Different `IBuildFarmJob` implementations can have different |
52 | + ways of constructing their identifying names. The names are |
53 | + produced by `IBuildFarmJob.getName` and verified by |
54 | + `IBuildFarmJobBehavior.verifySlaveBuildID`. |
55 | + |
56 | + This little helper makes it easier to verify an object id |
57 | + embedded in that name, check that it's a valid number, and |
58 | + retrieve the associated database object. |
59 | + |
60 | + :param raw_id: An unverified id string as extracted from the |
61 | + build name. The method will verify that it is a number, and |
62 | + try to retrieve the associated object. |
63 | + :param item_type: The type of object this id represents. Should |
64 | + be a class. |
65 | + :param finder: A function that, given an integral id, finds the |
66 | + associated object of type `item_type`. |
67 | + :raise CorruptBuildID: If `raw_id` is malformed in some way or |
68 | + the associated `item_type` object is not found. |
69 | + :return: An object that is an instance of `item_type`. |
70 | + """ |
71 | + type_name = item_type.__name__ |
72 | + try: |
73 | + numeric_id = int(raw_id) |
74 | + except ValueError: |
75 | + raise CorruptBuildID( |
76 | + "%s ID is not a number: '%s'" % (type_name, raw_id)) |
77 | + |
78 | + try: |
79 | + item = finder(numeric_id) |
80 | + except (NotFoundError, SQLObjectNotFound), reason: |
81 | + raise CorruptBuildID( |
82 | + "%s %d is not available: %s" % ( |
83 | + type_name, numeric_id, reason)) |
84 | + except Exception, reason: |
85 | + raise CorruptBuildID( |
86 | + "Error while looking up %s %d: %s" % ( |
87 | + type_name, numeric_id, reason)) |
88 | + |
89 | + if item is None: |
90 | + raise CorruptBuildID("There is no %s with id %d." % ( |
91 | + type_name, numeric_id)) |
92 | + |
93 | + assert zisinstance(item, item_type), ( |
94 | + "Looked for %s, but found %s." % (type_name, repr(item))) |
95 | + |
96 | + return item |
97 | + |
98 | + def getVerifiedBuild(self, raw_id): |
99 | + """Verify and retrieve the `Build` component of a slave build id. |
100 | + |
101 | + This does part of the verification for `verifySlaveBuildID`. |
102 | + |
103 | + By default, a `BuildFarmJob` has an identifying name of the form |
104 | + "b-q", where b is the id of its `Build` and q is the id of its |
105 | + `BuildQueue` record. |
106 | + |
107 | + Use `getVerifiedBuild` to verify the "b" part, and retrieve the |
108 | + associated `Build`. |
109 | + """ |
110 | + # Avoid circular import. |
111 | + from lp.soyuz.model.build import Build |
112 | + |
113 | + return self._helpVerifyBuildIDComponent( |
114 | + raw_id, Build, getUtility(IBuildSet).getByBuildID) |
115 | + |
116 | + def getVerifiedBuildQueue(self, raw_id): |
117 | + """Verify and retrieve the `BuildQueue` component of a slave build id. |
118 | + |
119 | + This does part of the verification for `verifySlaveBuildID`. |
120 | + |
121 | + By default, a `BuildFarmJob` has an identifying name of the form |
122 | + "b-q", where b is the id of its `Build` and q is the id of its |
123 | + `BuildQueue` record. |
124 | + |
125 | + Use `getVerifiedBuildQueue` to verify the "q" part, and retrieve |
126 | + the associated `BuildQueue` object. |
127 | + """ |
128 | + return self._helpVerifyBuildIDComponent( |
129 | + raw_id, BuildQueue, getUtility(IBuildQueueSet).get) |
130 | + |
131 | def verifySlaveBuildID(self, slave_build_id): |
132 | """See `IBuildFarmJobBehavior`.""" |
133 | # Extract information from the identifier. |
134 | try: |
135 | build_id, queue_item_id = slave_build_id.split('-') |
136 | - build_id = int(build_id) |
137 | - queue_item_id = int(queue_item_id) |
138 | except ValueError: |
139 | raise CorruptBuildID('Malformed build ID') |
140 | + |
141 | + build = self.getVerifiedBuild(build_id) |
142 | + queue_item = self.getVerifiedBuildQueue(queue_item_id) |
143 | |
144 | - try: |
145 | - queue_item = getUtility(IBuildQueueSet).get(queue_item_id) |
146 | - # Check whether build and buildqueue are properly related. |
147 | - except SQLObjectNotFound, reason: |
148 | - raise CorruptBuildID(str(reason)) |
149 | - if queue_item.specific_job.build.id != build_id: |
150 | + if build != queue_item.specific_job.build: |
151 | raise CorruptBuildID('Job build entry mismatch') |
152 | |
153 | def updateBuild(self, queueItem): |
154 | |
155 | === modified file 'lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py' |
156 | --- lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-06 04:57:40 +0000 |
157 | +++ lib/lp/buildmaster/tests/test_buildfarmjobbehavior.py 2010-03-13 10:43:28 +0000 |
158 | @@ -3,10 +3,18 @@ |
159 | |
160 | """Unit tests for BuildFarmJobBehaviorBase.""" |
161 | |
162 | -from unittest import TestCase, TestLoader |
163 | +from unittest import TestLoader |
164 | + |
165 | +from zope.component import getUtility |
166 | + |
167 | +from canonical.testing.layers import ZopelessDatabaseLayer |
168 | +from lp.testing import TestCaseWithFactory |
169 | |
170 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
171 | +from lp.buildmaster.interfaces.builder import CorruptBuildID |
172 | from lp.buildmaster.model.buildfarmjobbehavior import BuildFarmJobBehaviorBase |
173 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
174 | +from lp.soyuz.interfaces.processor import IProcessorFamilySet |
175 | |
176 | |
177 | class FakeBuildFarmJob: |
178 | @@ -14,27 +22,91 @@ |
179 | pass |
180 | |
181 | |
182 | -class TestBuildFarmJobBehaviorBase(TestCase): |
183 | +class TestBuildFarmJobBehaviorBase(TestCaseWithFactory): |
184 | """Test very small, basic bits of BuildFarmJobBehaviorBase.""" |
185 | |
186 | - def setUp(self): |
187 | - self.behavior = BuildFarmJobBehaviorBase(FakeBuildFarmJob()) |
188 | + layer = ZopelessDatabaseLayer |
189 | + |
190 | + def _makeBehavior(self, buildfarmjob=None): |
191 | + """Create a `BuildFarmJobBehaviorBase`.""" |
192 | + if buildfarmjob is None: |
193 | + buildfarmjob = FakeBuildFarmJob() |
194 | + return BuildFarmJobBehaviorBase(buildfarmjob) |
195 | + |
196 | + def _makeBuild(self): |
197 | + """Create a `Build` object.""" |
198 | + x86 = getUtility(IProcessorFamilySet).getByName('x86') |
199 | + distroarchseries = self.factory.makeDistroArchSeries( |
200 | + architecturetag='x86', processorfamily=x86) |
201 | + distroseries = distroarchseries.distroseries |
202 | + archive = self.factory.makeArchive( |
203 | + distribution=distroseries.distribution) |
204 | + pocket = PackagePublishingPocket.RELEASE |
205 | + spr = self.factory.makeSourcePackageRelease( |
206 | + distroseries=distroseries, archive=archive) |
207 | + |
208 | + return spr.createBuild( |
209 | + distroarchseries=distroarchseries, pocket=pocket, archive=archive) |
210 | + |
211 | + def _makeBuildQueue(self): |
212 | + """Create a `BuildQueue` object.""" |
213 | + return self.factory.makeSourcePackageRecipeBuildJob() |
214 | |
215 | def test_extractBuildStatus_baseline(self): |
216 | # extractBuildStatus picks the name of the build status out of a |
217 | # dict describing the slave's status. |
218 | slave_status = {'build_status': 'BuildStatus.BUILDING'} |
219 | + behavior = self._makeBehavior() |
220 | self.assertEqual( |
221 | BuildStatus.BUILDING.name, |
222 | - self.behavior.extractBuildStatus(slave_status)) |
223 | + behavior.extractBuildStatus(slave_status)) |
224 | |
225 | def test_extractBuildStatus_malformed(self): |
226 | # extractBuildStatus errors out when the status string is not |
227 | # of the form it expects. |
228 | slave_status = {'build_status': 'BUILDING'} |
229 | - self.assertRaises( |
230 | - AssertionError, |
231 | - self.behavior.extractBuildStatus, slave_status) |
232 | + behavior = self._makeBehavior() |
233 | + self.assertRaises( |
234 | + AssertionError, behavior.extractBuildStatus, slave_status) |
235 | + |
236 | + def test_getVerifiedBuild_success(self): |
237 | + build = self._makeBuild() |
238 | + behavior = self._makeBehavior() |
239 | + raw_id = str(build.id) |
240 | + |
241 | + self.assertEqual(build, behavior.getVerifiedBuild(raw_id)) |
242 | + |
243 | + def test_getVerifiedBuild_malformed(self): |
244 | + behavior = self._makeBehavior() |
245 | + self.assertRaises(CorruptBuildID, behavior.getVerifiedBuild, 'hi!') |
246 | + |
247 | + def test_getVerifiedBuild_notfound(self): |
248 | + build = self._makeBuild() |
249 | + behavior = self._makeBehavior() |
250 | + nonexistent_id = str(build.id + 99) |
251 | + |
252 | + self.assertRaises( |
253 | + CorruptBuildID, behavior.getVerifiedBuild, nonexistent_id) |
254 | + |
255 | + def test_getVerifiedBuildQueue_success(self): |
256 | + buildqueue = self._makeBuildQueue() |
257 | + behavior = self._makeBehavior() |
258 | + raw_id = str(buildqueue.id) |
259 | + |
260 | + self.assertEqual(buildqueue, behavior.getVerifiedBuildQueue(raw_id)) |
261 | + |
262 | + def test_getVerifiedBuildQueue_malformed(self): |
263 | + behavior = self._makeBehavior() |
264 | + self.assertRaises( |
265 | + CorruptBuildID, behavior.getVerifiedBuildQueue, 'bye!') |
266 | + |
267 | + def test_getVerifiedBuildQueue_notfound(self): |
268 | + buildqueue = self._makeBuildQueue() |
269 | + behavior = self._makeBehavior() |
270 | + nonexistent_id = str(buildqueue.id + 99) |
271 | + |
272 | + self.assertRaises( |
273 | + CorruptBuildID, behavior.getVerifiedBuildQueue, nonexistent_id) |
274 | |
275 | |
276 | def test_suite(): |
277 | |
278 | === modified file 'lib/lp/code/model/recipebuilder.py' |
279 | --- lib/lp/code/model/recipebuilder.py 2010-02-05 15:06:28 +0000 |
280 | +++ lib/lp/code/model/recipebuilder.py 2010-03-13 10:43:28 +0000 |
281 | @@ -8,7 +8,7 @@ |
282 | 'RecipeBuildBehavior', |
283 | ] |
284 | |
285 | -from zope.component import adapts |
286 | +from zope.component import adapts, getUtility |
287 | from zope.interface import implements |
288 | |
289 | from lp.archiveuploader.permission import check_upload_to_pocket |
290 | @@ -18,7 +18,8 @@ |
291 | from lp.buildmaster.model.buildfarmjobbehavior import ( |
292 | BuildFarmJobBehaviorBase) |
293 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
294 | - ISourcePackageRecipeBuildJob) |
295 | + ISourcePackageRecipeBuildJob, ISourcePackageRecipeBuildSource) |
296 | +from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild |
297 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
298 | from lp.soyuz.adapters.archivedependencies import ( |
299 | get_primary_current_component, get_sources_list_for_building) |
300 | @@ -168,3 +169,11 @@ |
301 | extra_info['logtail'] = raw_slave_status[2] |
302 | |
303 | return extra_info |
304 | + |
305 | + def getVerifiedBuild(self, raw_id): |
306 | + """See `IBuildFarmJobBehavior`.""" |
307 | + # This type of job has a build that is of type BuildBase but not |
308 | + # actually a Build. |
309 | + return self._helpVerifyBuildIDComponent( |
310 | + raw_id, SourcePackageRecipeBuild, |
311 | + getUtility(ISourcePackageRecipeBuildSource).getById) |
312 | |
313 | === modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt' |
314 | --- lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-10 12:50:18 +0000 |
315 | +++ lib/lp/soyuz/doc/buildd-slavescanner.txt 2010-03-13 10:43:28 +0000 |
316 | @@ -95,15 +95,16 @@ |
317 | >>> lostbuilding_builder = MockBuilder( |
318 | ... 'Lost Building Slave', LostBuildingSlave()) |
319 | >>> buildergroup.rescueBuilderIfLost(lostbuilding_builder) |
320 | - WARNING:root:Builder 'Lost Building Slave' rescued from |
321 | - '1000-10000': 'Object not found' |
322 | + WARNING:root:Builder 'Lost Building Slave' rescued from '1000-10000': |
323 | + 'Build 1000 is not available: 'Object not found'' |
324 | |
325 | Then a lost slave in status 'WAITING': |
326 | |
327 | >>> lostwaiting_builder = MockBuilder( |
328 | ... 'Lost Waiting Slave', LostWaitingSlave()) |
329 | >>> buildergroup.rescueBuilderIfLost(lostwaiting_builder) |
330 | - WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': 'Object not found' |
331 | + WARNING:root:Builder 'Lost Waiting Slave' rescued from '1000-10000': |
332 | + 'Build 1000 is not available: 'Object not found'' |
333 | |
334 | Both got rescued, as expected. |
335 | |
336 | |
337 | === modified file 'lib/lp/testing/factory.py' |
338 | --- lib/lp/testing/factory.py 2010-03-11 20:54:36 +0000 |
339 | +++ lib/lp/testing/factory.py 2010-03-13 10:43:28 +0000 |
340 | @@ -2008,6 +2008,81 @@ |
341 | def getAnySourcePackageUrgency(self): |
342 | return SourcePackageUrgency.MEDIUM |
343 | |
344 | + def makeSourcePackageRelease(self, archive=None, sourcepackagename=None, |
345 | + distroseries=None, maintainer=None, |
346 | + creator=None, component=None, section=None, |
347 | + urgency=None, version=None, |
348 | + builddepends=None, builddependsindep=None, |
349 | + build_conflicts=None, |
350 | + build_conflicts_indep=None, |
351 | + architecturehintlist='all', |
352 | + dsc_maintainer_rfc822=None, |
353 | + dsc_standards_version='3.6.2', |
354 | + dsc_format='1.0', dsc_binaries='foo-bin', |
355 | + date_uploaded=UTC_NOW): |
356 | + """Make a `SourcePackageRelease`.""" |
357 | + if distroseries is None: |
358 | + if archive is None: |
359 | + distribution = None |
360 | + else: |
361 | + distribution = archive.distribution |
362 | + distroseries = self.makeDistroRelease(distribution=distribution) |
363 | + |
364 | + if archive is None: |
365 | + archive = self.makeArchive( |
366 | + distribution=distroseries.distribution, |
367 | + purpose=ArchivePurpose.PRIMARY) |
368 | + |
369 | + if sourcepackagename is None: |
370 | + sourcepackagename = self.makeSourcePackageName() |
371 | + |
372 | + if component is None: |
373 | + component = self.makeComponent() |
374 | + |
375 | + if urgency is None: |
376 | + urgency = self.getAnySourcePackageUrgency() |
377 | + |
378 | + if section is None: |
379 | + section = self.getUniqueString('section') |
380 | + section = getUtility(ISectionSet).ensure(section) |
381 | + |
382 | + if maintainer is None: |
383 | + maintainer = self.makePerson() |
384 | + |
385 | + maintainer_email = '%s <%s>' % ( |
386 | + maintainer.displayname, |
387 | + maintainer.preferredemail.email) |
388 | + |
389 | + if creator is None: |
390 | + creator = self.makePerson() |
391 | + |
392 | + if version is None: |
393 | + version = self.getUniqueString('version') |
394 | + |
395 | + return distroseries.createUploadedSourcePackageRelease( |
396 | + sourcepackagename=sourcepackagename, |
397 | + maintainer=maintainer, |
398 | + creator=creator, |
399 | + component=component, |
400 | + section=section, |
401 | + urgency=urgency, |
402 | + version=version, |
403 | + builddepends=builddepends, |
404 | + builddependsindep=builddependsindep, |
405 | + build_conflicts=build_conflicts, |
406 | + build_conflicts_indep=build_conflicts_indep, |
407 | + architecturehintlist=architecturehintlist, |
408 | + changelog_entry=None, |
409 | + dsc=None, |
410 | + copyright=self.getUniqueString(), |
411 | + dscsigningkey=None, |
412 | + dsc_maintainer_rfc822=maintainer_email, |
413 | + dsc_standards_version=dsc_standards_version, |
414 | + dsc_format=dsc_format, |
415 | + dsc_binaries=dsc_binaries, |
416 | + archive=archive, |
417 | + dateuploaded=date_uploaded) |
418 | + |
419 | def makeSourcePackagePublishingHistory(self, sourcepackagename=None, |
420 | distroseries=None, maintainer=None, |
421 | creator=None, component=None, |
422 | @@ -2027,54 +2102,31 @@ |
423 | dsc_format='1.0', |
424 | dsc_binaries='foo-bin', |
425 | ): |
426 | - if sourcepackagename is None: |
427 | - sourcepackagename = self.makeSourcePackageName() |
428 | - spn = sourcepackagename |
429 | - |
430 | + """Make a `SourcePackagePublishingHistory`.""" |
431 | if distroseries is None: |
432 | - distroseries = self.makeDistroRelease() |
433 | + if archive is None: |
434 | + distribution = None |
435 | + else: |
436 | + distribution = archive.distribution |
437 | + distroseries = self.makeDistroRelease(distribution=distribution) |
438 | |
439 | if archive is None: |
440 | archive = self.makeArchive( |
441 | distribution=distroseries.distribution, |
442 | purpose=ArchivePurpose.PRIMARY) |
443 | |
444 | - if component is None: |
445 | - component = self.makeComponent() |
446 | - |
447 | if pocket is None: |
448 | pocket = self.getAnyPocket() |
449 | |
450 | if status is None: |
451 | status = PackagePublishingStatus.PENDING |
452 | |
453 | - if urgency is None: |
454 | - urgency = self.getAnySourcePackageUrgency() |
455 | - |
456 | - if section is None: |
457 | - section = self.getUniqueString('section') |
458 | - section = getUtility(ISectionSet).ensure(section) |
459 | - |
460 | - if maintainer is None: |
461 | - maintainer = self.makePerson() |
462 | - |
463 | - maintainer_email = '%s <%s>' % ( |
464 | - maintainer.displayname, |
465 | - maintainer.preferredemail.email) |
466 | - |
467 | - if creator is None: |
468 | - creator = self.makePerson() |
469 | - |
470 | - if version is None: |
471 | - version = self.getUniqueString('version') |
472 | - |
473 | - gpg_key = self.makeGPGKey(creator) |
474 | - |
475 | - spr = distroseries.createUploadedSourcePackageRelease( |
476 | - sourcepackagename=spn, |
477 | + spr = self.makeSourcePackageRelease( |
478 | + archive=archive, |
479 | + sourcepackagename=sourcepackagename, |
480 | + distroseries=distroseries, |
481 | maintainer=maintainer, |
482 | - creator=creator, |
483 | - component=component, |
484 | + creator=creator, component=component, |
485 | section=section, |
486 | urgency=urgency, |
487 | version=version, |
488 | @@ -2083,15 +2135,10 @@ |
489 | build_conflicts=build_conflicts, |
490 | build_conflicts_indep=build_conflicts_indep, |
491 | architecturehintlist=architecturehintlist, |
492 | - changelog_entry=None, |
493 | - dsc=None, |
494 | - copyright=self.getUniqueString(), |
495 | - dscsigningkey=gpg_key, |
496 | - dsc_maintainer_rfc822=maintainer_email, |
497 | dsc_standards_version=dsc_standards_version, |
498 | dsc_format=dsc_format, |
499 | dsc_binaries=dsc_binaries, |
500 | - archive=archive, dateuploaded=date_uploaded) |
501 | + date_uploaded=date_uploaded) |
502 | |
503 | sspph = SourcePackagePublishingHistory( |
504 | distroseries=distroseries, |
505 | |
506 | === modified file 'lib/lp/translations/doc/translationtemplatesbuildbehavior.txt' |
507 | --- lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-05 13:52:32 +0000 |
508 | +++ lib/lp/translations/doc/translationtemplatesbuildbehavior.txt 2010-03-13 10:43:28 +0000 |
509 | @@ -2,13 +2,21 @@ |
510 | |
511 | == Setup == |
512 | |
513 | -Set up build environment. |
514 | +Set up build environment. Clear out the build queue. |
515 | |
516 | >>> import transaction |
517 | >>> import logging |
518 | >>> logger = logging.getLogger() |
519 | >>> logger.setLevel(logging.CRITICAL) |
520 | |
521 | + >>> from canonical.database.sqlbase import quote |
522 | + >>> from canonical.launchpad.interfaces.lpstorm import IMasterStore |
523 | + >>> from lp.services.job.interfaces.job import JobStatus |
524 | + >>> from lp.services.job.model.job import Job |
525 | + >>> store = IMasterStore(Job) |
526 | + >>> query = store.execute("UPDATE Job SET status = %s" % quote( |
527 | + ... JobStatus.FAILED)) |
528 | + |
529 | >>> from lp.buildmaster.master import BuilddMaster |
530 | >>> from canonical.buildd.tests import BuilddSlaveTestSetup |
531 | >>> bm = BuilddMaster(logger, transaction) |
532 | @@ -39,7 +47,21 @@ |
533 | |
534 | Make a builder to process our build request. |
535 | |
536 | - >>> builder = factory.makeBuilder(virtualized=False, processor=processor) |
537 | + >>> builder = factory.makeBuilder( |
538 | + ... virtualized=True, processor=processor, vm_host='hostname') |
539 | + |
540 | +The builder doesn't talk to a real slave. We don't have those in our |
541 | +test suite. But we give it a fake one. |
542 | + |
543 | + >>> from lp.testing.fakemethod import FakeMethod |
544 | + >>> class FakeSlave: |
545 | + ... build = FakeMethod() |
546 | + ... cacheFile = FakeMethod() |
547 | + ... resume = FakeMethod(result=('Output here', 'Errors here', 0)) |
548 | + |
549 | + >>> from zope.security.proxy import removeSecurityProxy |
550 | + >>> slave = FakeSlave() |
551 | + >>> removeSecurityProxy(builder).slave = slave |
552 | |
553 | |
554 | == Get a job! == |
555 | @@ -60,20 +82,27 @@ |
556 | >>> print buildqueue.date_started |
557 | None |
558 | |
559 | -Dispatch the job to the build slave. The proper method to call here |
560 | -would have been Builder.findAndStartJob, but it has not been generalised |
561 | -to handle new job types yet. |
562 | - |
563 | -# XXX JeroenVermeulen bug=506617: call findAndStartJob instead. |
564 | - |
565 | - >>> from zope.security.proxy import removeSecurityProxy |
566 | - >>> removeSecurityProxy(builder)._dispatchBuildCandidate(buildqueue) |
567 | - |
568 | -The build is now marked as started. |
569 | +Our job is now first in line to be executed. |
570 | + |
571 | + >>> removeSecurityProxy(builder)._findBuildCandidate() == buildqueue |
572 | + True |
573 | + |
574 | +Dispatch the first job in line (ours!) to the build slave. |
575 | + |
576 | + >>> activated_job = builder.findAndStartJob() |
577 | + >>> activated_job == buildqueue |
578 | + True |
579 | + |
580 | +Our build is now marked as started. |
581 | |
582 | >>> buildqueue.date_started is None |
583 | False |
584 | |
585 | +The slave's build method has been called. |
586 | + |
587 | + >>> slave.build.call_count |
588 | + 1 |
589 | + |
590 | |
591 | == Teardown == |
592 | |
593 | |
594 | === modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py' |
595 | --- lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-02-11 19:11:11 +0000 |
596 | +++ lib/lp/translations/model/translationtemplatesbuildbehavior.py 2010-03-13 10:43:28 +0000 |
597 | @@ -17,6 +17,7 @@ |
598 | |
599 | from canonical.launchpad.interfaces import ILaunchpadCelebrities |
600 | |
601 | +from lp.buildmaster.interfaces.builder import CorruptBuildID |
602 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
603 | IBuildFarmJobBehavior) |
604 | from lp.buildmaster.model.buildfarmjobbehavior import ( |
605 | @@ -40,12 +41,27 @@ |
606 | self._builder.slave.cacheFile(logger, chroot) |
607 | buildid = self.buildfarmjob.getName() |
608 | |
609 | - args = { 'branch_url': self.buildfarmjob.branch.url } |
610 | + args = self.buildfarmjob.metadata |
611 | filemap = {} |
612 | |
613 | self._builder.slave.build( |
614 | buildid, self.build_type, chroot_sha1, filemap, args) |
615 | |
616 | + def verifySlaveBuildID(self, slave_build_id): |
617 | + """See `IBuildFarmJobBehavior`.""" |
618 | + try: |
619 | + branch_name, queue_item_id = slave_build_id.rsplit('-', 1) |
620 | + except ValueError: |
621 | + raise CorruptBuildID( |
622 | + "Malformed translation templates build id: '%s'" % ( |
623 | + slave_build_id)) |
624 | + |
625 | + buildqueue = self.getVerifiedBuildQueue(queue_item_id) |
626 | + if buildqueue.job != self.buildfarmjob.job: |
627 | + raise CorruptBuildID( |
628 | + "ID mismatch for translation templates build '%s'" % ( |
629 | + slave_build_id)) |
630 | + |
631 | def _getChroot(self): |
632 | ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
633 | return ubuntu.currentseries.nominatedarchindep.getChroot() |
634 | |
635 | === modified file 'lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py' |
636 | --- lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-12 08:31:43 +0000 |
637 | +++ lib/lp/translations/tests/test_translationtemplatesbuildbehavior.py 2010-03-13 10:43:28 +0000 |
638 | @@ -19,6 +19,7 @@ |
639 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
640 | from lp.buildmaster.interfaces.buildfarmjobbehavior import ( |
641 | IBuildFarmJobBehavior) |
642 | +from lp.buildmaster.interfaces.builder import CorruptBuildID |
643 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
644 | from lp.testing import TestCaseWithFactory |
645 | from lp.testing.fakemethod import FakeMethod |
646 | @@ -123,6 +124,10 @@ |
647 | self.assertEqual( |
648 | 'translation-templates', slave_status['test_build_type']) |
649 | self.assertIn('branch_url', slave_status['test_build_args']) |
650 | + # The slave receives the public http URL for the branch. |
651 | + self.assertEqual( |
652 | + behavior.buildfarmjob.branch.composePublicURL(), |
653 | + slave_status['test_build_args']['branch_url']) |
654 | |
655 | def test_getChroot(self): |
656 | # _getChroot produces the current chroot for the current Ubuntu |
657 | @@ -170,6 +175,38 @@ |
658 | self.assertEqual(1, builder.cleanSlave.call_count) |
659 | self.assertEqual(0, behavior._uploadTarball.call_count) |
660 | |
661 | + def test_verifySlaveBuildID_success(self): |
662 | + # TranslationTemplatesBuildJob.getName generates slave build ids |
663 | + # that TranslationTemplatesBuildBehavior.verifySlaveBuildID |
664 | + # accepts. |
665 | + behavior = self._makeBehavior() |
666 | + buildfarmjob = behavior.buildfarmjob |
667 | + job = buildfarmjob.job |
668 | + |
669 | + # The test is that this not raise CorruptBuildID (or anything |
670 | + # else, for that matter). |
671 | + behavior.verifySlaveBuildID(behavior.buildfarmjob.getName()) |
672 | + |
673 | + def test_verifySlaveBuildID_handles_dashes(self): |
674 | + # TranslationTemplatesBuildBehavior.verifySlaveBuildID can deal |
675 | + # with dashes in branch names. |
676 | + behavior = self._makeBehavior() |
677 | + buildfarmjob = behavior.buildfarmjob |
678 | + job = buildfarmjob.job |
679 | + buildfarmjob.branch.name = 'x-y-z--' |
680 | + |
681 | + # The test is that this not raise CorruptBuildID (or anything |
682 | + # else, for that matter). |
683 | + behavior.verifySlaveBuildID(behavior.buildfarmjob.getName()) |
684 | + |
685 | + def test_verifySlaveBuildID_malformed(self): |
686 | + behavior = self._makeBehavior() |
687 | + self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, 'huh?') |
688 | + |
689 | + def test_verifySlaveBuildID_notfound(self): |
690 | + behavior = self._makeBehavior() |
691 | + self.assertRaises(CorruptBuildID, behavior.verifySlaveBuildID, '1-1') |
692 | + |
693 | |
694 | def test_suite(): |
695 | return TestLoader().loadTestsFromName(__name__) |
Hi Jeroen,
I found a few issues that I'd like you to address before I do a proper
review of this branch.
review needsfixing
> === modified file 'lib/lp/ buildmaster/ interfaces/ buildfarmjobbeh avior.py' buildmaster/ interfaces/ buildfarmjobbeh avior.py 2010-01-21 05:03:16 +0000 buildmaster/ interfaces/ buildfarmjobbeh avior.py 2010-03-12 13:56:29 +0000 msg=E0211, E0213 slaveStatus( ). d(raw_id) :
> --- lib/lp/
> +++ lib/lp/
> @@ -1,4 +1,4 @@
> -# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> # pylint: disable-
> @@ -57,6 +57,28 @@
> of IBuilder.
> """
>
> + def getVerifiedBuil
> + """Verify the `Build` id component of a slave build id.
I find it a bit odd that the method's name implies it's used to get
something but its docstring says it just verifies. How about
"""Return the `Build` object with the given id.
...
"""
I'd also like the docstring to state what sort of verification is done.
Oh, and does this need to be part of the public interface? ISTM that
it's only used internally, so it could be left out of the interface, no?
> + dQueue( raw_id) :
> + By default, a `BuildFarmJob` has an identifying name of the form
> + "b-q", where b is the id of its `Build` and q is the id of its
> + `BuildQueue` record.
> +
> + Use `getVerifiedBuild` to verify the "b" part, and retrieve the
> + associated `Build`.
> + """
> +
> + def getVerifiedBuil
> + """Verify the `BuildQueue` id component of a slave build id.
Same here.
> + ldQueue` to verify the "q" part, and retrieve dID(slave_ build_id) : buildmaster/ model/buildfarm jobbehavior. py' buildmaster/ model/buildfarm jobbehavior. py 2010-03-05 13:52:32 +0000 buildmaster/ model/buildfarm jobbehavior. py 2010-03-12 13:56:29 +0000 msg=E0211, E0213 roxy, isinstance as zisinstance librarian. interfaces import ILibrarianClient launchpad. webapp. interfaces import NotFoundError interfaces. bui...
> + By default, a `BuildFarmJob` has an identifying name of the form
> + "b-q", where b is the id of its `Build` and q is the id of its
> + `BuildQueue` record.
> +
> + Use `getVerifiedBui
> + the associated `BuildQueue` object.
> + """
> +
> def verifySlaveBuil
> """Verify that a slave's build ID shows no signs of corruption.
>
>
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -1,4 +1,4 @@
> -# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> # pylint: disable-
> @@ -17,17 +17,22 @@
> import xmlrpclib
>
> from sqlobject import SQLObjectNotFound
> +
> from zope.component import getUtility
> from zope.interface import implements
> -from zope.security.proxy import removeSecurityProxy
> +from zope.security.proxy import removeSecurityP
>
> from canonical import encoding
> from canonical.
> +
> +from canonical.
> from lp.buildmaster.