Merge ~cjwatson/launchpad:snap-base-arch into launchpad:master
- Git
- lp:~cjwatson/launchpad
- snap-base-arch
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | c1421e9cae13dce4ba5d57d06859022c3007e52e |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:snap-base-arch |
Merge into: | launchpad:master |
Diff against target: |
558 lines (+256/-31) 7 files modified
database/schema/security.cfg (+5/-0) lib/lp/snappy/interfaces/snapbase.py (+20/-1) lib/lp/snappy/model/snap.py (+11/-8) lib/lp/snappy/model/snapbase.py (+43/-1) lib/lp/snappy/tests/test_snap.py (+59/-19) lib/lp/snappy/tests/test_snapbase.py (+116/-0) lib/lp/testing/factory.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Cristian Gonzalez (community) | Approve | ||
Review via email: mp+402533@code.launchpad.net |
Commit message
Add and use SnapBaseArch
Description of the change
A snap base may not support all the architectures supported by its underlying distroseries: in particular, core20 does not support i386. Add a `SnapBaseArch` table along the same lines as the existing `SnapArch` (but with a simpler `setProcessors`, since only registry experts and admins can edit `SnapBase` anyway), and intersect the architectures supported by the snap base with other existing constraints when dispatching snap builds.
DB MP: https:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/database/schema/security.cfg b/database/schema/security.cfg |
2 | index c3aba65..b13d4a8 100644 |
3 | --- a/database/schema/security.cfg |
4 | +++ b/database/schema/security.cfg |
5 | @@ -301,6 +301,7 @@ public.sharingjob = SELECT, INSERT, UPDATE, DELETE |
6 | public.snap = SELECT, INSERT, UPDATE, DELETE |
7 | public.snaparch = SELECT, INSERT, DELETE |
8 | public.snapbase = SELECT, INSERT, UPDATE, DELETE |
9 | +public.snapbasearch = SELECT, INSERT, DELETE |
10 | public.snapbuild = SELECT, INSERT, UPDATE, DELETE |
11 | public.snapbuildjob = SELECT, INSERT, UPDATE, DELETE |
12 | public.snapfile = SELECT, INSERT, UPDATE, DELETE |
13 | @@ -842,6 +843,7 @@ public.product = SELECT |
14 | public.snap = SELECT, UPDATE |
15 | public.snaparch = SELECT |
16 | public.snapbase = SELECT |
17 | +public.snapbasearch = SELECT |
18 | public.snapbuild = SELECT, INSERT |
19 | public.snapbuildjob = SELECT |
20 | public.sourcepackagename = SELECT |
21 | @@ -1032,6 +1034,7 @@ public.seriessourcepackagebranch = SELECT |
22 | public.snap = SELECT |
23 | public.snaparch = SELECT |
24 | public.snapbase = SELECT |
25 | +public.snapbasearch = SELECT |
26 | public.snapbuild = SELECT, UPDATE |
27 | public.snapbuildjob = SELECT, INSERT |
28 | public.snapfile = SELECT |
29 | @@ -1490,6 +1493,7 @@ public.signedcodeofconduct = SELECT |
30 | public.snap = SELECT, UPDATE |
31 | public.snaparch = SELECT |
32 | public.snapbase = SELECT |
33 | +public.snapbasearch = SELECT |
34 | public.snapbuild = SELECT, UPDATE |
35 | public.snapbuildjob = SELECT, INSERT, UPDATE |
36 | public.snapfile = SELECT, INSERT, UPDATE |
37 | @@ -2719,6 +2723,7 @@ public.product = SELECT |
38 | public.snap = SELECT, UPDATE |
39 | public.snaparch = SELECT |
40 | public.snapbase = SELECT |
41 | +public.snapbasearch = SELECT |
42 | public.snapbuild = SELECT, INSERT, UPDATE |
43 | public.snapbuildjob = SELECT, UPDATE |
44 | public.snapfile = SELECT |
45 | diff --git a/lib/lp/snappy/interfaces/snapbase.py b/lib/lp/snappy/interfaces/snapbase.py |
46 | index a5fdf90..21e4729 100644 |
47 | --- a/lib/lp/snappy/interfaces/snapbase.py |
48 | +++ b/lib/lp/snappy/interfaces/snapbase.py |
49 | @@ -44,12 +44,14 @@ from zope.schema import ( |
50 | Datetime, |
51 | Dict, |
52 | Int, |
53 | + List, |
54 | TextLine, |
55 | ) |
56 | |
57 | from lp import _ |
58 | from lp.app.errors import NameLookupFailed |
59 | from lp.app.validators.name import name_validator |
60 | +from lp.buildmaster.interfaces.processor import IProcessor |
61 | from lp.registry.interfaces.distroseries import IDistroSeries |
62 | from lp.services.fields import ( |
63 | ContentNameField, |
64 | @@ -125,6 +127,12 @@ class ISnapBaseView(Interface): |
65 | could not be found. |
66 | """ |
67 | |
68 | + processors = exported(CollectionField( |
69 | + title=_("Processors"), |
70 | + description=_("The architectures that the snap base supports."), |
71 | + value_type=Reference(schema=IProcessor), |
72 | + readonly=True)) |
73 | + |
74 | |
75 | class ISnapBaseEditableAttributes(Interface): |
76 | """`ISnapBase` attributes that can be edited. |
77 | @@ -197,6 +205,14 @@ class ISnapBaseEdit(Interface): |
78 | :param dependency: an `IArchive`. |
79 | """ |
80 | |
81 | + @operation_parameters( |
82 | + processors=List( |
83 | + value_type=Reference(schema=IProcessor), required=True)) |
84 | + @export_write_operation() |
85 | + @operation_for_version("devel") |
86 | + def setProcessors(processors): |
87 | + """Set the architectures that the snap base supports.""" |
88 | + |
89 | @export_destructor_operation() |
90 | @operation_for_version("devel") |
91 | def destroySelf(): |
92 | @@ -218,11 +234,14 @@ class ISnapBaseSetEdit(Interface): |
93 | """`ISnapBaseSet` methods that require launchpad.Edit permission.""" |
94 | |
95 | @call_with(registrant=REQUEST_USER) |
96 | + @operation_parameters( |
97 | + processors=List( |
98 | + value_type=Reference(schema=IProcessor), required=False)) |
99 | @export_factory_operation( |
100 | ISnapBase, ["name", "display_name", "distro_series", "build_channels"]) |
101 | @operation_for_version("devel") |
102 | def new(registrant, name, display_name, distro_series, build_channels, |
103 | - date_created=None): |
104 | + processors=None, date_created=None): |
105 | """Create an `ISnapBase`.""" |
106 | |
107 | @operation_parameters( |
108 | diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py |
109 | index 1a108b0..4dc26e5 100644 |
110 | --- a/lib/lp/snappy/model/snap.py |
111 | +++ b/lib/lp/snappy/model/snap.py |
112 | @@ -612,7 +612,7 @@ class Snap(Storm, WebhookTargetMixin): |
113 | |
114 | processors = property(_getProcessors, setProcessors) |
115 | |
116 | - def _isBuildableArchitectureAllowed(self, das): |
117 | + def _isBuildableArchitectureAllowed(self, das, snap_base=None): |
118 | """Check whether we may build for a buildable `DistroArchSeries`. |
119 | |
120 | The caller is assumed to have already checked that a suitable chroot |
121 | @@ -624,20 +624,21 @@ class Snap(Storm, WebhookTargetMixin): |
122 | and das.processor in self.processors |
123 | and ( |
124 | das.processor.supports_virtualized |
125 | - or not self.require_virtualized)) |
126 | + or not self.require_virtualized) |
127 | + and (snap_base is None or das.processor in snap_base.processors)) |
128 | |
129 | - def _isArchitectureAllowed(self, das, pocket): |
130 | + def _isArchitectureAllowed(self, das, pocket, snap_base=None): |
131 | return ( |
132 | das.getChroot(pocket=pocket) is not None |
133 | - and self._isBuildableArchitectureAllowed(das)) |
134 | + and self._isBuildableArchitectureAllowed(das, snap_base=snap_base)) |
135 | |
136 | - def getAllowedArchitectures(self, distro_series=None): |
137 | + def getAllowedArchitectures(self, distro_series=None, snap_base=None): |
138 | """See `ISnap`.""" |
139 | if distro_series is None: |
140 | distro_series = self.distro_series |
141 | return [ |
142 | das for das in distro_series.buildable_architectures |
143 | - if self._isBuildableArchitectureAllowed(das)] |
144 | + if self._isBuildableArchitectureAllowed(das, snap_base=snap_base)] |
145 | |
146 | @property |
147 | def store_distro_series(self): |
148 | @@ -779,7 +780,8 @@ class Snap(Storm, WebhookTargetMixin): |
149 | snap_base=None, channels=None, build_request=None): |
150 | """See `ISnap`.""" |
151 | self._checkRequestBuild(requester, archive) |
152 | - if not self._isArchitectureAllowed(distro_arch_series, pocket): |
153 | + if not self._isArchitectureAllowed( |
154 | + distro_arch_series, pocket, snap_base=snap_base): |
155 | raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket) |
156 | |
157 | if not channels: |
158 | @@ -898,7 +900,8 @@ class Snap(Storm, WebhookTargetMixin): |
159 | # minimise confusion. |
160 | supported_arches = OrderedDict( |
161 | (das.architecturetag, das) for das in sorted( |
162 | - self.getAllowedArchitectures(distro_series), |
163 | + self.getAllowedArchitectures( |
164 | + distro_series, snap_base=snap_base), |
165 | key=attrgetter("processor.id")) |
166 | if (architectures is None or |
167 | das.architecturetag in architectures)) |
168 | diff --git a/lib/lp/snappy/model/snapbase.py b/lib/lp/snappy/model/snapbase.py |
169 | index 8916c61..c2f2a76 100644 |
170 | --- a/lib/lp/snappy/model/snapbase.py |
171 | +++ b/lib/lp/snappy/model/snapbase.py |
172 | @@ -27,6 +27,7 @@ from zope.interface import implementer |
173 | from zope.security.proxy import removeSecurityProxy |
174 | |
175 | from lp.app.errors import NotFoundError |
176 | +from lp.buildmaster.model.processor import Processor |
177 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
178 | from lp.registry.model.person import Person |
179 | from lp.services.database.constants import DEFAULT |
180 | @@ -85,6 +86,30 @@ class SnapBase(Storm): |
181 | self.date_created = date_created |
182 | self.is_default = False |
183 | |
184 | + def _getProcessors(self): |
185 | + return list(Store.of(self).find( |
186 | + Processor, |
187 | + Processor.id == SnapBaseArch.processor_id, |
188 | + SnapBaseArch.snap_base == self)) |
189 | + |
190 | + def setProcessors(self, processors): |
191 | + """See `ISnapBase`.""" |
192 | + enablements = dict(Store.of(self).find( |
193 | + (Processor, SnapBaseArch), |
194 | + Processor.id == SnapBaseArch.processor_id, |
195 | + SnapBaseArch.snap_base == self)) |
196 | + for proc in enablements: |
197 | + if proc not in processors: |
198 | + Store.of(self).remove(enablements[proc]) |
199 | + for proc in processors: |
200 | + if proc not in self.processors: |
201 | + snap_base_arch = SnapBaseArch() |
202 | + snap_base_arch.snap_base = self |
203 | + snap_base_arch.processor = proc |
204 | + Store.of(self).add(snap_base_arch) |
205 | + |
206 | + processors = property(_getProcessors, setProcessors) |
207 | + |
208 | @property |
209 | def dependencies(self): |
210 | """See `ISnapBase`.""" |
211 | @@ -145,18 +170,35 @@ class SnapBase(Storm): |
212 | Store.of(self).remove(self) |
213 | |
214 | |
215 | +class SnapBaseArch(Storm): |
216 | + """Link table to back `SnapArch.processors`.""" |
217 | + |
218 | + __storm_table__ = "SnapBaseArch" |
219 | + __storm_primary__ = ("snap_base_id", "processor_id") |
220 | + |
221 | + snap_base_id = Int(name="snap_base", allow_none=False) |
222 | + snap_base = Reference(snap_base_id, "SnapBase.id") |
223 | + |
224 | + processor_id = Int(name="processor", allow_none=False) |
225 | + processor = Reference(processor_id, "Processor.id") |
226 | + |
227 | + |
228 | @implementer(ISnapBaseSet) |
229 | class SnapBaseSet: |
230 | """See `ISnapBaseSet`.""" |
231 | |
232 | def new(self, registrant, name, display_name, distro_series, |
233 | - build_channels, date_created=DEFAULT): |
234 | + build_channels, processors=None, date_created=DEFAULT): |
235 | """See `ISnapBaseSet`.""" |
236 | store = IMasterStore(SnapBase) |
237 | snap_base = SnapBase( |
238 | registrant, name, display_name, distro_series, build_channels, |
239 | date_created=date_created) |
240 | store.add(snap_base) |
241 | + if processors is None: |
242 | + processors = [ |
243 | + das.processor for das in distro_series.enabled_architectures] |
244 | + snap_base.setProcessors(processors) |
245 | return snap_base |
246 | |
247 | def __iter__(self): |
248 | diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py |
249 | index be338d7..50d1269 100644 |
250 | --- a/lib/lp/snappy/tests/test_snap.py |
251 | +++ b/lib/lp/snappy/tests/test_snap.py |
252 | @@ -300,7 +300,8 @@ class TestSnap(TestCaseWithFactory): |
253 | snap = self.factory.makeSnap( |
254 | distroseries=distroarchseries.distroseries, processors=[processor]) |
255 | with admin_logged_in(): |
256 | - snap_base = self.factory.makeSnapBase() |
257 | + snap_base = self.factory.makeSnapBase( |
258 | + distro_series=distroarchseries.distroseries) |
259 | build = snap.requestBuild( |
260 | snap.owner, snap.distro_series.main_archive, distroarchseries, |
261 | PackagePublishingPocket.UPDATES, snap_base=snap_base) |
262 | @@ -745,20 +746,21 @@ class TestSnap(TestCaseWithFactory): |
263 | # base, requestBuildsFromJob requests builds for the appropriate |
264 | # distroseries for the base. |
265 | self.useFixture(GitHostingFixture(blob="base: test-base\n")) |
266 | - with admin_logged_in(): |
267 | - snap_base = self.factory.makeSnapBase( |
268 | - name="test-base", |
269 | - build_channels={"snapcraft": "stable/launchpad-buildd"}) |
270 | - self.factory.makeSnapBase() |
271 | + distroseries = self.factory.makeDistroSeries() |
272 | for arch_tag in ("mips64el", "riscv64"): |
273 | self.makeBuildableDistroArchSeries( |
274 | - distroseries=snap_base.distro_series, architecturetag=arch_tag, |
275 | + distroseries=distroseries, architecturetag=arch_tag, |
276 | processor=self.factory.makeProcessor( |
277 | name=arch_tag, supports_virtualized=True)) |
278 | + with admin_logged_in(): |
279 | + snap_base = self.factory.makeSnapBase( |
280 | + name="test-base", distro_series=distroseries, |
281 | + build_channels={"snapcraft": "stable/launchpad-buildd"}) |
282 | + self.factory.makeSnapBase() |
283 | snap = self.factory.makeSnap( |
284 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) |
285 | job = getUtility(ISnapRequestBuildsJobSource).create( |
286 | - snap, snap.owner.teamowner, snap_base.distro_series.main_archive, |
287 | + snap, snap.owner.teamowner, distroseries.main_archive, |
288 | PackagePublishingPocket.RELEASE, None) |
289 | self.assertEqual( |
290 | get_transaction_timestamp(IStore(snap)), job.date_created) |
291 | @@ -769,27 +771,29 @@ class TestSnap(TestCaseWithFactory): |
292 | build_request=job.build_request) |
293 | self.assertRequestedBuildsMatch( |
294 | builds, job, ["mips64el", "riscv64"], snap_base, |
295 | - snap_base.build_channels, distro_series=snap_base.distro_series) |
296 | + snap_base.build_channels, distro_series=distroseries) |
297 | |
298 | def test_requestBuildsFromJob_no_distroseries_no_explicit_base(self): |
299 | # If the snap doesn't specify a distroseries and has no explicit |
300 | # base, requestBuildsFromJob requests builds for the appropriate |
301 | # distroseries for the default base. |
302 | self.useFixture(GitHostingFixture(blob="name: foo\n")) |
303 | + distroseries = self.factory.makeDistroSeries() |
304 | + for arch_tag in ("mips64el", "riscv64"): |
305 | + self.makeBuildableDistroArchSeries( |
306 | + distroseries=distroseries, architecturetag=arch_tag, |
307 | + processor=self.factory.makeProcessor( |
308 | + name=arch_tag, supports_virtualized=True)) |
309 | with admin_logged_in(): |
310 | snap_base = self.factory.makeSnapBase( |
311 | + distro_series=distroseries, |
312 | build_channels={"snapcraft": "stable/launchpad-buildd"}) |
313 | getUtility(ISnapBaseSet).setDefault(snap_base) |
314 | self.factory.makeSnapBase() |
315 | - for arch_tag in ("mips64el", "riscv64"): |
316 | - self.makeBuildableDistroArchSeries( |
317 | - distroseries=snap_base.distro_series, architecturetag=arch_tag, |
318 | - processor=self.factory.makeProcessor( |
319 | - name=arch_tag, supports_virtualized=True)) |
320 | snap = self.factory.makeSnap( |
321 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) |
322 | job = getUtility(ISnapRequestBuildsJobSource).create( |
323 | - snap, snap.owner.teamowner, snap_base.distro_series.main_archive, |
324 | + snap, snap.owner.teamowner, distroseries.main_archive, |
325 | PackagePublishingPocket.RELEASE, None) |
326 | self.assertEqual( |
327 | get_transaction_timestamp(IStore(snap)), job.date_created) |
328 | @@ -800,7 +804,7 @@ class TestSnap(TestCaseWithFactory): |
329 | build_request=job.build_request) |
330 | self.assertRequestedBuildsMatch( |
331 | builds, job, ["mips64el", "riscv64"], snap_base, |
332 | - snap_base.build_channels, distro_series=snap_base.distro_series) |
333 | + snap_base.build_channels, distro_series=distroseries) |
334 | |
335 | def test_requestBuildsFromJob_no_distroseries_no_default_base(self): |
336 | # If the snap doesn't specify a distroseries and has an explicit |
337 | @@ -821,6 +825,40 @@ class TestSnap(TestCaseWithFactory): |
338 | job.requester, job.archive, job.pocket, |
339 | build_request=job.build_request) |
340 | |
341 | + def test_requestBuildsFromJob_snap_base_architectures(self): |
342 | + # requestBuildsFromJob intersects the architectures supported by the |
343 | + # snap base with any other constraints. |
344 | + self.useFixture(GitHostingFixture(blob="base: test-base\n")) |
345 | + processors = [ |
346 | + self.factory.makeProcessor(supports_virtualized=True) |
347 | + for _ in range(3)] |
348 | + distroseries = self.factory.makeDistroSeries() |
349 | + for processor in processors: |
350 | + self.makeBuildableDistroArchSeries( |
351 | + distroseries=distroseries, architecturetag=processor.name, |
352 | + processor=processor) |
353 | + with admin_logged_in(): |
354 | + snap_base = self.factory.makeSnapBase( |
355 | + name="test-base", distro_series=distroseries, |
356 | + build_channels={"snapcraft": "stable/launchpad-buildd"}, |
357 | + processors=processors[:2]) |
358 | + snap = self.factory.makeSnap( |
359 | + distroseries=None, git_ref=self.factory.makeGitRefs()[0]) |
360 | + job = getUtility(ISnapRequestBuildsJobSource).create( |
361 | + snap, snap.owner.teamowner, snap_base.distro_series.main_archive, |
362 | + PackagePublishingPocket.RELEASE, None) |
363 | + self.assertEqual( |
364 | + get_transaction_timestamp(IStore(snap)), job.date_created) |
365 | + transaction.commit() |
366 | + with person_logged_in(job.requester): |
367 | + builds = snap.requestBuildsFromJob( |
368 | + job.requester, job.archive, job.pocket, |
369 | + build_request=job.build_request) |
370 | + self.assertRequestedBuildsMatch( |
371 | + builds, job, [processor.name for processor in processors[:2]], |
372 | + snap_base, snap_base.build_channels, |
373 | + distro_series=snap_base.distro_series) |
374 | + |
375 | def test_requestBuildsFromJob_unsupported_remote(self): |
376 | # If the snap is based on an external Git repository from which we |
377 | # don't support fetching blobs, requestBuildsFromJob falls back to |
378 | @@ -2544,12 +2582,14 @@ class TestSnapSet(TestCaseWithFactory): |
379 | def test_makeAutoBuilds_infers_distroseries(self): |
380 | # ISnapSet.makeAutoBuilds can infer the series of a snap from the base |
381 | # specified in its snapcraft.yaml. |
382 | - with admin_logged_in(): |
383 | - snap_base = self.factory.makeSnapBase(name="core20") |
384 | + distroseries = self.factory.makeDistroSeries() |
385 | das = self.makeBuildableDistroArchSeries( |
386 | - distroseries=snap_base.distro_series, architecturetag='riscv64', |
387 | + distroseries=distroseries, architecturetag='riscv64', |
388 | processor=self.factory.makeProcessor( |
389 | name='riscv64', supports_virtualized=True)) |
390 | + with admin_logged_in(): |
391 | + snap_base = self.factory.makeSnapBase( |
392 | + name="core20", distro_series=distroseries) |
393 | [git_ref] = self.factory.makeGitRefs() |
394 | owner = self.factory.makePerson() |
395 | snap = self.factory.makeSnap( |
396 | diff --git a/lib/lp/snappy/tests/test_snapbase.py b/lib/lp/snappy/tests/test_snapbase.py |
397 | index 67754d2..c528f40 100644 |
398 | --- a/lib/lp/snappy/tests/test_snapbase.py |
399 | +++ b/lib/lp/snappy/tests/test_snapbase.py |
400 | @@ -31,6 +31,7 @@ from lp.snappy.interfaces.snapbase import ( |
401 | ) |
402 | from lp.soyuz.interfaces.component import IComponentSet |
403 | from lp.testing import ( |
404 | + admin_logged_in, |
405 | api_url, |
406 | celebrity_logged_in, |
407 | logout, |
408 | @@ -78,6 +79,56 @@ class TestSnapBase(TestCaseWithFactory): |
409 | self.assertRaises(CannotDeleteSnapBase, snap_base.destroySelf) |
410 | |
411 | |
412 | +class TestSnapBaseProcessors(TestCaseWithFactory): |
413 | + |
414 | + layer = ZopelessDatabaseLayer |
415 | + |
416 | + def setUp(self): |
417 | + super(TestSnapBaseProcessors, self).setUp(user="foo.bar@canonical.com") |
418 | + self.unrestricted_procs = [ |
419 | + self.factory.makeProcessor() for _ in range(3)] |
420 | + self.restricted_procs = [ |
421 | + self.factory.makeProcessor(restricted=True, build_by_default=False) |
422 | + for _ in range(2)] |
423 | + self.procs = self.unrestricted_procs + self.restricted_procs |
424 | + self.factory.makeProcessor() |
425 | + self.distroseries = self.factory.makeDistroSeries() |
426 | + for processor in self.procs: |
427 | + self.factory.makeDistroArchSeries( |
428 | + distroseries=self.distroseries, architecturetag=processor.name, |
429 | + processor=processor) |
430 | + |
431 | + def test_new_default_processors(self): |
432 | + # SnapBaseSet.new creates a SnapBaseArch for each available |
433 | + # Processor for the corresponding series. |
434 | + snap_base = getUtility(ISnapBaseSet).new( |
435 | + registrant=self.factory.makePerson(), |
436 | + name=self.factory.getUniqueUnicode(), |
437 | + display_name=self.factory.getUniqueUnicode(), |
438 | + distro_series=self.distroseries, build_channels={}) |
439 | + self.assertContentEqual(self.procs, snap_base.processors) |
440 | + |
441 | + def test_new_override_processors(self): |
442 | + # SnapBaseSet.new can be given a custom set of processors. |
443 | + snap_base = getUtility(ISnapBaseSet).new( |
444 | + registrant=self.factory.makePerson(), |
445 | + name=self.factory.getUniqueUnicode(), |
446 | + display_name=self.factory.getUniqueUnicode(), |
447 | + distro_series=self.distroseries, build_channels={}, |
448 | + processors=self.procs[:2]) |
449 | + self.assertContentEqual(self.procs[:2], snap_base.processors) |
450 | + |
451 | + def test_set(self): |
452 | + # The property remembers its value correctly. |
453 | + snap_base = self.factory.makeSnapBase() |
454 | + snap_base.setProcessors(self.restricted_procs) |
455 | + self.assertContentEqual(self.restricted_procs, snap_base.processors) |
456 | + snap_base.setProcessors(self.procs) |
457 | + self.assertContentEqual(self.procs, snap_base.processors) |
458 | + snap_base.setProcessors([]) |
459 | + self.assertContentEqual([], snap_base.processors) |
460 | + |
461 | + |
462 | class TestSnapBaseSet(TestCaseWithFactory): |
463 | |
464 | layer = ZopelessDatabaseLayer |
465 | @@ -368,6 +419,71 @@ class TestSnapBaseWebservice(TestCaseWithFactory): |
466 | with person_logged_in(person): |
467 | self.assertEqual([], list(snap_base.dependencies)) |
468 | |
469 | + def setUpProcessors(self): |
470 | + self.unrestricted_procs = [ |
471 | + self.factory.makeProcessor() for _ in range(3)] |
472 | + self.unrestricted_proc_names = [ |
473 | + processor.name for processor in self.unrestricted_procs] |
474 | + self.restricted_procs = [ |
475 | + self.factory.makeProcessor(restricted=True, build_by_default=False) |
476 | + for _ in range(2)] |
477 | + self.restricted_proc_names = [ |
478 | + processor.name for processor in self.restricted_procs] |
479 | + self.procs = self.unrestricted_procs + self.restricted_procs |
480 | + self.factory.makeProcessor() |
481 | + self.distroseries = self.factory.makeDistroSeries() |
482 | + for processor in self.procs: |
483 | + self.factory.makeDistroArchSeries( |
484 | + distroseries=self.distroseries, architecturetag=processor.name, |
485 | + processor=processor) |
486 | + |
487 | + def setProcessors(self, user, snap_base_url, names): |
488 | + ws = webservice_for_person( |
489 | + user, permission=OAuthPermission.WRITE_PUBLIC) |
490 | + return ws.named_post( |
491 | + snap_base_url, "setProcessors", |
492 | + processors=["/+processors/%s" % name for name in names], |
493 | + api_version="devel") |
494 | + |
495 | + def assertProcessors(self, user, snap_base_url, names): |
496 | + body = webservice_for_person(user).get( |
497 | + snap_base_url + "/processors", api_version="devel").jsonBody() |
498 | + self.assertContentEqual( |
499 | + names, [entry["name"] for entry in body["entries"]]) |
500 | + |
501 | + def test_setProcessors_admin(self): |
502 | + """An admin can change the supported processor set.""" |
503 | + self.setUpProcessors() |
504 | + with admin_logged_in(): |
505 | + snap_base = self.factory.makeSnapBase( |
506 | + distro_series=self.distroseries, |
507 | + processors=self.unrestricted_procs) |
508 | + snap_base_url = api_url(snap_base) |
509 | + admin = self.factory.makeAdministrator() |
510 | + self.assertProcessors( |
511 | + admin, snap_base_url, self.unrestricted_proc_names) |
512 | + |
513 | + response = self.setProcessors( |
514 | + admin, snap_base_url, |
515 | + [self.unrestricted_proc_names[0], self.restricted_proc_names[0]]) |
516 | + self.assertEqual(200, response.status) |
517 | + self.assertProcessors( |
518 | + admin, snap_base_url, |
519 | + [self.unrestricted_proc_names[0], self.restricted_proc_names[0]]) |
520 | + |
521 | + def test_setProcessors_non_admin_forbidden(self): |
522 | + """Only admins and registry experts can call setProcessors.""" |
523 | + self.setUpProcessors() |
524 | + with admin_logged_in(): |
525 | + snap_base = self.factory.makeSnapBase( |
526 | + distro_series=self.distroseries) |
527 | + snap_base_url = api_url(snap_base) |
528 | + person = self.factory.makePerson() |
529 | + |
530 | + response = self.setProcessors( |
531 | + person, snap_base_url, [self.unrestricted_proc_names[0]]) |
532 | + self.assertEqual(401, response.status) |
533 | + |
534 | def test_collection(self): |
535 | # lp.snap_bases is a collection of all SnapBases. |
536 | person = self.factory.makePerson() |
537 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py |
538 | index e95e83d..e65a596 100644 |
539 | --- a/lib/lp/testing/factory.py |
540 | +++ b/lib/lp/testing/factory.py |
541 | @@ -4911,7 +4911,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): |
542 | return snappy_series |
543 | |
544 | def makeSnapBase(self, registrant=None, name=None, display_name=None, |
545 | - distro_series=None, build_channels=None, |
546 | + distro_series=None, build_channels=None, processors=None, |
547 | date_created=DEFAULT): |
548 | """Make a new SnapBase.""" |
549 | if registrant is None: |
550 | @@ -4927,7 +4927,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): |
551 | build_channels = {u"snapcraft": u"stable"} |
552 | return getUtility(ISnapBaseSet).new( |
553 | registrant, name, display_name, distro_series, build_channels, |
554 | - date_created=date_created) |
555 | + processors=processors, date_created=date_created) |
556 | |
557 | def makeOCIProjectName(self, name=None): |
558 | if name is None: |
Looks good and well tested!