Merge ~cjwatson/launchpad:snap-base-arch into launchpad: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)
Reviewer Review Type Date Requested Status
Cristian Gonzalez 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://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/402532

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Looks good and well tested!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/database/schema/security.cfg b/database/schema/security.cfg
2index 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
45diff --git a/lib/lp/snappy/interfaces/snapbase.py b/lib/lp/snappy/interfaces/snapbase.py
46index 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(
108diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
109index 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))
168diff --git a/lib/lp/snappy/model/snapbase.py b/lib/lp/snappy/model/snapbase.py
169index 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):
248diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
249index 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(
396diff --git a/lib/lp/snappy/tests/test_snapbase.py b/lib/lp/snappy/tests/test_snapbase.py
397index 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()
537diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
538index 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:

Subscribers

People subscribed via source and target branches

to status/vote changes: