Merge lp:~julian-edwards/launchpad/api-expose-builders into lp:launchpad

Proposed by Julian Edwards
Status: Merged
Approved by: Leonard Richardson
Approved revision: no longer in the source branch.
Merged at revision: 11952
Proposed branch: lp:~julian-edwards/launchpad/api-expose-builders
Merge into: lp:launchpad
Diff against target: 296 lines (+142/-25)
5 files modified
lib/lp/buildmaster/configure.zcml (+2/-0)
lib/lp/buildmaster/interfaces/builder.py (+47/-24)
lib/lp/buildmaster/interfaces/webservice.py (+20/-0)
lib/lp/buildmaster/model/builder.py (+5/-1)
lib/lp/soyuz/stories/webservice/xx-builders.txt (+68/-0)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/api-expose-builders
Reviewer Review Type Date Requested Status
Leonard Richardson (community) Approve
Review via email: mp+39379@code.launchpad.net

Description of the change

= Summary =
Expose IBuilder in the webservice

== Implementation details ==
The usual trivial-ish interface changes. /builders is a top-level collection.

== Tests ==
bin/test -cvvt xx-builders.txt

To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This looks good. Just some nitpicky stuff:

On the web service, getByName is a named operation (or "operation"), not a method. Calling it a method makes it look like a local method call.

'Our "bob" builder was retrieved using the no-priv user, and he does not..' The "he" is ambiguous. s/he/no-priv/.

Do you want to iterate over a *sorted* list of attribute names? bob.lp_attributes is ultimately dependent on the order of attributes in the IBuilder definition. Something to think about.

You might want to distinguish between cprov's launchpad and no-priv's in the variable names. 'launchpad' and 'cprov_launchpad' or 'privileged_launchpad'.

Revision history for this message
Leonard Richardson (leonardr) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/buildmaster/configure.zcml'
2--- lib/lp/buildmaster/configure.zcml 2010-07-26 15:52:22 +0000
3+++ lib/lp/buildmaster/configure.zcml 2010-11-19 16:16:49 +0000
4@@ -6,6 +6,7 @@
5 xmlns="http://namespaces.zope.org/zope"
6 xmlns:browser="http://namespaces.zope.org/browser"
7 xmlns:i18n="http://namespaces.zope.org/i18n"
8+ xmlns:webservice="http://namespaces.canonical.com/webservice"
9 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
10 i18n_domain="launchpad">
11 <include package=".browser"/>
12@@ -114,5 +115,6 @@
13 interface="lp.buildmaster.interfaces.buildqueue.IBuildQueueSet"/>
14 </securedutility>
15
16+ <webservice:register module="lp.buildmaster.interfaces.webservice" />
17
18 </configure>
19
20=== modified file 'lib/lp/buildmaster/interfaces/builder.py'
21--- lib/lp/buildmaster/interfaces/builder.py 2010-10-27 14:25:19 +0000
22+++ lib/lp/buildmaster/interfaces/builder.py 2010-11-19 16:16:49 +0000
23@@ -19,6 +19,16 @@
24 'ProtocolVersionMismatch',
25 ]
26
27+from lazr.restful.declarations import (
28+ collection_default_content,
29+ export_as_webservice_collection,
30+ export_as_webservice_entry,
31+ export_read_operation,
32+ exported,
33+ operation_parameters,
34+ operation_returns_entry,
35+ )
36+
37 from zope.interface import (
38 Attribute,
39 Interface,
40@@ -38,6 +48,7 @@
41 from lp.registry.interfaces.role import IHasOwner
42 from lp.services.fields import (
43 Description,
44+ PersonChoice,
45 Title,
46 )
47
48@@ -92,71 +103,74 @@
49 machine status representation, including the field/properties:
50 virtualized, builderok, status, failnotes and currentjob.
51 """
52+ export_as_webservice_entry()
53+
54 id = Attribute("Builder identifier")
55+
56 processor = Choice(
57 title=_('Processor'), required=True, vocabulary='Processor',
58 description=_('Build Slave Processor, used to identify '
59 'which jobs can be built by this device.'))
60
61- owner = Choice(
62+ owner = exported(PersonChoice(
63 title=_('Owner'), required=True, vocabulary='ValidOwner',
64 description=_('Builder owner, a Launchpad member which '
65- 'will be responsible for this device.'))
66+ 'will be responsible for this device.')))
67
68- url = TextLine(
69+ url = exported(TextLine(
70 title=_('URL'), required=True, constraint=builder_url_validator,
71 description=_('The URL to the build machine, used as a unique '
72 'identifier. Includes protocol, host and port only, '
73- 'e.g.: http://farm.com:8221/'))
74+ 'e.g.: http://farm.com:8221/')))
75
76- name = TextLine(
77+ name = exported(TextLine(
78 title=_('Name'), required=True, constraint=name_validator,
79- description=_('Builder Slave Name used for reference proposes'))
80+ description=_('Builder Slave Name used for reference proposes')))
81
82- title = Title(
83+ title = exported(Title(
84 title=_('Title'), required=True,
85- description=_('The builder slave title. Should be just a few words.'))
86+ description=_('The builder slave title. Should be just a few words.')))
87
88- description = Description(
89+ description = exported(Description(
90 title=_('Description'), required=False,
91 description=_('The builder slave description, may be several '
92 'paragraphs of text, giving the highlights and '
93- 'details.'))
94+ 'details.')))
95
96- virtualized = Bool(
97+ virtualized = exported(Bool(
98 title=_('Virtualized'), required=True, default=False,
99 description=_('Whether or not the builder is a virtual Xen '
100- 'instance.'))
101+ 'instance.')))
102
103- manual = Bool(
104+ manual = exported(Bool(
105 title=_('Manual Mode'), required=False, default=False,
106 description=_('The auto-build system does not dispatch '
107- 'jobs automatically for slaves in manual mode.'))
108+ 'jobs automatically for slaves in manual mode.')))
109
110- builderok = Bool(
111+ builderok = exported(Bool(
112 title=_('Builder State OK'), required=True, default=True,
113- description=_('Whether or not the builder is ok'))
114+ description=_('Whether or not the builder is ok')))
115
116- failnotes = Text(
117+ failnotes = exported(Text(
118 title=_('Failure Notes'), required=False,
119- description=_('The reason for a builder not being ok'))
120+ description=_('The reason for a builder not being ok')))
121
122- vm_host = TextLine(
123+ vm_host = exported(TextLine(
124 title=_('Virtual Machine Host'), required=False,
125 description=_('The machine hostname hosting the virtual '
126- 'buildd-slave, e.g.: foobar-host.ppa'))
127+ 'buildd-slave, e.g.: foobar-host.ppa')))
128
129- active = Bool(
130+ active = exported(Bool(
131 title=_('Publicly Visible'), required=True, default=True,
132- description=_('Whether or not to present the builder publicly.'))
133+ description=_('Whether or not to present the builder publicly.')))
134
135 slave = Attribute("xmlrpclib.Server instance corresponding to builder.")
136
137 currentjob = Attribute("BuildQueue instance for job being processed.")
138
139- failure_count = Int(
140+ failure_count = exported(Int(
141 title=_('Failure Count'), required=False, default=0,
142- description=_("Number of consecutive failures for this builder."))
143+ description=_("Number of consecutive failures for this builder.")))
144
145 current_build_behavior = Field(
146 title=u"The current behavior of the builder for the current job.",
147@@ -333,6 +347,7 @@
148 Methods on this interface should deal with the set of Builders:
149 methods that affect a single Builder should be on IBuilder.
150 """
151+ export_as_webservice_collection(IBuilder)
152
153 title = Attribute('Title')
154
155@@ -342,6 +357,13 @@
156 def __getitem__(name):
157 """Retrieve a builder by name"""
158
159+ @operation_parameters(
160+ name=TextLine(title=_("Builder name"), required=True))
161+ @operation_returns_entry(IBuilder)
162+ @export_read_operation()
163+ def getByName(name):
164+ """Retrieve a builder by name"""
165+
166 def new(processor, url, name, title, description, owner,
167 active=True, virtualized=False, vm_host=None):
168 """Create a new Builder entry.
169@@ -360,6 +382,7 @@
170 def get(builder_id):
171 """Return the IBuilder with the given builderid."""
172
173+ @collection_default_content()
174 def getBuilders():
175 """Return all active configured builders."""
176
177
178=== added file 'lib/lp/buildmaster/interfaces/webservice.py'
179--- lib/lp/buildmaster/interfaces/webservice.py 1970-01-01 00:00:00 +0000
180+++ lib/lp/buildmaster/interfaces/webservice.py 2010-11-19 16:16:49 +0000
181@@ -0,0 +1,20 @@
182+# Copyright 2010 Canonical Ltd. This software is licensed under the
183+# GNU Affero General Public License version 3 (see the file LICENSE).
184+
185+"""All the interfaces that are exposed through the webservice.
186+
187+There is a declaration in ZCML somewhere that looks like:
188+ <webservice:register module="lp.soyuz.interfaces.webservice" />
189+
190+which tells `lazr.restful` that it should look for webservice exports here.
191+"""
192+
193+__all__ = [
194+ 'IBuilder',
195+ 'IBuilderSet',
196+ ]
197+
198+from lp.buildmaster.interfaces.builder import (
199+ IBuilder,
200+ IBuilderSet,
201+ )
202
203=== modified file 'lib/lp/buildmaster/model/builder.py'
204--- lib/lp/buildmaster/model/builder.py 2010-11-17 16:22:45 +0000
205+++ lib/lp/buildmaster/model/builder.py 2010-11-19 16:16:49 +0000
206@@ -832,12 +832,16 @@
207 def __iter__(self):
208 return iter(Builder.select())
209
210- def __getitem__(self, name):
211+ def getByName(self, name):
212+ """See IBuilderSet."""
213 try:
214 return Builder.selectOneBy(name=name)
215 except SQLObjectNotFound:
216 raise NotFoundError(name)
217
218+ def __getitem__(self, name):
219+ return self.getByName(name)
220+
221 def new(self, processor, url, name, title, description, owner,
222 active=True, virtualized=False, vm_host=None, manual=True):
223 """See IBuilderSet."""
224
225=== added file 'lib/lp/soyuz/stories/webservice/xx-builders.txt'
226--- lib/lp/soyuz/stories/webservice/xx-builders.txt 1970-01-01 00:00:00 +0000
227+++ lib/lp/soyuz/stories/webservice/xx-builders.txt 2010-11-19 16:16:49 +0000
228@@ -0,0 +1,68 @@
229+========
230+Builders
231+========
232+
233+The webservice exposes a top-level collection called "builders" which
234+contains all the registered builders in the Launchpad build farm.
235+
236+ >>> nopriv_launchpad = launchpadlib_for(
237+ ... 'builders test', 'no-priv', version='devel')
238+ >>> builders = nopriv_launchpad.builders
239+
240+Iterating over the collection is possible:
241+
242+ >>> for builder in builders:
243+ ... print builder
244+ http://api.launchpad.dev/devel/builders/bob
245+ http://api.launchpad.dev/devel/builders/frog
246+
247+An individual builder can be retrieved by name by using the getByName()
248+operation on "builders":
249+
250+ >>> bob = builders.getByName(name="bob")
251+
252+Each builder has a number of properties exposed:
253+
254+ >>> for attribute in bob.lp_attributes:
255+ ... print attribute
256+ self_link
257+ ...
258+ active
259+ builderok
260+ description
261+ failnotes
262+ failure_count
263+ manual
264+ name
265+ title
266+ url
267+ virtualized
268+ vm_host
269+
270+
271+Changing builder properties
272+===========================
273+
274+If an authorized person (usually a member of the buildd-admins team)
275+wishes to amend some property of a builder, the usual webservice 'lp_save'
276+operation is used.
277+
278+Our "bob" builder was retrieved using the no-priv user, and no-priv does not
279+have permission to save changes:
280+
281+ >>> print bob.active
282+ True
283+ >>> bob.active = False
284+ >>> bob.lp_save()
285+ Traceback (most recent call last):
286+ ...
287+ Unauthorized:...
288+
289+'cprov', who is a buildd-admin, is able to change the data:
290+
291+ >>> cprov_launchpad = launchpadlib_for(
292+ ... 'builders test', 'cprov', version='devel')
293+ >>> bob = cprov_launchpad.builders.getByName(name="bob")
294+ >>> bob.active = False
295+ >>> bob.lp_save()
296+