Merge lp:~jtv/launchpad/bug-536797 into lp:launchpad
- bug-536797
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~jtv/launchpad/bug-536797 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
814 lines (+356/-89) 23 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+2/-1) lib/canonical/launchpad/security.py (+52/-2) lib/canonical/launchpad/webapp/configure.zcml (+7/-0) lib/canonical/launchpad/webapp/tales.py (+27/-0) lib/lp/buildmaster/interfaces/buildfarmbranchjob.py (+21/-0) lib/lp/buildmaster/interfaces/buildfarmjob.py (+0/-1) lib/lp/buildmaster/interfaces/buildqueue.py (+4/-0) lib/lp/buildmaster/model/buildqueue.py (+12/-2) lib/lp/buildmaster/tests/test_buildqueue.py (+36/-7) lib/lp/code/interfaces/sourcepackagerecipebuild.py (+2/-7) lib/lp/soyuz/browser/build.py (+0/-1) lib/lp/soyuz/browser/builder.py (+2/-8) lib/lp/soyuz/browser/configure.zcml (+31/-1) lib/lp/soyuz/doc/build-estimated-dispatch-time.txt (+3/-4) lib/lp/soyuz/interfaces/buildfarmbuildjob.py (+21/-0) lib/lp/soyuz/interfaces/buildpackagejob.py (+4/-4) lib/lp/soyuz/templates/builder-index.pt (+5/-47) lib/lp/soyuz/templates/buildfarmbranchjob-current.pt (+11/-0) lib/lp/soyuz/templates/buildfarmbuildjob-current.pt (+8/-0) lib/lp/soyuz/templates/buildfarmjob-current.pt (+10/-0) lib/lp/soyuz/templates/buildqueue-current.pt (+24/-0) lib/lp/translations/model/translationtemplatesbuildjob.py (+5/-4) lib/lp/translations/stories/buildfarm/xx-build-summary.txt (+69/-0) |
||||
To merge this branch: | bzr merge lp:~jtv/launchpad/bug-536797 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Canonical Launchpad Engineering | ui | Pending | |
Review via email: mp+21439@code.launchpad.net |
Commit message
Minimal support for non-Soyuz build-farm jobs in builder UI.
Description of the change
= Bug 536797 =
The Builder UI is not very good at dealing with non-Soyuz build-farm jobs. Most build-farm job types have an IBuildBase associated with them, but not all. (It's not required by the IBuildFarmJob interface either).
This branch, after discussion with al-maisan, bigjools, noodles, wgrant, and probably others introduces a bit more UI support for coping with different types of build-farm job. It does this by introducing a few new interfaces: IBuildFarmBuildJob represents an IBuildFarmJob that also has an IBuild reference, and IBuildFarmBranchJob represents one that has an IBranch reference. These inherit from IBuildFarmJob so that zope can pick the most specific applicable interface to render in the UI.
You'll see a bunch of new templates: buildqueue-
I also added a link formatter for IBuildBase, to help cut down on complicated logic in the TAL.
In the same vein you'll see the access check for showing a job's log tail moved into security.py. Doing this in the TAL became too complicated with the different build types. It's quite possible that the current check is too strict; it may make sense to show a build's log if either its branch or its build is accessible. I think experience will have to show this. Soyuz is not the kind of thing you perfect all in one branch. In any case it's hard to exercise just now: our jobs that generate translation templates don't support private branches.
A BuildQueue's "current build duration" moves from the view into the model, which seems more appropriate and lets it reuse a "fake clock" fixture that's already there. I did replace some use of datetime.utcnow() with datetime.
You'll note that some of the files I introduced deserve to be in better places. I filed a separate bug about this, because I've just about hit the limit of what I can do in one branch, and a step like that would need broader discussion.
To test (runs a lot of tests, but many exercise one change or another):
{{{
./bin/test -vv -t buildmaster -t soyuz -t 'translations.
}}}
To Q/A, first off, browser around the /builders page. Then follow the instructions on https:/
I found no lint. I'm firing off an EC2 run to make sure I didn't miss any tests.
Jeroen
Jeroen T. Vermeulen (jtv) wrote : | # |
Hi Brad,
This is a really good review. Thanks for that! It does show that I was past my saturation point for this branch, doesn't it?
> Running your test command (was that just 'make check' in disguise?) I
> got a failure on test_dispatchBu
Hmm... my EC2 test came through clean though, as do my manual runs. You don't happen to have a log of this failure somewhere, do you? I wouldn't normally ask, but IIRC you were the one who wrote the retest tool, so you may have a more reliable habit of keeping them.
> Jeroen I tried on two different occasions to follow your instructions
> in order to exercise the new UI. Both times were very time consuming
> and ultimately failed. I give up. I approve the code but not the UI
> -- you needed to get a separate UI review anyway but as a code
> reviewer I like to have a once-over.
Alright. Sorry to put you to the trouble; this is indeed very hard work and it's been slowing me down no end. If you have any notes at all about what made it hard, please let me know so I can improve the documentation. I believe it's important to us to make this accessible to encourage development.
> > === modified file 'lib/canonical/
> > --- lib/canonical/
> > +++ lib/canonical/
> > @@ -1505,6 +1508,41 @@
> > return auth_spr.
> >
> >
> > +class ViewBuildFarmJo
> > + """Permission to view an `IBuildFarmJob`.
> > +
> > + This permission is based entirely on permission to view the
> > + associated `IBuild` and/or `IBranch`.
> > + """
> > + permission = 'launchpad.View'
> > + usedfor = IBuildFarmJob
> > +
> > + def _getBuildPermis
> > + return ViewBuildRecord
> > +
> > + def _checkBranchVis
> > + """Is the user free to view any branches associated with this
> job?"""
> > + if not IBuildFarmBranc
> > + return True
> > + else:
> > + return self.obj.
>
> I don't like the inverted test (our coding standards recommend against
> having a 'not' test if both paths are present) and the fact a return
> value of True has dual meanings.
You're right, of course. This is a missing final cleanup after various refactorings. I put the interface checks in separate "get {IBranch,
> > + def checkAuthentica
> > + if not self._checkBran
> > + return False
> > +
> > + if IBuildFarmBuild
> > + if not self._getBuildP
> > + return False
> > +
> > + return True
>
> Could you write a single method that provides checkAuthenticated and
> checkUnauthenti
> would make the real methods more readable.
TBH I still don't see why these security classes don't work like that in the first place. What cou...
Jeroen T. Vermeulen (jtv) wrote : | # |
Went over this with wgrant, and (IIRC) noodles & bigjools. The conclusion was that the status icon for regular builds is now in the wrong place; even though it's the build status, it's shown in the UI as the builder status. And since I was traveling, wgrant has already landed a fix.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' | |||
2 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-03-06 04:57:40 +0000 | |||
3 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-03-17 06:01:34 +0000 | |||
4 | @@ -322,7 +322,8 @@ | |||
5 | 322 | IStructuralSubscriptionTarget) | 322 | IStructuralSubscriptionTarget) |
6 | 323 | 323 | ||
7 | 324 | patch_reference_property( | 324 | patch_reference_property( |
9 | 325 | ISourcePackageRelease, 'source_package_recipe_build', ISourcePackageRecipeBuild) | 325 | ISourcePackageRelease, 'source_package_recipe_build', |
10 | 326 | ISourcePackageRecipeBuild) | ||
11 | 326 | 327 | ||
12 | 327 | # IHasBugs | 328 | # IHasBugs |
13 | 328 | patch_plain_parameter_type( | 329 | patch_plain_parameter_type( |
14 | 329 | 330 | ||
15 | === modified file 'lib/canonical/launchpad/security.py' | |||
16 | --- lib/canonical/launchpad/security.py 2010-03-16 10:47:00 +0000 | |||
17 | +++ lib/canonical/launchpad/security.py 2010-03-17 06:01:34 +0000 | |||
18 | @@ -1,4 +1,4 @@ | |||
20 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
21 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
22 | 3 | 3 | ||
23 | 4 | """Security policies for using content objects.""" | 4 | """Security policies for using content objects.""" |
24 | @@ -32,8 +32,9 @@ | |||
25 | 32 | from lp.bugs.interfaces.bugnomination import IBugNomination | 32 | from lp.bugs.interfaces.bugnomination import IBugNomination |
26 | 33 | from lp.bugs.interfaces.bugsubscription import IBugSubscription | 33 | from lp.bugs.interfaces.bugsubscription import IBugSubscription |
27 | 34 | from lp.bugs.interfaces.bugtracker import IBugTracker | 34 | from lp.bugs.interfaces.bugtracker import IBugTracker |
28 | 35 | from lp.soyuz.interfaces.build import IBuild | ||
29 | 36 | from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet | 35 | from lp.buildmaster.interfaces.builder import IBuilder, IBuilderSet |
30 | 36 | from lp.buildmaster.interfaces.buildfarmbranchjob import IBuildFarmBranchJob | ||
31 | 37 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob | ||
32 | 37 | from lp.code.interfaces.codeimport import ICodeImport | 38 | from lp.code.interfaces.codeimport import ICodeImport |
33 | 38 | from lp.code.interfaces.codeimportjob import ( | 39 | from lp.code.interfaces.codeimportjob import ( |
34 | 39 | ICodeImportJobSet, ICodeImportJobWorkflow) | 40 | ICodeImportJobSet, ICodeImportJobWorkflow) |
35 | @@ -70,6 +71,8 @@ | |||
36 | 70 | from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet | 71 | from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet |
37 | 71 | from lp.translations.interfaces.pofile import IPOFile | 72 | from lp.translations.interfaces.pofile import IPOFile |
38 | 72 | from lp.translations.interfaces.potemplate import IPOTemplate | 73 | from lp.translations.interfaces.potemplate import IPOTemplate |
39 | 74 | from lp.soyuz.interfaces.build import IBuild | ||
40 | 75 | from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob | ||
41 | 73 | from lp.soyuz.interfaces.publishing import ( | 76 | from lp.soyuz.interfaces.publishing import ( |
42 | 74 | IBinaryPackagePublishingHistory, IPublishingEdit, | 77 | IBinaryPackagePublishingHistory, IPublishingEdit, |
43 | 75 | ISourcePackagePublishingHistory) | 78 | ISourcePackagePublishingHistory) |
44 | @@ -1508,6 +1511,53 @@ | |||
45 | 1508 | return auth_spr.checkUnauthenticated() | 1511 | return auth_spr.checkUnauthenticated() |
46 | 1509 | 1512 | ||
47 | 1510 | 1513 | ||
48 | 1514 | class ViewBuildFarmJob(AuthorizationBase): | ||
49 | 1515 | """Permission to view an `IBuildFarmJob`. | ||
50 | 1516 | |||
51 | 1517 | This permission is based entirely on permission to view the | ||
52 | 1518 | associated `IBuild` and/or `IBranch`. | ||
53 | 1519 | """ | ||
54 | 1520 | permission = 'launchpad.View' | ||
55 | 1521 | usedfor = IBuildFarmJob | ||
56 | 1522 | |||
57 | 1523 | def _getBranch(self): | ||
58 | 1524 | """Get `IBranch` associated with this job, if any.""" | ||
59 | 1525 | if IBuildFarmBranchJob.providedBy(self.obj): | ||
60 | 1526 | return self.obj.branch | ||
61 | 1527 | else: | ||
62 | 1528 | return None | ||
63 | 1529 | |||
64 | 1530 | def _getBuild(self): | ||
65 | 1531 | """Get `IBuildBase` associated with this job, if any.""" | ||
66 | 1532 | if IBuildFarmBuildJob.providedBy(self.obj): | ||
67 | 1533 | return self.obj.build | ||
68 | 1534 | else: | ||
69 | 1535 | return None | ||
70 | 1536 | |||
71 | 1537 | def _checkBuildPermission(self, user=None): | ||
72 | 1538 | """Check access to `IBuildBase` for this job.""" | ||
73 | 1539 | permission = ViewBuildRecord(self.obj.build) | ||
74 | 1540 | if user is None: | ||
75 | 1541 | return permission.checkUnauthenticated() | ||
76 | 1542 | else: | ||
77 | 1543 | return permission.checkAuthenticated(user) | ||
78 | 1544 | |||
79 | 1545 | def _checkAccess(self, user=None): | ||
80 | 1546 | """Unified access check for anonymous and authenticated users.""" | ||
81 | 1547 | branch = self._getBranch() | ||
82 | 1548 | if branch is not None and not branch.visibleByUser(user): | ||
83 | 1549 | return False | ||
84 | 1550 | |||
85 | 1551 | build = self._getBuild() | ||
86 | 1552 | if build is not None and not self._checkBuildPermission(user): | ||
87 | 1553 | return False | ||
88 | 1554 | |||
89 | 1555 | return True | ||
90 | 1556 | |||
91 | 1557 | checkAuthenticated = _checkAccess | ||
92 | 1558 | checkUnauthenticated = _checkAccess | ||
93 | 1559 | |||
94 | 1560 | |||
95 | 1511 | class AdminQuestion(AdminByAdminsTeam): | 1561 | class AdminQuestion(AdminByAdminsTeam): |
96 | 1512 | permission = 'launchpad.Admin' | 1562 | permission = 'launchpad.Admin' |
97 | 1513 | usedfor = IQuestion | 1563 | usedfor = IQuestion |
98 | 1514 | 1564 | ||
99 | === modified file 'lib/canonical/launchpad/webapp/configure.zcml' | |||
100 | --- lib/canonical/launchpad/webapp/configure.zcml 2010-02-17 11:13:06 +0000 | |||
101 | +++ lib/canonical/launchpad/webapp/configure.zcml 2010-03-17 06:01:34 +0000 | |||
102 | @@ -426,6 +426,13 @@ | |||
103 | 426 | /> | 426 | /> |
104 | 427 | 427 | ||
105 | 428 | <adapter | 428 | <adapter |
106 | 429 | for="lp.buildmaster.interfaces.buildbase.IBuildBase" | ||
107 | 430 | provides="zope.traversing.interfaces.IPathAdapter" | ||
108 | 431 | factory="canonical.launchpad.webapp.tales.BuildBaseFormatterAPI" | ||
109 | 432 | name="fmt" | ||
110 | 433 | /> | ||
111 | 434 | |||
112 | 435 | <adapter | ||
113 | 429 | for="lp.code.interfaces.codeimportmachine.ICodeImportMachine" | 436 | for="lp.code.interfaces.codeimportmachine.ICodeImportMachine" |
114 | 430 | provides="zope.traversing.interfaces.IPathAdapter" | 437 | provides="zope.traversing.interfaces.IPathAdapter" |
115 | 431 | factory="canonical.launchpad.webapp.tales.CodeImportMachineFormatterAPI" | 438 | factory="canonical.launchpad.webapp.tales.CodeImportMachineFormatterAPI" |
116 | 432 | 439 | ||
117 | === modified file 'lib/canonical/launchpad/webapp/tales.py' | |||
118 | --- lib/canonical/launchpad/webapp/tales.py 2010-03-10 12:50:18 +0000 | |||
119 | +++ lib/canonical/launchpad/webapp/tales.py 2010-03-17 06:01:34 +0000 | |||
120 | @@ -1514,6 +1514,33 @@ | |||
121 | 1514 | return url | 1514 | return url |
122 | 1515 | 1515 | ||
123 | 1516 | 1516 | ||
124 | 1517 | class BuildBaseFormatterAPI(ObjectFormatterAPI): | ||
125 | 1518 | """Adapter providing fmt support for `IBuildBase` objects.""" | ||
126 | 1519 | def _composeArchiveReference(self, archive): | ||
127 | 1520 | if archive.is_ppa: | ||
128 | 1521 | return " [%s/%s]" % ( | ||
129 | 1522 | cgi.escape(archive.owner.name), cgi.escape(archive.name)) | ||
130 | 1523 | else: | ||
131 | 1524 | return "" | ||
132 | 1525 | |||
133 | 1526 | def icon(self, view_name): | ||
134 | 1527 | if not check_permission('launchpad.View', self._context): | ||
135 | 1528 | return '<img src="/@@/processing" alt="[build]" />' | ||
136 | 1529 | |||
137 | 1530 | return BuildImageDisplayAPI(self._context).icon() | ||
138 | 1531 | |||
139 | 1532 | def link(self, view_name, rootsite=None): | ||
140 | 1533 | icon = self.icon(view_name) | ||
141 | 1534 | build = self._context | ||
142 | 1535 | if not check_permission('launchpad.View', build): | ||
143 | 1536 | return '%s private source' % icon | ||
144 | 1537 | |||
145 | 1538 | url = self.url(view_name=view_name, rootsite=rootsite) | ||
146 | 1539 | title = cgi.escape(build.title) | ||
147 | 1540 | archive = self._composeArchiveReference(build.archive) | ||
148 | 1541 | return '<a href="%s">%s%s</a>%s' % (url, icon, title, archive) | ||
149 | 1542 | |||
150 | 1543 | |||
151 | 1517 | class CodeImportMachineFormatterAPI(CustomizableFormatter): | 1544 | class CodeImportMachineFormatterAPI(CustomizableFormatter): |
152 | 1518 | """Adapter providing fmt support for CodeImport objects""" | 1545 | """Adapter providing fmt support for CodeImport objects""" |
153 | 1519 | 1546 | ||
154 | 1520 | 1547 | ||
155 | === added file 'lib/lp/buildmaster/interfaces/buildfarmbranchjob.py' | |||
156 | --- lib/lp/buildmaster/interfaces/buildfarmbranchjob.py 1970-01-01 00:00:00 +0000 | |||
157 | +++ lib/lp/buildmaster/interfaces/buildfarmbranchjob.py 2010-03-17 06:01:34 +0000 | |||
158 | @@ -0,0 +1,21 @@ | |||
159 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
160 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
161 | 3 | |||
162 | 4 | """Interface for `IBuildFarmJob`s that are also `IBranchJob`s.""" | ||
163 | 5 | |||
164 | 6 | __metaclass__ = type | ||
165 | 7 | __all__ = [ | ||
166 | 8 | 'IBuildFarmBranchJob' | ||
167 | 9 | ] | ||
168 | 10 | |||
169 | 11 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob | ||
170 | 12 | from lp.code.interfaces.branchjob import IBranchJob | ||
171 | 13 | |||
172 | 14 | |||
173 | 15 | class IBuildFarmBranchJob(IBuildFarmJob, IBranchJob): | ||
174 | 16 | """An `IBuildFarmJob` that's also an `IBranchJob`. | ||
175 | 17 | |||
176 | 18 | Use this interface for `IBuildFarmJob` implementations that do not | ||
177 | 19 | have a "build" attribute but do implement `IBranchJob`, so that the | ||
178 | 20 | UI can render appropriate status information. | ||
179 | 21 | """ | ||
180 | 0 | 22 | ||
181 | === modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py' | |||
182 | --- lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-01-30 05:27:48 +0000 | |||
183 | +++ lib/lp/buildmaster/interfaces/buildfarmjob.py 2010-03-17 06:01:34 +0000 | |||
184 | @@ -91,7 +91,6 @@ | |||
185 | 91 | "return None.")) | 91 | "return None.")) |
186 | 92 | 92 | ||
187 | 93 | 93 | ||
188 | 94 | |||
189 | 95 | class ISpecificBuildFarmJobClass(Interface): | 94 | class ISpecificBuildFarmJobClass(Interface): |
190 | 96 | """Class interface provided by `IBuildFarmJob` classes. | 95 | """Class interface provided by `IBuildFarmJob` classes. |
191 | 97 | 96 | ||
192 | 98 | 97 | ||
193 | === modified file 'lib/lp/buildmaster/interfaces/buildqueue.py' | |||
194 | --- lib/lp/buildmaster/interfaces/buildqueue.py 2010-03-05 13:52:32 +0000 | |||
195 | +++ lib/lp/buildmaster/interfaces/buildqueue.py 2010-03-17 06:01:34 +0000 | |||
196 | @@ -72,6 +72,10 @@ | |||
197 | 72 | title=_("Estimated Job Duration"), required=True, | 72 | title=_("Estimated Job Duration"), required=True, |
198 | 73 | description=_("Estimated job duration interval.")) | 73 | description=_("Estimated job duration interval.")) |
199 | 74 | 74 | ||
200 | 75 | current_build_duration = Timedelta( | ||
201 | 76 | title=_("Current build duration"), required=False, | ||
202 | 77 | description=_("Time spent building so far.")) | ||
203 | 78 | |||
204 | 75 | def manualScore(value): | 79 | def manualScore(value): |
205 | 76 | """Manually set a score value to a queue item and lock it.""" | 80 | """Manually set a score value to a queue item and lock it.""" |
206 | 77 | 81 | ||
207 | 78 | 82 | ||
208 | === modified file 'lib/lp/buildmaster/model/buildqueue.py' | |||
209 | --- lib/lp/buildmaster/model/buildqueue.py 2010-03-08 12:53:59 +0000 | |||
210 | +++ lib/lp/buildmaster/model/buildqueue.py 2010-03-17 06:01:34 +0000 | |||
211 | @@ -14,6 +14,7 @@ | |||
212 | 14 | from collections import defaultdict | 14 | from collections import defaultdict |
213 | 15 | from datetime import datetime, timedelta | 15 | from datetime import datetime, timedelta |
214 | 16 | import logging | 16 | import logging |
215 | 17 | import pytz | ||
216 | 17 | 18 | ||
217 | 18 | from sqlobject import ( | 19 | from sqlobject import ( |
218 | 19 | StringCol, ForeignKey, BoolCol, IntCol, IntervalCol, SQLObjectNotFound) | 20 | StringCol, ForeignKey, BoolCol, IntCol, IntervalCol, SQLObjectNotFound) |
219 | @@ -118,6 +119,15 @@ | |||
220 | 118 | """See `IBuildQueue`.""" | 119 | """See `IBuildQueue`.""" |
221 | 119 | return self.job.date_started | 120 | return self.job.date_started |
222 | 120 | 121 | ||
223 | 122 | @property | ||
224 | 123 | def current_build_duration(self): | ||
225 | 124 | """See `IBuildQueue`.""" | ||
226 | 125 | date_started = self.date_started | ||
227 | 126 | if date_started is None: | ||
228 | 127 | return None | ||
229 | 128 | else: | ||
230 | 129 | return self._now() - date_started | ||
231 | 130 | |||
232 | 121 | def destroySelf(self): | 131 | def destroySelf(self): |
233 | 122 | """Remove this record and associated job/specific_job.""" | 132 | """Remove this record and associated job/specific_job.""" |
234 | 123 | job = self.job | 133 | job = self.job |
235 | @@ -464,8 +474,8 @@ | |||
236 | 464 | 474 | ||
237 | 465 | @staticmethod | 475 | @staticmethod |
238 | 466 | def _now(): | 476 | def _now(): |
241 | 467 | """Provide utcnow() while allowing test code to monkey-patch this.""" | 477 | """Return current time (UTC). Overridable for test purposes.""" |
242 | 468 | return datetime.utcnow() | 478 | return datetime.now(pytz.UTC) |
243 | 469 | 479 | ||
244 | 470 | 480 | ||
245 | 471 | class BuildQueueSet(object): | 481 | class BuildQueueSet(object): |
246 | 472 | 482 | ||
247 | === modified file 'lib/lp/buildmaster/tests/test_buildqueue.py' | |||
248 | --- lib/lp/buildmaster/tests/test_buildqueue.py 2010-03-10 21:30:57 +0000 | |||
249 | +++ lib/lp/buildmaster/tests/test_buildqueue.py 2010-03-17 06:01:34 +0000 | |||
250 | @@ -1,4 +1,4 @@ | |||
252 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
253 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
254 | 3 | # pylint: disable-msg=C0324 | 3 | # pylint: disable-msg=C0324 |
255 | 4 | 4 | ||
256 | @@ -6,13 +6,14 @@ | |||
257 | 6 | 6 | ||
258 | 7 | from datetime import datetime, timedelta | 7 | from datetime import datetime, timedelta |
259 | 8 | from pytz import utc | 8 | from pytz import utc |
260 | 9 | from unittest import TestLoader | ||
261 | 9 | 10 | ||
262 | 10 | from zope.component import getUtility | 11 | from zope.component import getUtility |
263 | 11 | from zope.interface.verify import verifyObject | 12 | from zope.interface.verify import verifyObject |
264 | 12 | 13 | ||
265 | 13 | from canonical.launchpad.webapp.interfaces import ( | 14 | from canonical.launchpad.webapp.interfaces import ( |
266 | 14 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) | 15 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
268 | 15 | from canonical.testing import LaunchpadZopelessLayer | 16 | from canonical.testing import LaunchpadZopelessLayer, ZopelessDatabaseLayer |
269 | 16 | 17 | ||
270 | 17 | from lp.buildmaster.interfaces.buildbase import BuildStatus | 18 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
271 | 18 | from lp.buildmaster.interfaces.builder import IBuilderSet | 19 | from lp.buildmaster.interfaces.builder import IBuilderSet |
272 | @@ -28,6 +29,7 @@ | |||
273 | 28 | from lp.soyuz.model.build import Build | 29 | from lp.soyuz.model.build import Build |
274 | 29 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher | 30 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
275 | 30 | from lp.testing import TestCaseWithFactory | 31 | from lp.testing import TestCaseWithFactory |
276 | 32 | from lp.testing.fakemethod import FakeMethod | ||
277 | 31 | 33 | ||
278 | 32 | 34 | ||
279 | 33 | def find_job(test, name, processor='386'): | 35 | def find_job(test, name, processor='386'): |
280 | @@ -140,10 +142,12 @@ | |||
281 | 140 | This avoids spurious test failures. | 142 | This avoids spurious test failures. |
282 | 141 | """ | 143 | """ |
283 | 142 | # Use the date/time the job started if available. | 144 | # Use the date/time the job started if available. |
288 | 143 | time_stamp = buildqueue.job.date_started | 145 | if buildqueue.job.date_started: |
289 | 144 | if not time_stamp: | 146 | time_stamp = buildqueue.job.date_started |
290 | 145 | time_stamp = datetime.now(utc) | 147 | else: |
291 | 146 | buildqueue._now = lambda: time_stamp | 148 | time_stamp = buildqueue._now() |
292 | 149 | |||
293 | 150 | buildqueue._now = FakeMethod(result=time_stamp) | ||
294 | 147 | return time_stamp | 151 | return time_stamp |
295 | 148 | 152 | ||
296 | 149 | 153 | ||
297 | @@ -209,7 +213,6 @@ | |||
298 | 209 | "An active build job must have a builder.") | 213 | "An active build job must have a builder.") |
299 | 210 | 214 | ||
300 | 211 | 215 | ||
301 | 212 | |||
302 | 213 | class TestBuildQueueBase(TestCaseWithFactory): | 216 | class TestBuildQueueBase(TestCaseWithFactory): |
303 | 214 | """Setup the test publisher and some builders.""" | 217 | """Setup the test publisher and some builders.""" |
304 | 215 | 218 | ||
305 | @@ -605,6 +608,7 @@ | |||
306 | 605 | # fact that no builders capable of running the job are available. | 608 | # fact that no builders capable of running the job are available. |
307 | 606 | check_mintime_to_builder(self, job, 0) | 609 | check_mintime_to_builder(self, job, 0) |
308 | 607 | 610 | ||
309 | 611 | |||
310 | 608 | class MultiArchBuildsBase(TestBuildQueueBase): | 612 | class MultiArchBuildsBase(TestBuildQueueBase): |
311 | 609 | """Set up a test environment with builds and multiple processors.""" | 613 | """Set up a test environment with builds and multiple processors.""" |
312 | 610 | def setUp(self): | 614 | def setUp(self): |
313 | @@ -805,6 +809,27 @@ | |||
314 | 805 | check_mintime_to_builder(self, apg_job, 0) | 809 | check_mintime_to_builder(self, apg_job, 0) |
315 | 806 | 810 | ||
316 | 807 | 811 | ||
317 | 812 | class TestBuildQueueDuration(TestCaseWithFactory): | ||
318 | 813 | layer = ZopelessDatabaseLayer | ||
319 | 814 | |||
320 | 815 | def _makeBuildQueue(self): | ||
321 | 816 | """Produce a `BuildQueue` object to test.""" | ||
322 | 817 | return self.factory.makeSourcePackageRecipeBuildJob() | ||
323 | 818 | |||
324 | 819 | def test_current_build_duration_not_started(self): | ||
325 | 820 | buildqueue = self._makeBuildQueue() | ||
326 | 821 | self.assertEqual(None, buildqueue.current_build_duration) | ||
327 | 822 | |||
328 | 823 | def test_current_build_duration(self): | ||
329 | 824 | buildqueue = self._makeBuildQueue() | ||
330 | 825 | now = buildqueue._now() | ||
331 | 826 | buildqueue._now = FakeMethod(result=now) | ||
332 | 827 | age = timedelta(minutes=3) | ||
333 | 828 | buildqueue.job.date_started = now - age | ||
334 | 829 | |||
335 | 830 | self.assertEqual(age, buildqueue.current_build_duration) | ||
336 | 831 | |||
337 | 832 | |||
338 | 808 | class TestJobClasses(TestCaseWithFactory): | 833 | class TestJobClasses(TestCaseWithFactory): |
339 | 809 | """Tests covering build farm job type classes.""" | 834 | """Tests covering build farm job type classes.""" |
340 | 810 | layer = LaunchpadZopelessLayer | 835 | layer = LaunchpadZopelessLayer |
341 | @@ -1301,3 +1326,7 @@ | |||
342 | 1301 | assign_to_builder(self, 'xxr-daptup', 1, None) | 1326 | assign_to_builder(self, 'xxr-daptup', 1, None) |
343 | 1302 | postgres_build, postgres_job = find_job(self, 'postgres', '386') | 1327 | postgres_build, postgres_job = find_job(self, 'postgres', '386') |
344 | 1303 | check_estimate(self, postgres_job, 120) | 1328 | check_estimate(self, postgres_job, 120) |
345 | 1329 | |||
346 | 1330 | |||
347 | 1331 | def test_suite(): | ||
348 | 1332 | return TestLoader().loadTestsFromName(__name__) | ||
349 | 1304 | 1333 | ||
350 | === modified file 'lib/lp/code/interfaces/sourcepackagerecipebuild.py' | |||
351 | --- lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-02-19 06:34:18 +0000 | |||
352 | +++ lib/lp/code/interfaces/sourcepackagerecipebuild.py 2010-03-17 06:01:34 +0000 | |||
353 | @@ -21,7 +21,7 @@ | |||
354 | 21 | from canonical.launchpad import _ | 21 | from canonical.launchpad import _ |
355 | 22 | 22 | ||
356 | 23 | from lp.buildmaster.interfaces.buildbase import IBuildBase | 23 | from lp.buildmaster.interfaces.buildbase import IBuildBase |
358 | 24 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob | 24 | from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob |
359 | 25 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe | 25 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe |
360 | 26 | from lp.registry.interfaces.person import IPerson | 26 | from lp.registry.interfaces.person import IPerson |
361 | 27 | from lp.registry.interfaces.distroseries import IDistroSeries | 27 | from lp.registry.interfaces.distroseries import IDistroSeries |
362 | @@ -79,18 +79,13 @@ | |||
363 | 79 | """ | 79 | """ |
364 | 80 | 80 | ||
365 | 81 | 81 | ||
367 | 82 | class ISourcePackageRecipeBuildJob(IBuildFarmJob): | 82 | class ISourcePackageRecipeBuildJob(IBuildFarmBuildJob): |
368 | 83 | """A read-only interface for recipe build jobs.""" | 83 | """A read-only interface for recipe build jobs.""" |
369 | 84 | 84 | ||
370 | 85 | job = Reference( | 85 | job = Reference( |
371 | 86 | IJob, title=_("Job"), required=True, readonly=True, | 86 | IJob, title=_("Job"), required=True, readonly=True, |
372 | 87 | description=_("Data common to all job types.")) | 87 | description=_("Data common to all job types.")) |
373 | 88 | 88 | ||
374 | 89 | build = Reference( | ||
375 | 90 | ISourcePackageRecipeBuild, title=_("Build"), | ||
376 | 91 | required=True, readonly=True, | ||
377 | 92 | description=_("Build record associated with this job.")) | ||
378 | 93 | |||
379 | 94 | 89 | ||
380 | 95 | class ISourcePackageRecipeBuildJobSource(Interface): | 90 | class ISourcePackageRecipeBuildJobSource(Interface): |
381 | 96 | """A utility of this interface used to create _things_.""" | 91 | """A utility of this interface used to create _things_.""" |
382 | 97 | 92 | ||
383 | === modified file 'lib/lp/soyuz/browser/build.py' | |||
384 | --- lib/lp/soyuz/browser/build.py 2010-03-10 13:28:55 +0000 | |||
385 | +++ lib/lp/soyuz/browser/build.py 2010-03-17 06:01:34 +0000 | |||
386 | @@ -525,4 +525,3 @@ | |||
387 | 525 | @property | 525 | @property |
388 | 526 | def no_results(self): | 526 | def no_results(self): |
389 | 527 | return self.form_submitted and not self.complete_builds | 527 | return self.form_submitted and not self.complete_builds |
390 | 528 | |||
391 | 529 | 528 | ||
392 | === modified file 'lib/lp/soyuz/browser/builder.py' | |||
393 | --- lib/lp/soyuz/browser/builder.py 2010-03-08 05:30:13 +0000 | |||
394 | +++ lib/lp/soyuz/browser/builder.py 2010-03-17 06:01:34 +0000 | |||
395 | @@ -18,9 +18,7 @@ | |||
396 | 18 | 'BuilderView', | 18 | 'BuilderView', |
397 | 19 | ] | 19 | ] |
398 | 20 | 20 | ||
399 | 21 | import datetime | ||
400 | 22 | import operator | 21 | import operator |
401 | 23 | import pytz | ||
402 | 24 | 22 | ||
403 | 25 | from zope.component import getUtility | 23 | from zope.component import getUtility |
404 | 26 | from zope.event import notify | 24 | from zope.event import notify |
405 | @@ -235,14 +233,10 @@ | |||
406 | 235 | 233 | ||
407 | 236 | @property | 234 | @property |
408 | 237 | def current_build_duration(self): | 235 | def current_build_duration(self): |
412 | 238 | """Return the delta representing the duration of the current job.""" | 236 | if self.context.currentjob is None: |
410 | 239 | if (self.context.currentjob is None or | ||
411 | 240 | self.context.currentjob.date_started is None): | ||
413 | 241 | return None | 237 | return None |
414 | 242 | else: | 238 | else: |
418 | 243 | UTC = pytz.timezone('UTC') | 239 | return self.context.currentjob.current_build_duration |
416 | 244 | date_started = self.context.currentjob.date_started | ||
417 | 245 | return datetime.datetime.now(UTC) - date_started | ||
419 | 246 | 240 | ||
420 | 247 | @property | 241 | @property |
421 | 248 | def page_title(self): | 242 | def page_title(self): |
422 | 249 | 243 | ||
423 | === modified file 'lib/lp/soyuz/browser/configure.zcml' | |||
424 | --- lib/lp/soyuz/browser/configure.zcml 2010-03-08 05:30:13 +0000 | |||
425 | +++ lib/lp/soyuz/browser/configure.zcml 2010-03-17 06:01:34 +0000 | |||
426 | @@ -1,4 +1,4 @@ | |||
428 | 1 | <!-- Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | <!-- Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
429 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | 2 | GNU Affero General Public License version 3 (see the file LICENSE). |
430 | 3 | --> | 3 | --> |
431 | 4 | 4 | ||
432 | @@ -798,4 +798,34 @@ | |||
433 | 798 | path_expression="string:+dependency/${dependency/id}" | 798 | path_expression="string:+dependency/${dependency/id}" |
434 | 799 | attribute_to_parent="archive" | 799 | attribute_to_parent="archive" |
435 | 800 | /> | 800 | /> |
436 | 801 | |||
437 | 802 | <!-- XXX JeroenVermeulen 2010-03-13 bug=539395: This is buildmaster, not soyuz --> | ||
438 | 803 | <browser:page | ||
439 | 804 | for="lp.buildmaster.interfaces.buildqueue.IBuildQueue" | ||
440 | 805 | name="+current" | ||
441 | 806 | class="canonical.launchpad.webapp.publisher.LaunchpadView" | ||
442 | 807 | facet="overview" | ||
443 | 808 | permission="launchpad.View" | ||
444 | 809 | template="../templates/buildqueue-current.pt"/> | ||
445 | 810 | <browser:page | ||
446 | 811 | for="lp.buildmaster.interfaces.buildqueue.IBuildFarmJob" | ||
447 | 812 | name="+current" | ||
448 | 813 | class="canonical.launchpad.webapp.publisher.LaunchpadView" | ||
449 | 814 | facet="overview" | ||
450 | 815 | permission="launchpad.View" | ||
451 | 816 | template="../templates/buildfarmjob-current.pt"/> | ||
452 | 817 | <browser:page | ||
453 | 818 | for="lp.buildmaster.interfaces.buildfarmbranchjob.IBuildFarmBranchJob" | ||
454 | 819 | name="+current" | ||
455 | 820 | class="canonical.launchpad.webapp.publisher.LaunchpadView" | ||
456 | 821 | facet="overview" | ||
457 | 822 | permission="launchpad.View" | ||
458 | 823 | template="../templates/buildfarmbranchjob-current.pt"/> | ||
459 | 824 | <browser:page | ||
460 | 825 | for="lp.soyuz.interfaces.buildfarmbuildjob.IBuildFarmBuildJob" | ||
461 | 826 | name="+current" | ||
462 | 827 | class="canonical.launchpad.webapp.publisher.LaunchpadView" | ||
463 | 828 | facet="overview" | ||
464 | 829 | permission="launchpad.View" | ||
465 | 830 | template="../templates/buildfarmbuildjob-current.pt"/> | ||
466 | 801 | </configure> | 831 | </configure> |
467 | 802 | 832 | ||
468 | === modified file 'lib/lp/soyuz/doc/build-estimated-dispatch-time.txt' | |||
469 | --- lib/lp/soyuz/doc/build-estimated-dispatch-time.txt 2010-03-06 04:57:40 +0000 | |||
470 | +++ lib/lp/soyuz/doc/build-estimated-dispatch-time.txt 2010-03-17 06:01:34 +0000 | |||
471 | @@ -56,7 +56,6 @@ | |||
472 | 56 | 56 | ||
473 | 57 | >>> from datetime import datetime | 57 | >>> from datetime import datetime |
474 | 58 | >>> import pytz | 58 | >>> import pytz |
475 | 59 | >>> UTC = pytz.timezone('UTC') | ||
476 | 60 | >>> bob_the_builder = builder_set.get(1) | 59 | >>> bob_the_builder = builder_set.get(1) |
477 | 61 | >>> cur_bqueue = bob_the_builder.currentjob | 60 | >>> cur_bqueue = bob_the_builder.currentjob |
478 | 62 | >>> from lp.soyuz.interfaces.build import IBuildSet | 61 | >>> from lp.soyuz.interfaces.build import IBuildSet |
479 | @@ -76,7 +75,7 @@ | |||
480 | 76 | >>> from zope.security.proxy import removeSecurityProxy | 75 | >>> from zope.security.proxy import removeSecurityProxy |
481 | 77 | >>> cur_bqueue.lastscore = 1111 | 76 | >>> cur_bqueue.lastscore = 1111 |
482 | 78 | >>> cur_bqueue.setDateStarted( | 77 | >>> cur_bqueue.setDateStarted( |
484 | 79 | ... datetime(2008, 4, 1, 10, 45, 39, tzinfo=UTC)) | 78 | ... datetime(2008, 4, 1, 10, 45, 39, tzinfo=pytz.UTC)) |
485 | 80 | >>> print cur_bqueue.date_started | 79 | >>> print cur_bqueue.date_started |
486 | 81 | 2008-04-01 10:45:39+00:00 | 80 | 2008-04-01 10:45:39+00:00 |
487 | 82 | 81 | ||
488 | @@ -89,7 +88,7 @@ | |||
489 | 89 | The estimated start time for the pending job is either now or lies | 88 | The estimated start time for the pending job is either now or lies |
490 | 90 | in the future. | 89 | in the future. |
491 | 91 | 90 | ||
493 | 92 | >>> now = datetime.utcnow() | 91 | >>> now = datetime.now(pytz.UTC) |
494 | 93 | >>> def job_start_estimate(build): | 92 | >>> def job_start_estimate(build): |
495 | 94 | ... return build.buildqueue_record.getEstimatedJobStartTime() | 93 | ... return build.buildqueue_record.getEstimatedJobStartTime() |
496 | 95 | >>> estimate = job_start_estimate(alsa_build) | 94 | >>> estimate = job_start_estimate(alsa_build) |
497 | @@ -147,7 +146,7 @@ | |||
498 | 147 | Since the 'iceweasel' build has a higher score (666) than the 'pmount' | 146 | Since the 'iceweasel' build has a higher score (666) than the 'pmount' |
499 | 148 | build (66) its estimated dispatch time is essentially "now". | 147 | build (66) its estimated dispatch time is essentially "now". |
500 | 149 | 148 | ||
502 | 150 | >>> now = datetime.utcnow() | 149 | >>> now = datetime.now(pytz.UTC) |
503 | 151 | >>> estimate = job_start_estimate(iceweasel_build) | 150 | >>> estimate = job_start_estimate(iceweasel_build) |
504 | 152 | >>> estimate > now | 151 | >>> estimate > now |
505 | 153 | True | 152 | True |
506 | 154 | 153 | ||
507 | === added file 'lib/lp/soyuz/interfaces/buildfarmbuildjob.py' | |||
508 | --- lib/lp/soyuz/interfaces/buildfarmbuildjob.py 1970-01-01 00:00:00 +0000 | |||
509 | +++ lib/lp/soyuz/interfaces/buildfarmbuildjob.py 2010-03-17 06:01:34 +0000 | |||
510 | @@ -0,0 +1,21 @@ | |||
511 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
512 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
513 | 3 | |||
514 | 4 | """Interface to support UI for most build-farm jobs.""" | ||
515 | 5 | |||
516 | 6 | __metaclass__ = type | ||
517 | 7 | __all__ = [ | ||
518 | 8 | 'IBuildFarmBuildJob' | ||
519 | 9 | ] | ||
520 | 10 | |||
521 | 11 | from canonical.launchpad import _ | ||
522 | 12 | from lazr.restful.fields import Reference | ||
523 | 13 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob | ||
524 | 14 | from lp.soyuz.interfaces.build import IBuild | ||
525 | 15 | |||
526 | 16 | |||
527 | 17 | class IBuildFarmBuildJob(IBuildFarmJob): | ||
528 | 18 | """An `IBuildFarmJob` with an `IBuild` reference.""" | ||
529 | 19 | build = Reference( | ||
530 | 20 | IBuild, title=_("Build"), required=True, readonly=True, | ||
531 | 21 | description=_("Build record associated with this job.")) | ||
532 | 0 | 22 | ||
533 | === modified file 'lib/lp/soyuz/interfaces/buildpackagejob.py' | |||
534 | --- lib/lp/soyuz/interfaces/buildpackagejob.py 2010-01-20 22:09:26 +0000 | |||
535 | +++ lib/lp/soyuz/interfaces/buildpackagejob.py 2010-03-17 06:01:34 +0000 | |||
536 | @@ -15,12 +15,12 @@ | |||
537 | 15 | 15 | ||
538 | 16 | from canonical.launchpad import _ | 16 | from canonical.launchpad import _ |
539 | 17 | from lazr.restful.fields import Reference | 17 | from lazr.restful.fields import Reference |
540 | 18 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob | ||
541 | 19 | from lp.services.job.interfaces.job import IJob | 18 | from lp.services.job.interfaces.job import IJob |
542 | 20 | from lp.soyuz.interfaces.build import IBuild | 19 | from lp.soyuz.interfaces.build import IBuild |
546 | 21 | 20 | from lp.soyuz.interfaces.buildfarmbuildjob import IBuildFarmBuildJob | |
547 | 22 | 21 | ||
548 | 23 | class IBuildPackageJob(IBuildFarmJob): | 22 | |
549 | 23 | class IBuildPackageJob(IBuildFarmBuildJob): | ||
550 | 24 | """A read-only interface for build package jobs.""" | 24 | """A read-only interface for build package jobs.""" |
551 | 25 | 25 | ||
552 | 26 | id = Int(title=_('ID'), required=True, readonly=True) | 26 | id = Int(title=_('ID'), required=True, readonly=True) |
553 | 27 | 27 | ||
554 | === modified file 'lib/lp/soyuz/templates/builder-index.pt' | |||
555 | --- lib/lp/soyuz/templates/builder-index.pt 2009-11-12 16:19:18 +0000 | |||
556 | +++ lib/lp/soyuz/templates/builder-index.pt 2010-03-17 06:01:34 +0000 | |||
557 | @@ -104,32 +104,10 @@ | |||
558 | 104 | </tal:buildernok> | 104 | </tal:buildernok> |
559 | 105 | </tal:no_job> | 105 | </tal:no_job> |
560 | 106 | 106 | ||
567 | 107 | <tal:comment replace="nothing"> | 107 | <tal:job-header condition="job"> |
562 | 108 | In the very near future, 'job' will not just be a Build job. | ||
563 | 109 | The template needs to cope with that as and when new job types are | ||
564 | 110 | added. | ||
565 | 111 | </tal:comment> | ||
566 | 112 | <tal:job condition="job"> | ||
568 | 113 | <span class="sortkey" tal:content="job/id" /> | 108 | <span class="sortkey" tal:content="job/id" /> |
588 | 114 | <tal:build define="build job/specific_job/build"> | 109 | <tal:specific-job replace="structure job/specific_job/@@+current" /> |
589 | 115 | <tal:visible condition="build/required:launchpad.View"> | 110 | </tal:job-header> |
571 | 116 | <tal:icon replace="structure build/image:icon" /> | ||
572 | 117 | Building | ||
573 | 118 | <a tal:attributes="href build/fmt:url" | ||
574 | 119 | tal:content="build/title" | ||
575 | 120 | >i386 build of mozilla-firefox 0.9 in ubuntu hoary RELEASE</a> | ||
576 | 121 | <tal:ppa condition="build/archive/is_ppa" | ||
577 | 122 | define="ppa build/archive;"> | ||
578 | 123 | <span tal:replace="string: [${ppa/owner/name}/${ppa/name}]" | ||
579 | 124 | >[cprov/ppa]</span> | ||
580 | 125 | </tal:ppa> | ||
581 | 126 | </tal:visible> | ||
582 | 127 | <tal:restricted condition="not: build/required:launchpad.View"> | ||
583 | 128 | <img src="/@@/processing" alt="[building]" /> | ||
584 | 129 | Building private source | ||
585 | 130 | </tal:restricted> | ||
586 | 131 | </tal:build> | ||
587 | 132 | </tal:job> | ||
590 | 133 | 111 | ||
591 | 134 | </metal:macro> | 112 | </metal:macro> |
592 | 135 | 113 | ||
593 | @@ -147,7 +125,7 @@ | |||
594 | 147 | </span> | 125 | </span> |
595 | 148 | Current status</h2> | 126 | Current status</h2> |
596 | 149 | 127 | ||
598 | 150 | <p tal:define="builder context"> | 128 | <p tal:define="builder context" id="current-build-summary"> |
599 | 151 | <metal:summary use-macro="template/macros/status-summary" /> | 129 | <metal:summary use-macro="template/macros/status-summary" /> |
600 | 152 | </p> | 130 | </p> |
601 | 153 | 131 | ||
602 | @@ -156,27 +134,7 @@ | |||
603 | 156 | context/failnotes/fmt:text-to-html" /> | 134 | context/failnotes/fmt:text-to-html" /> |
604 | 157 | </tal:buildernok> | 135 | </tal:buildernok> |
605 | 158 | 136 | ||
627 | 159 | <tal:job condition="job"> | 137 | <tal:job condition="job" replace="structure job/@@+current" /> |
607 | 160 | <p class="sprite">Started | ||
608 | 161 | <span tal:attributes="title job/job/date_started/fmt:datetime" | ||
609 | 162 | tal:content="view/current_build_duration/fmt:exactduration" | ||
610 | 163 | /> ago.</p> | ||
611 | 164 | <tal:visible | ||
612 | 165 | define="build job/specific_job/build" | ||
613 | 166 | condition="build/required:launchpad.View"> | ||
614 | 167 | <tal:logtail condition="job/logtail"> | ||
615 | 168 | <h3>Buildlog</h3> | ||
616 | 169 | <div tal:content="structure job/logtail/fmt:text-to-html" | ||
617 | 170 | id="buildlog-tail" class="logtail"> | ||
618 | 171 | Things are crashing and burning all over the place. | ||
619 | 172 | </div> | ||
620 | 173 | <p class="discreet" tal:condition="view/user"> | ||
621 | 174 | Updated on | ||
622 | 175 | <span tal:replace="structure view/user/fmt:local-time"/> | ||
623 | 176 | </p> | ||
624 | 177 | </tal:logtail> | ||
625 | 178 | </tal:visible> | ||
626 | 179 | </tal:job> | ||
628 | 180 | </div> | 138 | </div> |
629 | 181 | 139 | ||
630 | 182 | </metal:macro> | 140 | </metal:macro> |
631 | 183 | 141 | ||
632 | === added file 'lib/lp/soyuz/templates/buildfarmbranchjob-current.pt' | |||
633 | --- lib/lp/soyuz/templates/buildfarmbranchjob-current.pt 1970-01-01 00:00:00 +0000 | |||
634 | +++ lib/lp/soyuz/templates/buildfarmbranchjob-current.pt 2010-03-17 06:01:34 +0000 | |||
635 | @@ -0,0 +1,11 @@ | |||
636 | 1 | <tal:root | ||
637 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
638 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
639 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
640 | 5 | omit-tag=""> | ||
641 | 6 | |||
642 | 7 | <img src="/@@/processing" alt="[building]" /> | ||
643 | 8 | Working on | ||
644 | 9 | <tal:jobtype replace="context/__class__/__name__" /> | ||
645 | 10 | for branch <tal:branch replace="structure context/branch/fmt:link" />. | ||
646 | 11 | </tal:root> | ||
647 | 0 | 12 | ||
648 | === added file 'lib/lp/soyuz/templates/buildfarmbuildjob-current.pt' | |||
649 | --- lib/lp/soyuz/templates/buildfarmbuildjob-current.pt 1970-01-01 00:00:00 +0000 | |||
650 | +++ lib/lp/soyuz/templates/buildfarmbuildjob-current.pt 2010-03-17 06:01:34 +0000 | |||
651 | @@ -0,0 +1,8 @@ | |||
652 | 1 | <tal:root | ||
653 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
654 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
655 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
656 | 5 | omit-tag=""> | ||
657 | 6 | |||
658 | 7 | Building <tal:build replace="structure context/build/fmt:link" /> | ||
659 | 8 | </tal:root> | ||
660 | 0 | 9 | ||
661 | === added file 'lib/lp/soyuz/templates/buildfarmjob-current.pt' | |||
662 | --- lib/lp/soyuz/templates/buildfarmjob-current.pt 1970-01-01 00:00:00 +0000 | |||
663 | +++ lib/lp/soyuz/templates/buildfarmjob-current.pt 2010-03-17 06:01:34 +0000 | |||
664 | @@ -0,0 +1,10 @@ | |||
665 | 1 | <tal:root | ||
666 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
667 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
668 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
669 | 5 | omit-tag=""> | ||
670 | 6 | |||
671 | 7 | <img src="/@@/processing" alt="[building]" /> | ||
672 | 8 | Working on | ||
673 | 9 | <tal:jobtype replace="context/__class__/__name__" />. | ||
674 | 10 | </tal:root> | ||
675 | 0 | 11 | ||
676 | === added file 'lib/lp/soyuz/templates/buildqueue-current.pt' | |||
677 | --- lib/lp/soyuz/templates/buildqueue-current.pt 1970-01-01 00:00:00 +0000 | |||
678 | +++ lib/lp/soyuz/templates/buildqueue-current.pt 2010-03-17 06:01:34 +0000 | |||
679 | @@ -0,0 +1,24 @@ | |||
680 | 1 | <tal:root | ||
681 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
682 | 3 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
683 | 4 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
684 | 5 | omit-tag=""> | ||
685 | 6 | |||
686 | 7 | <p class="sprite">Started | ||
687 | 8 | <span | ||
688 | 9 | tal:attributes="title context/job/date_started/fmt:datetime" | ||
689 | 10 | tal:content="context/current_build_duration/fmt:exactduration" /> | ||
690 | 11 | ago. | ||
691 | 12 | </p> | ||
692 | 13 | <tal:logtail condition="context/specific_job/required:launchpad.View"> | ||
693 | 14 | <h2>Buildlog</h2> | ||
694 | 15 | <div tal:content="structure context/logtail/fmt:text-to-html" | ||
695 | 16 | id="buildlog-tail" | ||
696 | 17 | class="logtail"> | ||
697 | 18 | Things are crashing and burning all over the place. | ||
698 | 19 | </div> | ||
699 | 20 | <p class="discreet" tal:condition="view/user"> | ||
700 | 21 | Updated on <tal:date replace="structure view/user/fmt:local-time" /> | ||
701 | 22 | </p> | ||
702 | 23 | </tal:logtail> | ||
703 | 24 | </tal:root> | ||
704 | 0 | 25 | ||
705 | === modified file 'lib/lp/translations/model/translationtemplatesbuildjob.py' | |||
706 | --- lib/lp/translations/model/translationtemplatesbuildjob.py 2010-03-06 00:21:57 +0000 | |||
707 | +++ lib/lp/translations/model/translationtemplatesbuildjob.py 2010-03-17 06:01:34 +0000 | |||
708 | @@ -19,10 +19,11 @@ | |||
709 | 19 | DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR) | 19 | DEFAULT_FLAVOR, IStoreSelector, MAIN_STORE, MASTER_FLAVOR) |
710 | 20 | 20 | ||
711 | 21 | from lp.buildmaster.interfaces.buildfarmjob import ( | 21 | from lp.buildmaster.interfaces.buildfarmjob import ( |
713 | 22 | BuildFarmJobType, IBuildFarmJob, ISpecificBuildFarmJobClass) | 22 | BuildFarmJobType, ISpecificBuildFarmJobClass) |
714 | 23 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 23 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
715 | 24 | from lp.buildmaster.model.buildqueue import BuildQueue | 24 | from lp.buildmaster.model.buildqueue import BuildQueue |
717 | 25 | from lp.code.interfaces.branchjob import IBranchJob, IRosettaUploadJobSource | 25 | from lp.code.interfaces.branchjob import IRosettaUploadJobSource |
718 | 26 | from lp.buildmaster.interfaces.buildfarmbranchjob import IBuildFarmBranchJob | ||
719 | 26 | from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType | 27 | from lp.code.model.branchjob import BranchJob, BranchJobDerived, BranchJobType |
720 | 27 | from lp.translations.interfaces.translationtemplatesbuildjob import ( | 28 | from lp.translations.interfaces.translationtemplatesbuildjob import ( |
721 | 28 | ITranslationTemplatesBuildJobSource) | 29 | ITranslationTemplatesBuildJobSource) |
722 | @@ -34,7 +35,7 @@ | |||
723 | 34 | 35 | ||
724 | 35 | Implementation-wise, this is actually a `BranchJob`. | 36 | Implementation-wise, this is actually a `BranchJob`. |
725 | 36 | """ | 37 | """ |
727 | 37 | implements(IBranchJob, IBuildFarmJob) | 38 | implements(IBuildFarmBranchJob) |
728 | 38 | 39 | ||
729 | 39 | class_job_type = BranchJobType.TRANSLATION_TEMPLATES_BUILD | 40 | class_job_type = BranchJobType.TRANSLATION_TEMPLATES_BUILD |
730 | 40 | 41 | ||
731 | @@ -101,7 +102,7 @@ | |||
732 | 101 | """See `ITranslationTemplatesBuildJobSource`.""" | 102 | """See `ITranslationTemplatesBuildJobSource`.""" |
733 | 102 | store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) | 103 | store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
734 | 103 | 104 | ||
736 | 104 | # We don't have any JSON metadata for this BranchJob type. | 105 | # Pass public HTTP URL for the branch. |
737 | 105 | metadata = {'branch_url': branch.composePublicURL()} | 106 | metadata = {'branch_url': branch.composePublicURL()} |
738 | 106 | branch_job = BranchJob( | 107 | branch_job = BranchJob( |
739 | 107 | branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata) | 108 | branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata) |
740 | 108 | 109 | ||
741 | === added directory 'lib/lp/translations/stories/buildfarm' | |||
742 | === added file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt' | |||
743 | --- lib/lp/translations/stories/buildfarm/xx-build-summary.txt 1970-01-01 00:00:00 +0000 | |||
744 | +++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-03-17 06:01:34 +0000 | |||
745 | @@ -0,0 +1,69 @@ | |||
746 | 1 | = TranslationTemplatesBuildJob Build Summary = | ||
747 | 2 | |||
748 | 3 | The builders UI can show TranslationTemplateBuildJobs, although they | ||
749 | 4 | look a little different from Soyuz-style jobs. | ||
750 | 5 | |||
751 | 6 | == Setup == | ||
752 | 7 | |||
753 | 8 | Create a builder working on a TranslationTemplatesBuildJob for a branch. | ||
754 | 9 | |||
755 | 10 | >>> from zope.component import getUtility | ||
756 | 11 | >>> from canonical.launchpad.interfaces.launchpad import ( | ||
757 | 12 | ... ILaunchpadCelebrities) | ||
758 | 13 | >>> from canonical.launchpad.interfaces.librarian import ( | ||
759 | 14 | ... ILibraryFileAliasSet) | ||
760 | 15 | >>> from canonical.launchpad.scripts.logger import QuietFakeLogger | ||
761 | 16 | >>> from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | ||
762 | 17 | >>> from lp.testing.fakemethod import FakeMethod | ||
763 | 18 | >>> from lp.translations.interfaces.translations import ( | ||
764 | 19 | ... TranslationsBranchImportMode) | ||
765 | 20 | |||
766 | 21 | >>> class FakeSlave: | ||
767 | 22 | ... resume = FakeMethod(result=('stdout', 'stderr', 0)) | ||
768 | 23 | ... build = FakeMethod() | ||
769 | 24 | ... cacheFile = FakeMethod() | ||
770 | 25 | |||
771 | 26 | >>> login(ANONYMOUS) | ||
772 | 27 | >>> owner_email = factory.getUniqueString() + '@example.com' | ||
773 | 28 | >>> owner = factory.makePerson(email=owner_email, password='test') | ||
774 | 29 | |||
775 | 30 | >>> productseries = factory.makeProductSeries(owner=owner) | ||
776 | 31 | >>> product = productseries.product | ||
777 | 32 | >>> product.official_rosetta = True | ||
778 | 33 | >>> branch = factory.makeProductBranch(product=product, owner=owner) | ||
779 | 34 | >>> branch_url = branch.unique_name | ||
780 | 35 | |||
781 | 36 | >>> productseries.branch = factory.makeBranch() | ||
782 | 37 | >>> productseries.translations_autoimport_mode = ( | ||
783 | 38 | ... TranslationsBranchImportMode.IMPORT_TEMPLATES) | ||
784 | 39 | >>> specific_job = factory.makeTranslationTemplatesBuildJob(branch=branch) | ||
785 | 40 | >>> buildqueue = getUtility(IBuildQueueSet).getByJob(specific_job.job) | ||
786 | 41 | |||
787 | 42 | >>> fake_chroot = getUtility(ILibraryFileAliasSet)[1] | ||
788 | 43 | >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu | ||
789 | 44 | >>> unused = ubuntu.currentseries.nominatedarchindep.addOrUpdateChroot( | ||
790 | 45 | ... fake_chroot) | ||
791 | 46 | |||
792 | 47 | >>> builder = factory.makeBuilder(vm_host=factory.getUniqueString()) | ||
793 | 48 | >>> builder.setSlaveForTesting(FakeSlave()) | ||
794 | 49 | >>> builder.startBuild(buildqueue, QuietFakeLogger()) | ||
795 | 50 | |||
796 | 51 | >>> builder_page = canonical_url(builder) | ||
797 | 52 | >>> logout() | ||
798 | 53 | |||
799 | 54 | Helper: find the current build's summary on a browser's current page. | ||
800 | 55 | |||
801 | 56 | >>> def find_build_summary(browser): | ||
802 | 57 | ... return find_tag_by_id(browser.contents, 'current-build-summary') | ||
803 | 58 | |||
804 | 59 | |||
805 | 60 | == Show summary == | ||
806 | 61 | |||
807 | 62 | The job's summary shows that what type of job this is. It also links | ||
808 | 63 | to the branch. | ||
809 | 64 | |||
810 | 65 | >>> user_browser.open(builder_page) | ||
811 | 66 | >>> print extract_text(find_build_summary(user_browser)) | ||
812 | 67 | Working on TranslationTemplatesBuildJob for branch ... | ||
813 | 68 | |||
814 | 69 | >>> user_browser.getLink(branch_url).click() |
Hi Jeroen,
Thanks for the nice agrarian branch.
Running your test command (was that just 'make check' in disguise?) I ildToSlave.
got a failure on test_dispatchBu
Jeroen I tried on two different occasions to follow your instructions
in order to exercise the new UI. Both times were very time consuming
and ultimately failed. I give up. I approve the code but not the UI
-- you needed to get a separate UI review anyway but as a code
reviewer I like to have a once-over.
> === modified file 'lib/canonical/ launchpad/ security. py' launchpad/ security. py 2010-03-12 08:59:36 +0000 launchpad/ security. py 2010-03-16 11:13:21 +0000 checkUnauthenti cated() b(Authorization Base): sion(self) : (self.obj. build) ibility( self, user=None): hJob.providedBy (self.obj) : branch. visibleByUser( user)
> --- lib/canonical/
> +++ lib/canonical/
> @@ -1505,6 +1508,41 @@
> return auth_spr.
>
>
> +class ViewBuildFarmJo
> + """Permission to view an `IBuildFarmJob`.
> +
> + This permission is based entirely on permission to view the
> + associated `IBuild` and/or `IBranch`.
> + """
> + permission = 'launchpad.View'
> + usedfor = IBuildFarmJob
> +
> + def _getBuildPermis
> + return ViewBuildRecord
> +
> + def _checkBranchVis
> + """Is the user free to view any branches associated with this job?"""
> + if not IBuildFarmBranc
> + return True
> + else:
> + return self.obj.
I don't like the inverted test (our coding standards recommend against
having a 'not' test if both paths are present) and the fact a return
value of True has dual meanings.
> + def checkAuthentica ted(self, user): chVisibility( user): Job.providedBy( self.obj) : ermission( ).checkAuthenti cated(user) :
> + if not self._checkBran
> + return False
> +
> + if IBuildFarmBuild
> + if not self._getBuildP
> + return False
> +
> + return True
Could you write a single method that provides checkAuthenticated and cated for your two possiblities? A nice little wrapper
checkUnauthenti
would make the real methods more readable.
> + def checkUnauthenti cated(self) : chVisibility( ): ermission( ).checkUnauthen ticated( )
> + if not self._checkBran
> + return False
> + return self._getBuildP
Again I find this implementation confusing.
> + AdminByAdminsTe am):
> class AdminQuestion(
> permission = 'launchpad.Admin'
> usedfor = IQuestion
> === modified file 'lib/canonical/ launchpad/ webapp/ tales.py' launchpad/ webapp/ tales.py 2010-03-10 12:50:18 +0000 launchpad/ webapp/ tales.py 2010-03-16 11:13:21 +0000 terAPI( ObjectFormatter API):
> --- lib/canonical/
> +++ lib/canonical/
> @@ -1514,6 +1514,33 @@
> return url
>
>
> +class BuildBaseFormat
> + """Adapter providing fmt support for `IBuildBase` objects."""
You have plenty of room so please spell out formatter.
> + def _composeArchive Reference( self, archive): archive. owner.name) , cgi.escape( archive. name))
> + if archive.is_ppa:
> + return " [%s/%s]" % (
> + cgi.escape(
> + else:
> + return ""
> +
> + def icon(self, view_name):
> + if not check_permi...