1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
|
= Builder View Classes and Pages =
>>> from zope.component import getMultiAdapter, getUtility
>>> from lp.buildmaster.interfaces.builder import IBuilderSet
>>> from lp.soyuz.interfaces.binarypackagebuild import (
... IBinaryPackageBuildSet)
>>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
>>> builder = getUtility(IBuilderSet).get(1)
Get a "mock" request:
>>> mock_form = {}
>>> request = LaunchpadTestRequest(form=mock_form)
Let's instantiate the view for +index:
>>> builder_view = getMultiAdapter((builder, request), name="+index")
>>> print builder_view.page_title
Builder ...Bob The Builder...
The BuilderView provides a helper for the text to use on the toggle
mode button.
>>> print builder_view.toggle_mode_text
Switch to manual-mode
== Builder history ==
Let's instantiate a view for +history:
>>> builder_view = getMultiAdapter((builder, request), name="+history")
>>> print builder_view.page_title
Build history
setupBuildList, build a batched list of build records and store it
in view.batch, also store the batch navigator in view.batchnav. it
simply returns None:
>>> builder_view.setupBuildList()
As expected we have a 'batched' list with the size requested in
mock_form:
>>> len(builder_view.batchnav.currentBatch())
5
== Builder edit ==
Let's instantiate the view for +edit and check that the correct title,
fields and actions are displayed:
This page requires launchpad.Edit permission and the builder is owned
by mark. we nee to log in as mark or any other member of admin team
>>> from canonical.launchpad.ftests import login
>>> login("foo.bar@canonical.com")
>>> builder_view = create_initialized_view(builder, name="+edit")
>>> print builder_view.page_title
Change details for builder ...Bob The Builder...
>>> for field_name in builder_view.field_names:
... print field_name
name
title
description
processor
url
manual
owner
virtualized
builderok
failnotes
vm_host
active
>>> for action in builder_view.actions:
... print action.label
Change
The BuilderEditView also has a next_url property for redirecting after
a successful form submission.
>>> print builder_view.next_url
http://launchpad.dev/builders/bob
The BuilderEditView can be used to update the relevant fields on the
builder.
>>> def print_builder_info(builder):
... print "%s: manual=%s, vm_host=%s." % (
... builder.name,
... builder.manual,
... builder.vm_host,
... )
>>> print_builder_info(builder)
bob: manual=False, vm_host=None.
>>> builder_view = create_initialized_view(
... builder, name="+edit", method="POST", form={
... 'field.name': u'biscoito',
... 'field.manual': 'on',
... 'field.vm_host': 'foobar-host.ppa',
... 'field.actions.update': 'Change',
... })
>>> print_builder_info(builder)
biscoito: manual=True, vm_host=foobar-host.ppa.
After editing a builder, a relevant notification is added to the view.
>>> for notification in builder_view.request.notifications:
... print notification.message
The builder "Bob The Builder" was updated successfully.
== Builders building private jobs ==
In order to restrict access to private PPA details in general, we also
need to be able to hide the fact that a builder is building a private
PPA job.
This feature is evaluated on the view layer, since it varies according
to the user who is trying to access a given content.
Before checking if it works as expected we will setup an environment
where builder 'Frog' is building a job from Celso's private PPA.
>>> from lp.buildmaster.interfaces.builder import IBuilderSet
>>> from lp.buildmaster.model.buildqueue import BuildQueue
>>> from lp.registry.interfaces.person import IPersonSet
>>> cprov = getUtility(IPersonSet).getByName("cprov")
>>> cprov_private_ppa = factory.makeArchive(
... owner=cprov, private=True)
SoyuzTestPublisher is used to make a new publication only in Celso's
private PPA.
>>> from lp.buildmaster.enums import BuildStatus
>>> from lp.soyuz.tests.test_publishing import (
... SoyuzTestPublisher)
>>> from lp.soyuz.enums import (
... PackagePublishingStatus)
>>> test_publisher = SoyuzTestPublisher()
>>> test_publisher.prepareBreezyAutotest()
>>> private_source_pub = test_publisher.getPubSource(
... status=PackagePublishingStatus.PUBLISHED,
... sourcename='privacy-test',
... archive=cprov_private_ppa)
>>> [private_build] = private_source_pub.createMissingBuilds()
Assign the build to the 'frog' builder:
>>> frog = getUtility(IBuilderSet)['frog']
>>> frog.builderok = True
>>> private_build.builder = frog
>>> private_job = private_build.buildqueue_record
>>> private_job.builder = frog
>>> private_job_id = private_job.id
>>> from canonical.database.sqlbase import flush_database_caches
>>> flush_database_caches()
At the content class level, all the information about the current job
is widely available:
* Frog is OK;
* Frog 'currentjob' exists;
* Frog has no 'failnotes';
>>> print frog.builderok
True
>>> build_set = getUtility(IBinaryPackageBuildSet)
>>> build = build_set.getByQueueEntry(frog.currentjob)
>>> print build.title
i386 build of privacy-test 666 in ubuntutest breezy-autotest RELEASE
>>> print frog.failnotes
None
Accessing the view for $builder/+index as a Foo Bar, which has
launchpad.View permission on the target archive of the 'currentjob',
all the 'private' information is exposed.
>>> import transaction
>>> transaction.commit()
>>> login("foo.bar@canonical.com")
>>> empty_request = LaunchpadTestRequest(form={})
>>> admin_view = getMultiAdapter((frog, empty_request), name="+index")
>>> print admin_view.context.builderok
True
>>> build = build_set.getByQueueEntry(admin_view.context.currentjob)
>>> print build.title
i386 build of privacy-test 666 in ubuntutest breezy-autotest RELEASE
>>> print admin_view.context.failnotes
None
>>> import datetime
>>> import pytz
>>> private_job.setDateStarted(
... datetime.datetime.now(pytz.UTC) - datetime.timedelta(10))
>>> print admin_view.current_build_duration
10 days...
Once the private job is gone, Frog 'real' details are exposed publicly
again.
>>> from storm.store import Store
>>> store = Store.of(frog)
>>> login("foo.bar@canonical.com")
>>> private_job = store.get(BuildQueue, private_job_id)
>>> private_job.builder = None
>>> frog.current_build_behavior = None
>>> login('no-priv@canonical.com')
>>> nopriv_view = getMultiAdapter((frog, empty_request), name="+index")
>>> login(ANONYMOUS)
== BuilderSet view ==
BuilderSetView offer a way to treat the currently registered builders
in categories. They are:
* 'Official distribution build machines': a group of builders capable
of building 'trusted' sources, ubuntu official packages. The
'non-vitualized' build-farm.
* 'PPA build machines': a group of builders capable of building
'untrusted' sources, PPA packages. The 'virtualized' build-farm.
>>> from lp.buildmaster.interfaces.builder import IBuilderSet
>>> builderset = getUtility(IBuilderSet)
>>> builderset_view = getMultiAdapter(
... (builderset, empty_request), name="+index")
In order to have a proper dataset for the tests we will populate the
builder table with several builders for different categories and
architectures.
>>> from lp.registry.interfaces.person import IPersonSet
>>> cprov = getUtility(IPersonSet).getByName('cprov')
>>> from lp.soyuz.model.processor import Processor
>>> i386 = Processor.selectOneBy(name='386')
>>> amd64 = Processor.selectOneBy(name='amd64')
>>> hppa = Processor.selectOneBy(name='hppa')
>>> a_builder = builderset.new(
... i386, url='http://hamburger', name="hamburger",
... title="The Hamburger Builder", description="Uhmmm", owner=cprov,
... virtualized=True)
>>> a_builder = builderset.new(
... hppa, url='http://cheese', name="cheese",
... title="The Cheese Builder", description="Uhmmm", owner=cprov,
... virtualized=True)
>>> a_builder = builderset.new(
... amd64, url='http://bacon', name="bacon",
... title="The Bacon Builder", description="Uhmmm", owner=cprov,
... virtualized=True)
>>> a_builder = builderset.new(
... i386, url='http://egg', name="egg",
... title="The Egg Builder", description="Uhmmm", owner=cprov,
... virtualized=False)
>>> a_builder = builderset.new(
... hppa, url='http://ham', name="ham",
... title="The Ham Builder", description="Uhmmm", owner=cprov,
... virtualized=False)
>>> a_builder = builderset.new(
... amd64, url='http://prosciuto', name="prosciuto",
... title="The Prosciuto Builder", description="Uhmmm", owner=cprov,
... virtualized=False)
Newly created builders will be in manual mode because we don't want
them going straight into the build farm until tested.
>>> a_builder.manual
True
The 'Other' builder category is a `BuilderCategory` class, which
contains the following attributes:
* title: the title that will be presented for this category in the UI;
* virtualized: wheter the category represents the virtualized or
non-virtualized build-farm;
* groups: a property that return all `BuilderGroup` instanced
available in this category ordered by processor name.
>>> builder_category = builderset_view.other_builders
>>> print builder_category
<...BuilderCategory ...>
>>> print builder_category.title
Official distributions build status
>>> print builder_category.virtualized
False
>>> print builder_category.groups[0]
<...BuilderGroup ...>
Similarly to what is done in the UI, we have a helper that prints the
grouped builders within a category in a easy manner.
>>> def print_category(category):
... for group in category.groups:
... print group.processor_name, \
... group.number_of_available_builders, \
... group.queue_size, \
... group.duration
>>> print_category(builder_category)
386 2 1 0:00:30
amd64 1 0 None
hppa 1 0 None
Each `BuilderGroup` contains the following attributes:
* processor_name: the `Processor` name of all builders in this group;
* number_of_available_builders: the number of builders available for
this processor.
* queue_size: the number of jobs wainting to be processed for one of
the builders in this group
* duration: estimated time that will be used to build all jobs in
queue (sum(job_duration)/number_of_available_builders)
>>> [i386_group, amd64_group, hppa_group] = builder_category.groups
>>> print i386_group.processor_name
386
>>> print i386_group.number_of_available_builders
2
>>> print i386_group.queue_size
1
>>> print i386_group.duration
0:00:30
The 'PPA' builder category is also available in BuilderSetView as a
`BuilderCategory`.
>>> builder_category = builderset_view.ppa_builders
>>> print builder_category.title
PPA build status
>>> print builder_category.virtualized
True
>>> print_category(builder_category)
386 2 1 0:00:30
amd64 1 0 None
hppa 1 0 None
We change the sampledata to create a pending build in for the 386
processor queue in the PPA category.
>>> import datetime
>>> from zope.security.proxy import removeSecurityProxy
>>> login('foo.bar@canonical.com')
>>> any_failed_build = cprov.archive.getBuildRecords(
... build_state=BuildStatus.FAILEDTOBUILD)[0]
>>> one_minute = datetime.timedelta(seconds=60)
>>> any_failed_build.retry()
>>> removeSecurityProxy(
... any_failed_build.buildqueue_record).estimated_duration = one_minute
>>> transaction.commit()
>>> login(ANONYMOUS)
Now the pending build is included in the right category and group.
>>> builderset_view = getMultiAdapter(
... (builderset, empty_request), name="+index")
>>> builder_category = builderset_view.ppa_builders
>>> print_category(builder_category)
386 2 2 0:01:00
amd64 1 0 None
hppa 1 0 None
|