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
=== modified file 'lib/lp/buildmaster/configure.zcml'
--- lib/lp/buildmaster/configure.zcml 2010-07-26 15:52:22 +0000
+++ lib/lp/buildmaster/configure.zcml 2010-11-19 16:16:49 +0000
@@ -6,6 +6,7 @@
6 xmlns="http://namespaces.zope.org/zope"6 xmlns="http://namespaces.zope.org/zope"
7 xmlns:browser="http://namespaces.zope.org/browser"7 xmlns:browser="http://namespaces.zope.org/browser"
8 xmlns:i18n="http://namespaces.zope.org/i18n"8 xmlns:i18n="http://namespaces.zope.org/i18n"
9 xmlns:webservice="http://namespaces.canonical.com/webservice"
9 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"10 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
10 i18n_domain="launchpad">11 i18n_domain="launchpad">
11 <include package=".browser"/>12 <include package=".browser"/>
@@ -114,5 +115,6 @@
114 interface="lp.buildmaster.interfaces.buildqueue.IBuildQueueSet"/>115 interface="lp.buildmaster.interfaces.buildqueue.IBuildQueueSet"/>
115 </securedutility>116 </securedutility>
116117
118 <webservice:register module="lp.buildmaster.interfaces.webservice" />
117119
118</configure>120</configure>
119121
=== modified file 'lib/lp/buildmaster/interfaces/builder.py'
--- lib/lp/buildmaster/interfaces/builder.py 2010-10-27 14:25:19 +0000
+++ lib/lp/buildmaster/interfaces/builder.py 2010-11-19 16:16:49 +0000
@@ -19,6 +19,16 @@
19 'ProtocolVersionMismatch',19 'ProtocolVersionMismatch',
20 ]20 ]
2121
22from lazr.restful.declarations import (
23 collection_default_content,
24 export_as_webservice_collection,
25 export_as_webservice_entry,
26 export_read_operation,
27 exported,
28 operation_parameters,
29 operation_returns_entry,
30 )
31
22from zope.interface import (32from zope.interface import (
23 Attribute,33 Attribute,
24 Interface,34 Interface,
@@ -38,6 +48,7 @@
38from lp.registry.interfaces.role import IHasOwner48from lp.registry.interfaces.role import IHasOwner
39from lp.services.fields import (49from lp.services.fields import (
40 Description,50 Description,
51 PersonChoice,
41 Title,52 Title,
42 )53 )
4354
@@ -92,71 +103,74 @@
92 machine status representation, including the field/properties:103 machine status representation, including the field/properties:
93 virtualized, builderok, status, failnotes and currentjob.104 virtualized, builderok, status, failnotes and currentjob.
94 """105 """
106 export_as_webservice_entry()
107
95 id = Attribute("Builder identifier")108 id = Attribute("Builder identifier")
109
96 processor = Choice(110 processor = Choice(
97 title=_('Processor'), required=True, vocabulary='Processor',111 title=_('Processor'), required=True, vocabulary='Processor',
98 description=_('Build Slave Processor, used to identify '112 description=_('Build Slave Processor, used to identify '
99 'which jobs can be built by this device.'))113 'which jobs can be built by this device.'))
100114
101 owner = Choice(115 owner = exported(PersonChoice(
102 title=_('Owner'), required=True, vocabulary='ValidOwner',116 title=_('Owner'), required=True, vocabulary='ValidOwner',
103 description=_('Builder owner, a Launchpad member which '117 description=_('Builder owner, a Launchpad member which '
104 'will be responsible for this device.'))118 'will be responsible for this device.')))
105119
106 url = TextLine(120 url = exported(TextLine(
107 title=_('URL'), required=True, constraint=builder_url_validator,121 title=_('URL'), required=True, constraint=builder_url_validator,
108 description=_('The URL to the build machine, used as a unique '122 description=_('The URL to the build machine, used as a unique '
109 'identifier. Includes protocol, host and port only, '123 'identifier. Includes protocol, host and port only, '
110 'e.g.: http://farm.com:8221/'))124 'e.g.: http://farm.com:8221/')))
111125
112 name = TextLine(126 name = exported(TextLine(
113 title=_('Name'), required=True, constraint=name_validator,127 title=_('Name'), required=True, constraint=name_validator,
114 description=_('Builder Slave Name used for reference proposes'))128 description=_('Builder Slave Name used for reference proposes')))
115129
116 title = Title(130 title = exported(Title(
117 title=_('Title'), required=True,131 title=_('Title'), required=True,
118 description=_('The builder slave title. Should be just a few words.'))132 description=_('The builder slave title. Should be just a few words.')))
119133
120 description = Description(134 description = exported(Description(
121 title=_('Description'), required=False,135 title=_('Description'), required=False,
122 description=_('The builder slave description, may be several '136 description=_('The builder slave description, may be several '
123 'paragraphs of text, giving the highlights and '137 'paragraphs of text, giving the highlights and '
124 'details.'))138 'details.')))
125139
126 virtualized = Bool(140 virtualized = exported(Bool(
127 title=_('Virtualized'), required=True, default=False,141 title=_('Virtualized'), required=True, default=False,
128 description=_('Whether or not the builder is a virtual Xen '142 description=_('Whether or not the builder is a virtual Xen '
129 'instance.'))143 'instance.')))
130144
131 manual = Bool(145 manual = exported(Bool(
132 title=_('Manual Mode'), required=False, default=False,146 title=_('Manual Mode'), required=False, default=False,
133 description=_('The auto-build system does not dispatch '147 description=_('The auto-build system does not dispatch '
134 'jobs automatically for slaves in manual mode.'))148 'jobs automatically for slaves in manual mode.')))
135149
136 builderok = Bool(150 builderok = exported(Bool(
137 title=_('Builder State OK'), required=True, default=True,151 title=_('Builder State OK'), required=True, default=True,
138 description=_('Whether or not the builder is ok'))152 description=_('Whether or not the builder is ok')))
139153
140 failnotes = Text(154 failnotes = exported(Text(
141 title=_('Failure Notes'), required=False,155 title=_('Failure Notes'), required=False,
142 description=_('The reason for a builder not being ok'))156 description=_('The reason for a builder not being ok')))
143157
144 vm_host = TextLine(158 vm_host = exported(TextLine(
145 title=_('Virtual Machine Host'), required=False,159 title=_('Virtual Machine Host'), required=False,
146 description=_('The machine hostname hosting the virtual '160 description=_('The machine hostname hosting the virtual '
147 'buildd-slave, e.g.: foobar-host.ppa'))161 'buildd-slave, e.g.: foobar-host.ppa')))
148162
149 active = Bool(163 active = exported(Bool(
150 title=_('Publicly Visible'), required=True, default=True,164 title=_('Publicly Visible'), required=True, default=True,
151 description=_('Whether or not to present the builder publicly.'))165 description=_('Whether or not to present the builder publicly.')))
152166
153 slave = Attribute("xmlrpclib.Server instance corresponding to builder.")167 slave = Attribute("xmlrpclib.Server instance corresponding to builder.")
154168
155 currentjob = Attribute("BuildQueue instance for job being processed.")169 currentjob = Attribute("BuildQueue instance for job being processed.")
156170
157 failure_count = Int(171 failure_count = exported(Int(
158 title=_('Failure Count'), required=False, default=0,172 title=_('Failure Count'), required=False, default=0,
159 description=_("Number of consecutive failures for this builder."))173 description=_("Number of consecutive failures for this builder.")))
160174
161 current_build_behavior = Field(175 current_build_behavior = Field(
162 title=u"The current behavior of the builder for the current job.",176 title=u"The current behavior of the builder for the current job.",
@@ -333,6 +347,7 @@
333 Methods on this interface should deal with the set of Builders:347 Methods on this interface should deal with the set of Builders:
334 methods that affect a single Builder should be on IBuilder.348 methods that affect a single Builder should be on IBuilder.
335 """349 """
350 export_as_webservice_collection(IBuilder)
336351
337 title = Attribute('Title')352 title = Attribute('Title')
338353
@@ -342,6 +357,13 @@
342 def __getitem__(name):357 def __getitem__(name):
343 """Retrieve a builder by name"""358 """Retrieve a builder by name"""
344359
360 @operation_parameters(
361 name=TextLine(title=_("Builder name"), required=True))
362 @operation_returns_entry(IBuilder)
363 @export_read_operation()
364 def getByName(name):
365 """Retrieve a builder by name"""
366
345 def new(processor, url, name, title, description, owner,367 def new(processor, url, name, title, description, owner,
346 active=True, virtualized=False, vm_host=None):368 active=True, virtualized=False, vm_host=None):
347 """Create a new Builder entry.369 """Create a new Builder entry.
@@ -360,6 +382,7 @@
360 def get(builder_id):382 def get(builder_id):
361 """Return the IBuilder with the given builderid."""383 """Return the IBuilder with the given builderid."""
362384
385 @collection_default_content()
363 def getBuilders():386 def getBuilders():
364 """Return all active configured builders."""387 """Return all active configured builders."""
365388
366389
=== added file 'lib/lp/buildmaster/interfaces/webservice.py'
--- lib/lp/buildmaster/interfaces/webservice.py 1970-01-01 00:00:00 +0000
+++ lib/lp/buildmaster/interfaces/webservice.py 2010-11-19 16:16:49 +0000
@@ -0,0 +1,20 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""All the interfaces that are exposed through the webservice.
5
6There is a declaration in ZCML somewhere that looks like:
7 <webservice:register module="lp.soyuz.interfaces.webservice" />
8
9which tells `lazr.restful` that it should look for webservice exports here.
10"""
11
12__all__ = [
13 'IBuilder',
14 'IBuilderSet',
15 ]
16
17from lp.buildmaster.interfaces.builder import (
18 IBuilder,
19 IBuilderSet,
20 )
021
=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py 2010-11-17 16:22:45 +0000
+++ lib/lp/buildmaster/model/builder.py 2010-11-19 16:16:49 +0000
@@ -832,12 +832,16 @@
832 def __iter__(self):832 def __iter__(self):
833 return iter(Builder.select())833 return iter(Builder.select())
834834
835 def __getitem__(self, name):835 def getByName(self, name):
836 """See IBuilderSet."""
836 try:837 try:
837 return Builder.selectOneBy(name=name)838 return Builder.selectOneBy(name=name)
838 except SQLObjectNotFound:839 except SQLObjectNotFound:
839 raise NotFoundError(name)840 raise NotFoundError(name)
840841
842 def __getitem__(self, name):
843 return self.getByName(name)
844
841 def new(self, processor, url, name, title, description, owner,845 def new(self, processor, url, name, title, description, owner,
842 active=True, virtualized=False, vm_host=None, manual=True):846 active=True, virtualized=False, vm_host=None, manual=True):
843 """See IBuilderSet."""847 """See IBuilderSet."""
844848
=== added file 'lib/lp/soyuz/stories/webservice/xx-builders.txt'
--- lib/lp/soyuz/stories/webservice/xx-builders.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/stories/webservice/xx-builders.txt 2010-11-19 16:16:49 +0000
@@ -0,0 +1,68 @@
1========
2Builders
3========
4
5The webservice exposes a top-level collection called "builders" which
6contains all the registered builders in the Launchpad build farm.
7
8 >>> nopriv_launchpad = launchpadlib_for(
9 ... 'builders test', 'no-priv', version='devel')
10 >>> builders = nopriv_launchpad.builders
11
12Iterating over the collection is possible:
13
14 >>> for builder in builders:
15 ... print builder
16 http://api.launchpad.dev/devel/builders/bob
17 http://api.launchpad.dev/devel/builders/frog
18
19An individual builder can be retrieved by name by using the getByName()
20operation on "builders":
21
22 >>> bob = builders.getByName(name="bob")
23
24Each builder has a number of properties exposed:
25
26 >>> for attribute in bob.lp_attributes:
27 ... print attribute
28 self_link
29 ...
30 active
31 builderok
32 description
33 failnotes
34 failure_count
35 manual
36 name
37 title
38 url
39 virtualized
40 vm_host
41
42
43Changing builder properties
44===========================
45
46If an authorized person (usually a member of the buildd-admins team)
47wishes to amend some property of a builder, the usual webservice 'lp_save'
48operation is used.
49
50Our "bob" builder was retrieved using the no-priv user, and no-priv does not
51have permission to save changes:
52
53 >>> print bob.active
54 True
55 >>> bob.active = False
56 >>> bob.lp_save()
57 Traceback (most recent call last):
58 ...
59 Unauthorized:...
60
61'cprov', who is a buildd-admin, is able to change the data:
62
63 >>> cprov_launchpad = launchpadlib_for(
64 ... 'builders test', 'cprov', version='devel')
65 >>> bob = cprov_launchpad.builders.getByName(name="bob")
66 >>> bob.active = False
67 >>> bob.lp_save()
68