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 | 301 | public.snap = SELECT, INSERT, UPDATE, DELETE | 301 | public.snap = SELECT, INSERT, UPDATE, DELETE |
7 | 302 | public.snaparch = SELECT, INSERT, DELETE | 302 | public.snaparch = SELECT, INSERT, DELETE |
8 | 303 | public.snapbase = SELECT, INSERT, UPDATE, DELETE | 303 | public.snapbase = SELECT, INSERT, UPDATE, DELETE |
9 | 304 | public.snapbasearch = SELECT, INSERT, DELETE | ||
10 | 304 | public.snapbuild = SELECT, INSERT, UPDATE, DELETE | 305 | public.snapbuild = SELECT, INSERT, UPDATE, DELETE |
11 | 305 | public.snapbuildjob = SELECT, INSERT, UPDATE, DELETE | 306 | public.snapbuildjob = SELECT, INSERT, UPDATE, DELETE |
12 | 306 | public.snapfile = SELECT, INSERT, UPDATE, DELETE | 307 | public.snapfile = SELECT, INSERT, UPDATE, DELETE |
13 | @@ -842,6 +843,7 @@ public.product = SELECT | |||
14 | 842 | public.snap = SELECT, UPDATE | 843 | public.snap = SELECT, UPDATE |
15 | 843 | public.snaparch = SELECT | 844 | public.snaparch = SELECT |
16 | 844 | public.snapbase = SELECT | 845 | public.snapbase = SELECT |
17 | 846 | public.snapbasearch = SELECT | ||
18 | 845 | public.snapbuild = SELECT, INSERT | 847 | public.snapbuild = SELECT, INSERT |
19 | 846 | public.snapbuildjob = SELECT | 848 | public.snapbuildjob = SELECT |
20 | 847 | public.sourcepackagename = SELECT | 849 | public.sourcepackagename = SELECT |
21 | @@ -1032,6 +1034,7 @@ public.seriessourcepackagebranch = SELECT | |||
22 | 1032 | public.snap = SELECT | 1034 | public.snap = SELECT |
23 | 1033 | public.snaparch = SELECT | 1035 | public.snaparch = SELECT |
24 | 1034 | public.snapbase = SELECT | 1036 | public.snapbase = SELECT |
25 | 1037 | public.snapbasearch = SELECT | ||
26 | 1035 | public.snapbuild = SELECT, UPDATE | 1038 | public.snapbuild = SELECT, UPDATE |
27 | 1036 | public.snapbuildjob = SELECT, INSERT | 1039 | public.snapbuildjob = SELECT, INSERT |
28 | 1037 | public.snapfile = SELECT | 1040 | public.snapfile = SELECT |
29 | @@ -1490,6 +1493,7 @@ public.signedcodeofconduct = SELECT | |||
30 | 1490 | public.snap = SELECT, UPDATE | 1493 | public.snap = SELECT, UPDATE |
31 | 1491 | public.snaparch = SELECT | 1494 | public.snaparch = SELECT |
32 | 1492 | public.snapbase = SELECT | 1495 | public.snapbase = SELECT |
33 | 1496 | public.snapbasearch = SELECT | ||
34 | 1493 | public.snapbuild = SELECT, UPDATE | 1497 | public.snapbuild = SELECT, UPDATE |
35 | 1494 | public.snapbuildjob = SELECT, INSERT, UPDATE | 1498 | public.snapbuildjob = SELECT, INSERT, UPDATE |
36 | 1495 | public.snapfile = SELECT, INSERT, UPDATE | 1499 | public.snapfile = SELECT, INSERT, UPDATE |
37 | @@ -2719,6 +2723,7 @@ public.product = SELECT | |||
38 | 2719 | public.snap = SELECT, UPDATE | 2723 | public.snap = SELECT, UPDATE |
39 | 2720 | public.snaparch = SELECT | 2724 | public.snaparch = SELECT |
40 | 2721 | public.snapbase = SELECT | 2725 | public.snapbase = SELECT |
41 | 2726 | public.snapbasearch = SELECT | ||
42 | 2722 | public.snapbuild = SELECT, INSERT, UPDATE | 2727 | public.snapbuild = SELECT, INSERT, UPDATE |
43 | 2723 | public.snapbuildjob = SELECT, UPDATE | 2728 | public.snapbuildjob = SELECT, UPDATE |
44 | 2724 | public.snapfile = SELECT | 2729 | 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 | 44 | Datetime, | 44 | Datetime, |
51 | 45 | Dict, | 45 | Dict, |
52 | 46 | Int, | 46 | Int, |
53 | 47 | List, | ||
54 | 47 | TextLine, | 48 | TextLine, |
55 | 48 | ) | 49 | ) |
56 | 49 | 50 | ||
57 | 50 | from lp import _ | 51 | from lp import _ |
58 | 51 | from lp.app.errors import NameLookupFailed | 52 | from lp.app.errors import NameLookupFailed |
59 | 52 | from lp.app.validators.name import name_validator | 53 | from lp.app.validators.name import name_validator |
60 | 54 | from lp.buildmaster.interfaces.processor import IProcessor | ||
61 | 53 | from lp.registry.interfaces.distroseries import IDistroSeries | 55 | from lp.registry.interfaces.distroseries import IDistroSeries |
62 | 54 | from lp.services.fields import ( | 56 | from lp.services.fields import ( |
63 | 55 | ContentNameField, | 57 | ContentNameField, |
64 | @@ -125,6 +127,12 @@ class ISnapBaseView(Interface): | |||
65 | 125 | could not be found. | 127 | could not be found. |
66 | 126 | """ | 128 | """ |
67 | 127 | 129 | ||
68 | 130 | processors = exported(CollectionField( | ||
69 | 131 | title=_("Processors"), | ||
70 | 132 | description=_("The architectures that the snap base supports."), | ||
71 | 133 | value_type=Reference(schema=IProcessor), | ||
72 | 134 | readonly=True)) | ||
73 | 135 | |||
74 | 128 | 136 | ||
75 | 129 | class ISnapBaseEditableAttributes(Interface): | 137 | class ISnapBaseEditableAttributes(Interface): |
76 | 130 | """`ISnapBase` attributes that can be edited. | 138 | """`ISnapBase` attributes that can be edited. |
77 | @@ -197,6 +205,14 @@ class ISnapBaseEdit(Interface): | |||
78 | 197 | :param dependency: an `IArchive`. | 205 | :param dependency: an `IArchive`. |
79 | 198 | """ | 206 | """ |
80 | 199 | 207 | ||
81 | 208 | @operation_parameters( | ||
82 | 209 | processors=List( | ||
83 | 210 | value_type=Reference(schema=IProcessor), required=True)) | ||
84 | 211 | @export_write_operation() | ||
85 | 212 | @operation_for_version("devel") | ||
86 | 213 | def setProcessors(processors): | ||
87 | 214 | """Set the architectures that the snap base supports.""" | ||
88 | 215 | |||
89 | 200 | @export_destructor_operation() | 216 | @export_destructor_operation() |
90 | 201 | @operation_for_version("devel") | 217 | @operation_for_version("devel") |
91 | 202 | def destroySelf(): | 218 | def destroySelf(): |
92 | @@ -218,11 +234,14 @@ class ISnapBaseSetEdit(Interface): | |||
93 | 218 | """`ISnapBaseSet` methods that require launchpad.Edit permission.""" | 234 | """`ISnapBaseSet` methods that require launchpad.Edit permission.""" |
94 | 219 | 235 | ||
95 | 220 | @call_with(registrant=REQUEST_USER) | 236 | @call_with(registrant=REQUEST_USER) |
96 | 237 | @operation_parameters( | ||
97 | 238 | processors=List( | ||
98 | 239 | value_type=Reference(schema=IProcessor), required=False)) | ||
99 | 221 | @export_factory_operation( | 240 | @export_factory_operation( |
100 | 222 | ISnapBase, ["name", "display_name", "distro_series", "build_channels"]) | 241 | ISnapBase, ["name", "display_name", "distro_series", "build_channels"]) |
101 | 223 | @operation_for_version("devel") | 242 | @operation_for_version("devel") |
102 | 224 | def new(registrant, name, display_name, distro_series, build_channels, | 243 | def new(registrant, name, display_name, distro_series, build_channels, |
104 | 225 | date_created=None): | 244 | processors=None, date_created=None): |
105 | 226 | """Create an `ISnapBase`.""" | 245 | """Create an `ISnapBase`.""" |
106 | 227 | 246 | ||
107 | 228 | @operation_parameters( | 247 | @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 | 612 | 612 | ||
114 | 613 | processors = property(_getProcessors, setProcessors) | 613 | processors = property(_getProcessors, setProcessors) |
115 | 614 | 614 | ||
117 | 615 | def _isBuildableArchitectureAllowed(self, das): | 615 | def _isBuildableArchitectureAllowed(self, das, snap_base=None): |
118 | 616 | """Check whether we may build for a buildable `DistroArchSeries`. | 616 | """Check whether we may build for a buildable `DistroArchSeries`. |
119 | 617 | 617 | ||
120 | 618 | The caller is assumed to have already checked that a suitable chroot | 618 | The caller is assumed to have already checked that a suitable chroot |
121 | @@ -624,20 +624,21 @@ class Snap(Storm, WebhookTargetMixin): | |||
122 | 624 | and das.processor in self.processors | 624 | and das.processor in self.processors |
123 | 625 | and ( | 625 | and ( |
124 | 626 | das.processor.supports_virtualized | 626 | das.processor.supports_virtualized |
126 | 627 | or not self.require_virtualized)) | 627 | or not self.require_virtualized) |
127 | 628 | and (snap_base is None or das.processor in snap_base.processors)) | ||
128 | 628 | 629 | ||
130 | 629 | def _isArchitectureAllowed(self, das, pocket): | 630 | def _isArchitectureAllowed(self, das, pocket, snap_base=None): |
131 | 630 | return ( | 631 | return ( |
132 | 631 | das.getChroot(pocket=pocket) is not None | 632 | das.getChroot(pocket=pocket) is not None |
134 | 632 | and self._isBuildableArchitectureAllowed(das)) | 633 | and self._isBuildableArchitectureAllowed(das, snap_base=snap_base)) |
135 | 633 | 634 | ||
137 | 634 | def getAllowedArchitectures(self, distro_series=None): | 635 | def getAllowedArchitectures(self, distro_series=None, snap_base=None): |
138 | 635 | """See `ISnap`.""" | 636 | """See `ISnap`.""" |
139 | 636 | if distro_series is None: | 637 | if distro_series is None: |
140 | 637 | distro_series = self.distro_series | 638 | distro_series = self.distro_series |
141 | 638 | return [ | 639 | return [ |
142 | 639 | das for das in distro_series.buildable_architectures | 640 | das for das in distro_series.buildable_architectures |
144 | 640 | if self._isBuildableArchitectureAllowed(das)] | 641 | if self._isBuildableArchitectureAllowed(das, snap_base=snap_base)] |
145 | 641 | 642 | ||
146 | 642 | @property | 643 | @property |
147 | 643 | def store_distro_series(self): | 644 | def store_distro_series(self): |
148 | @@ -779,7 +780,8 @@ class Snap(Storm, WebhookTargetMixin): | |||
149 | 779 | snap_base=None, channels=None, build_request=None): | 780 | snap_base=None, channels=None, build_request=None): |
150 | 780 | """See `ISnap`.""" | 781 | """See `ISnap`.""" |
151 | 781 | self._checkRequestBuild(requester, archive) | 782 | self._checkRequestBuild(requester, archive) |
153 | 782 | if not self._isArchitectureAllowed(distro_arch_series, pocket): | 783 | if not self._isArchitectureAllowed( |
154 | 784 | distro_arch_series, pocket, snap_base=snap_base): | ||
155 | 783 | raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket) | 785 | raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket) |
156 | 784 | 786 | ||
157 | 785 | if not channels: | 787 | if not channels: |
158 | @@ -898,7 +900,8 @@ class Snap(Storm, WebhookTargetMixin): | |||
159 | 898 | # minimise confusion. | 900 | # minimise confusion. |
160 | 899 | supported_arches = OrderedDict( | 901 | supported_arches = OrderedDict( |
161 | 900 | (das.architecturetag, das) for das in sorted( | 902 | (das.architecturetag, das) for das in sorted( |
163 | 901 | self.getAllowedArchitectures(distro_series), | 903 | self.getAllowedArchitectures( |
164 | 904 | distro_series, snap_base=snap_base), | ||
165 | 902 | key=attrgetter("processor.id")) | 905 | key=attrgetter("processor.id")) |
166 | 903 | if (architectures is None or | 906 | if (architectures is None or |
167 | 904 | das.architecturetag in architectures)) | 907 | 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 | 27 | from zope.security.proxy import removeSecurityProxy | 27 | from zope.security.proxy import removeSecurityProxy |
174 | 28 | 28 | ||
175 | 29 | from lp.app.errors import NotFoundError | 29 | from lp.app.errors import NotFoundError |
176 | 30 | from lp.buildmaster.model.processor import Processor | ||
177 | 30 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 31 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
178 | 31 | from lp.registry.model.person import Person | 32 | from lp.registry.model.person import Person |
179 | 32 | from lp.services.database.constants import DEFAULT | 33 | from lp.services.database.constants import DEFAULT |
180 | @@ -85,6 +86,30 @@ class SnapBase(Storm): | |||
181 | 85 | self.date_created = date_created | 86 | self.date_created = date_created |
182 | 86 | self.is_default = False | 87 | self.is_default = False |
183 | 87 | 88 | ||
184 | 89 | def _getProcessors(self): | ||
185 | 90 | return list(Store.of(self).find( | ||
186 | 91 | Processor, | ||
187 | 92 | Processor.id == SnapBaseArch.processor_id, | ||
188 | 93 | SnapBaseArch.snap_base == self)) | ||
189 | 94 | |||
190 | 95 | def setProcessors(self, processors): | ||
191 | 96 | """See `ISnapBase`.""" | ||
192 | 97 | enablements = dict(Store.of(self).find( | ||
193 | 98 | (Processor, SnapBaseArch), | ||
194 | 99 | Processor.id == SnapBaseArch.processor_id, | ||
195 | 100 | SnapBaseArch.snap_base == self)) | ||
196 | 101 | for proc in enablements: | ||
197 | 102 | if proc not in processors: | ||
198 | 103 | Store.of(self).remove(enablements[proc]) | ||
199 | 104 | for proc in processors: | ||
200 | 105 | if proc not in self.processors: | ||
201 | 106 | snap_base_arch = SnapBaseArch() | ||
202 | 107 | snap_base_arch.snap_base = self | ||
203 | 108 | snap_base_arch.processor = proc | ||
204 | 109 | Store.of(self).add(snap_base_arch) | ||
205 | 110 | |||
206 | 111 | processors = property(_getProcessors, setProcessors) | ||
207 | 112 | |||
208 | 88 | @property | 113 | @property |
209 | 89 | def dependencies(self): | 114 | def dependencies(self): |
210 | 90 | """See `ISnapBase`.""" | 115 | """See `ISnapBase`.""" |
211 | @@ -145,18 +170,35 @@ class SnapBase(Storm): | |||
212 | 145 | Store.of(self).remove(self) | 170 | Store.of(self).remove(self) |
213 | 146 | 171 | ||
214 | 147 | 172 | ||
215 | 173 | class SnapBaseArch(Storm): | ||
216 | 174 | """Link table to back `SnapArch.processors`.""" | ||
217 | 175 | |||
218 | 176 | __storm_table__ = "SnapBaseArch" | ||
219 | 177 | __storm_primary__ = ("snap_base_id", "processor_id") | ||
220 | 178 | |||
221 | 179 | snap_base_id = Int(name="snap_base", allow_none=False) | ||
222 | 180 | snap_base = Reference(snap_base_id, "SnapBase.id") | ||
223 | 181 | |||
224 | 182 | processor_id = Int(name="processor", allow_none=False) | ||
225 | 183 | processor = Reference(processor_id, "Processor.id") | ||
226 | 184 | |||
227 | 185 | |||
228 | 148 | @implementer(ISnapBaseSet) | 186 | @implementer(ISnapBaseSet) |
229 | 149 | class SnapBaseSet: | 187 | class SnapBaseSet: |
230 | 150 | """See `ISnapBaseSet`.""" | 188 | """See `ISnapBaseSet`.""" |
231 | 151 | 189 | ||
232 | 152 | def new(self, registrant, name, display_name, distro_series, | 190 | def new(self, registrant, name, display_name, distro_series, |
234 | 153 | build_channels, date_created=DEFAULT): | 191 | build_channels, processors=None, date_created=DEFAULT): |
235 | 154 | """See `ISnapBaseSet`.""" | 192 | """See `ISnapBaseSet`.""" |
236 | 155 | store = IMasterStore(SnapBase) | 193 | store = IMasterStore(SnapBase) |
237 | 156 | snap_base = SnapBase( | 194 | snap_base = SnapBase( |
238 | 157 | registrant, name, display_name, distro_series, build_channels, | 195 | registrant, name, display_name, distro_series, build_channels, |
239 | 158 | date_created=date_created) | 196 | date_created=date_created) |
240 | 159 | store.add(snap_base) | 197 | store.add(snap_base) |
241 | 198 | if processors is None: | ||
242 | 199 | processors = [ | ||
243 | 200 | das.processor for das in distro_series.enabled_architectures] | ||
244 | 201 | snap_base.setProcessors(processors) | ||
245 | 160 | return snap_base | 202 | return snap_base |
246 | 161 | 203 | ||
247 | 162 | def __iter__(self): | 204 | 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 | 300 | snap = self.factory.makeSnap( | 300 | snap = self.factory.makeSnap( |
254 | 301 | distroseries=distroarchseries.distroseries, processors=[processor]) | 301 | distroseries=distroarchseries.distroseries, processors=[processor]) |
255 | 302 | with admin_logged_in(): | 302 | with admin_logged_in(): |
257 | 303 | snap_base = self.factory.makeSnapBase() | 303 | snap_base = self.factory.makeSnapBase( |
258 | 304 | distro_series=distroarchseries.distroseries) | ||
259 | 304 | build = snap.requestBuild( | 305 | build = snap.requestBuild( |
260 | 305 | snap.owner, snap.distro_series.main_archive, distroarchseries, | 306 | snap.owner, snap.distro_series.main_archive, distroarchseries, |
261 | 306 | PackagePublishingPocket.UPDATES, snap_base=snap_base) | 307 | PackagePublishingPocket.UPDATES, snap_base=snap_base) |
262 | @@ -745,20 +746,21 @@ class TestSnap(TestCaseWithFactory): | |||
263 | 745 | # base, requestBuildsFromJob requests builds for the appropriate | 746 | # base, requestBuildsFromJob requests builds for the appropriate |
264 | 746 | # distroseries for the base. | 747 | # distroseries for the base. |
265 | 747 | self.useFixture(GitHostingFixture(blob="base: test-base\n")) | 748 | self.useFixture(GitHostingFixture(blob="base: test-base\n")) |
271 | 748 | with admin_logged_in(): | 749 | distroseries = self.factory.makeDistroSeries() |
267 | 749 | snap_base = self.factory.makeSnapBase( | ||
268 | 750 | name="test-base", | ||
269 | 751 | build_channels={"snapcraft": "stable/launchpad-buildd"}) | ||
270 | 752 | self.factory.makeSnapBase() | ||
272 | 753 | for arch_tag in ("mips64el", "riscv64"): | 750 | for arch_tag in ("mips64el", "riscv64"): |
273 | 754 | self.makeBuildableDistroArchSeries( | 751 | self.makeBuildableDistroArchSeries( |
275 | 755 | distroseries=snap_base.distro_series, architecturetag=arch_tag, | 752 | distroseries=distroseries, architecturetag=arch_tag, |
276 | 756 | processor=self.factory.makeProcessor( | 753 | processor=self.factory.makeProcessor( |
277 | 757 | name=arch_tag, supports_virtualized=True)) | 754 | name=arch_tag, supports_virtualized=True)) |
278 | 755 | with admin_logged_in(): | ||
279 | 756 | snap_base = self.factory.makeSnapBase( | ||
280 | 757 | name="test-base", distro_series=distroseries, | ||
281 | 758 | build_channels={"snapcraft": "stable/launchpad-buildd"}) | ||
282 | 759 | self.factory.makeSnapBase() | ||
283 | 758 | snap = self.factory.makeSnap( | 760 | snap = self.factory.makeSnap( |
284 | 759 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) | 761 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) |
285 | 760 | job = getUtility(ISnapRequestBuildsJobSource).create( | 762 | job = getUtility(ISnapRequestBuildsJobSource).create( |
287 | 761 | snap, snap.owner.teamowner, snap_base.distro_series.main_archive, | 763 | snap, snap.owner.teamowner, distroseries.main_archive, |
288 | 762 | PackagePublishingPocket.RELEASE, None) | 764 | PackagePublishingPocket.RELEASE, None) |
289 | 763 | self.assertEqual( | 765 | self.assertEqual( |
290 | 764 | get_transaction_timestamp(IStore(snap)), job.date_created) | 766 | get_transaction_timestamp(IStore(snap)), job.date_created) |
291 | @@ -769,27 +771,29 @@ class TestSnap(TestCaseWithFactory): | |||
292 | 769 | build_request=job.build_request) | 771 | build_request=job.build_request) |
293 | 770 | self.assertRequestedBuildsMatch( | 772 | self.assertRequestedBuildsMatch( |
294 | 771 | builds, job, ["mips64el", "riscv64"], snap_base, | 773 | builds, job, ["mips64el", "riscv64"], snap_base, |
296 | 772 | snap_base.build_channels, distro_series=snap_base.distro_series) | 774 | snap_base.build_channels, distro_series=distroseries) |
297 | 773 | 775 | ||
298 | 774 | def test_requestBuildsFromJob_no_distroseries_no_explicit_base(self): | 776 | def test_requestBuildsFromJob_no_distroseries_no_explicit_base(self): |
299 | 775 | # If the snap doesn't specify a distroseries and has no explicit | 777 | # If the snap doesn't specify a distroseries and has no explicit |
300 | 776 | # base, requestBuildsFromJob requests builds for the appropriate | 778 | # base, requestBuildsFromJob requests builds for the appropriate |
301 | 777 | # distroseries for the default base. | 779 | # distroseries for the default base. |
302 | 778 | self.useFixture(GitHostingFixture(blob="name: foo\n")) | 780 | self.useFixture(GitHostingFixture(blob="name: foo\n")) |
303 | 781 | distroseries = self.factory.makeDistroSeries() | ||
304 | 782 | for arch_tag in ("mips64el", "riscv64"): | ||
305 | 783 | self.makeBuildableDistroArchSeries( | ||
306 | 784 | distroseries=distroseries, architecturetag=arch_tag, | ||
307 | 785 | processor=self.factory.makeProcessor( | ||
308 | 786 | name=arch_tag, supports_virtualized=True)) | ||
309 | 779 | with admin_logged_in(): | 787 | with admin_logged_in(): |
310 | 780 | snap_base = self.factory.makeSnapBase( | 788 | snap_base = self.factory.makeSnapBase( |
311 | 789 | distro_series=distroseries, | ||
312 | 781 | build_channels={"snapcraft": "stable/launchpad-buildd"}) | 790 | build_channels={"snapcraft": "stable/launchpad-buildd"}) |
313 | 782 | getUtility(ISnapBaseSet).setDefault(snap_base) | 791 | getUtility(ISnapBaseSet).setDefault(snap_base) |
314 | 783 | self.factory.makeSnapBase() | 792 | self.factory.makeSnapBase() |
315 | 784 | for arch_tag in ("mips64el", "riscv64"): | ||
316 | 785 | self.makeBuildableDistroArchSeries( | ||
317 | 786 | distroseries=snap_base.distro_series, architecturetag=arch_tag, | ||
318 | 787 | processor=self.factory.makeProcessor( | ||
319 | 788 | name=arch_tag, supports_virtualized=True)) | ||
320 | 789 | snap = self.factory.makeSnap( | 793 | snap = self.factory.makeSnap( |
321 | 790 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) | 794 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) |
322 | 791 | job = getUtility(ISnapRequestBuildsJobSource).create( | 795 | job = getUtility(ISnapRequestBuildsJobSource).create( |
324 | 792 | snap, snap.owner.teamowner, snap_base.distro_series.main_archive, | 796 | snap, snap.owner.teamowner, distroseries.main_archive, |
325 | 793 | PackagePublishingPocket.RELEASE, None) | 797 | PackagePublishingPocket.RELEASE, None) |
326 | 794 | self.assertEqual( | 798 | self.assertEqual( |
327 | 795 | get_transaction_timestamp(IStore(snap)), job.date_created) | 799 | get_transaction_timestamp(IStore(snap)), job.date_created) |
328 | @@ -800,7 +804,7 @@ class TestSnap(TestCaseWithFactory): | |||
329 | 800 | build_request=job.build_request) | 804 | build_request=job.build_request) |
330 | 801 | self.assertRequestedBuildsMatch( | 805 | self.assertRequestedBuildsMatch( |
331 | 802 | builds, job, ["mips64el", "riscv64"], snap_base, | 806 | builds, job, ["mips64el", "riscv64"], snap_base, |
333 | 803 | snap_base.build_channels, distro_series=snap_base.distro_series) | 807 | snap_base.build_channels, distro_series=distroseries) |
334 | 804 | 808 | ||
335 | 805 | def test_requestBuildsFromJob_no_distroseries_no_default_base(self): | 809 | def test_requestBuildsFromJob_no_distroseries_no_default_base(self): |
336 | 806 | # If the snap doesn't specify a distroseries and has an explicit | 810 | # If the snap doesn't specify a distroseries and has an explicit |
337 | @@ -821,6 +825,40 @@ class TestSnap(TestCaseWithFactory): | |||
338 | 821 | job.requester, job.archive, job.pocket, | 825 | job.requester, job.archive, job.pocket, |
339 | 822 | build_request=job.build_request) | 826 | build_request=job.build_request) |
340 | 823 | 827 | ||
341 | 828 | def test_requestBuildsFromJob_snap_base_architectures(self): | ||
342 | 829 | # requestBuildsFromJob intersects the architectures supported by the | ||
343 | 830 | # snap base with any other constraints. | ||
344 | 831 | self.useFixture(GitHostingFixture(blob="base: test-base\n")) | ||
345 | 832 | processors = [ | ||
346 | 833 | self.factory.makeProcessor(supports_virtualized=True) | ||
347 | 834 | for _ in range(3)] | ||
348 | 835 | distroseries = self.factory.makeDistroSeries() | ||
349 | 836 | for processor in processors: | ||
350 | 837 | self.makeBuildableDistroArchSeries( | ||
351 | 838 | distroseries=distroseries, architecturetag=processor.name, | ||
352 | 839 | processor=processor) | ||
353 | 840 | with admin_logged_in(): | ||
354 | 841 | snap_base = self.factory.makeSnapBase( | ||
355 | 842 | name="test-base", distro_series=distroseries, | ||
356 | 843 | build_channels={"snapcraft": "stable/launchpad-buildd"}, | ||
357 | 844 | processors=processors[:2]) | ||
358 | 845 | snap = self.factory.makeSnap( | ||
359 | 846 | distroseries=None, git_ref=self.factory.makeGitRefs()[0]) | ||
360 | 847 | job = getUtility(ISnapRequestBuildsJobSource).create( | ||
361 | 848 | snap, snap.owner.teamowner, snap_base.distro_series.main_archive, | ||
362 | 849 | PackagePublishingPocket.RELEASE, None) | ||
363 | 850 | self.assertEqual( | ||
364 | 851 | get_transaction_timestamp(IStore(snap)), job.date_created) | ||
365 | 852 | transaction.commit() | ||
366 | 853 | with person_logged_in(job.requester): | ||
367 | 854 | builds = snap.requestBuildsFromJob( | ||
368 | 855 | job.requester, job.archive, job.pocket, | ||
369 | 856 | build_request=job.build_request) | ||
370 | 857 | self.assertRequestedBuildsMatch( | ||
371 | 858 | builds, job, [processor.name for processor in processors[:2]], | ||
372 | 859 | snap_base, snap_base.build_channels, | ||
373 | 860 | distro_series=snap_base.distro_series) | ||
374 | 861 | |||
375 | 824 | def test_requestBuildsFromJob_unsupported_remote(self): | 862 | def test_requestBuildsFromJob_unsupported_remote(self): |
376 | 825 | # If the snap is based on an external Git repository from which we | 863 | # If the snap is based on an external Git repository from which we |
377 | 826 | # don't support fetching blobs, requestBuildsFromJob falls back to | 864 | # don't support fetching blobs, requestBuildsFromJob falls back to |
378 | @@ -2544,12 +2582,14 @@ class TestSnapSet(TestCaseWithFactory): | |||
379 | 2544 | def test_makeAutoBuilds_infers_distroseries(self): | 2582 | def test_makeAutoBuilds_infers_distroseries(self): |
380 | 2545 | # ISnapSet.makeAutoBuilds can infer the series of a snap from the base | 2583 | # ISnapSet.makeAutoBuilds can infer the series of a snap from the base |
381 | 2546 | # specified in its snapcraft.yaml. | 2584 | # specified in its snapcraft.yaml. |
384 | 2547 | with admin_logged_in(): | 2585 | distroseries = self.factory.makeDistroSeries() |
383 | 2548 | snap_base = self.factory.makeSnapBase(name="core20") | ||
385 | 2549 | das = self.makeBuildableDistroArchSeries( | 2586 | das = self.makeBuildableDistroArchSeries( |
387 | 2550 | distroseries=snap_base.distro_series, architecturetag='riscv64', | 2587 | distroseries=distroseries, architecturetag='riscv64', |
388 | 2551 | processor=self.factory.makeProcessor( | 2588 | processor=self.factory.makeProcessor( |
389 | 2552 | name='riscv64', supports_virtualized=True)) | 2589 | name='riscv64', supports_virtualized=True)) |
390 | 2590 | with admin_logged_in(): | ||
391 | 2591 | snap_base = self.factory.makeSnapBase( | ||
392 | 2592 | name="core20", distro_series=distroseries) | ||
393 | 2553 | [git_ref] = self.factory.makeGitRefs() | 2593 | [git_ref] = self.factory.makeGitRefs() |
394 | 2554 | owner = self.factory.makePerson() | 2594 | owner = self.factory.makePerson() |
395 | 2555 | snap = self.factory.makeSnap( | 2595 | 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 | 31 | ) | 31 | ) |
402 | 32 | from lp.soyuz.interfaces.component import IComponentSet | 32 | from lp.soyuz.interfaces.component import IComponentSet |
403 | 33 | from lp.testing import ( | 33 | from lp.testing import ( |
404 | 34 | admin_logged_in, | ||
405 | 34 | api_url, | 35 | api_url, |
406 | 35 | celebrity_logged_in, | 36 | celebrity_logged_in, |
407 | 36 | logout, | 37 | logout, |
408 | @@ -78,6 +79,56 @@ class TestSnapBase(TestCaseWithFactory): | |||
409 | 78 | self.assertRaises(CannotDeleteSnapBase, snap_base.destroySelf) | 79 | self.assertRaises(CannotDeleteSnapBase, snap_base.destroySelf) |
410 | 79 | 80 | ||
411 | 80 | 81 | ||
412 | 82 | class TestSnapBaseProcessors(TestCaseWithFactory): | ||
413 | 83 | |||
414 | 84 | layer = ZopelessDatabaseLayer | ||
415 | 85 | |||
416 | 86 | def setUp(self): | ||
417 | 87 | super(TestSnapBaseProcessors, self).setUp(user="foo.bar@canonical.com") | ||
418 | 88 | self.unrestricted_procs = [ | ||
419 | 89 | self.factory.makeProcessor() for _ in range(3)] | ||
420 | 90 | self.restricted_procs = [ | ||
421 | 91 | self.factory.makeProcessor(restricted=True, build_by_default=False) | ||
422 | 92 | for _ in range(2)] | ||
423 | 93 | self.procs = self.unrestricted_procs + self.restricted_procs | ||
424 | 94 | self.factory.makeProcessor() | ||
425 | 95 | self.distroseries = self.factory.makeDistroSeries() | ||
426 | 96 | for processor in self.procs: | ||
427 | 97 | self.factory.makeDistroArchSeries( | ||
428 | 98 | distroseries=self.distroseries, architecturetag=processor.name, | ||
429 | 99 | processor=processor) | ||
430 | 100 | |||
431 | 101 | def test_new_default_processors(self): | ||
432 | 102 | # SnapBaseSet.new creates a SnapBaseArch for each available | ||
433 | 103 | # Processor for the corresponding series. | ||
434 | 104 | snap_base = getUtility(ISnapBaseSet).new( | ||
435 | 105 | registrant=self.factory.makePerson(), | ||
436 | 106 | name=self.factory.getUniqueUnicode(), | ||
437 | 107 | display_name=self.factory.getUniqueUnicode(), | ||
438 | 108 | distro_series=self.distroseries, build_channels={}) | ||
439 | 109 | self.assertContentEqual(self.procs, snap_base.processors) | ||
440 | 110 | |||
441 | 111 | def test_new_override_processors(self): | ||
442 | 112 | # SnapBaseSet.new can be given a custom set of processors. | ||
443 | 113 | snap_base = getUtility(ISnapBaseSet).new( | ||
444 | 114 | registrant=self.factory.makePerson(), | ||
445 | 115 | name=self.factory.getUniqueUnicode(), | ||
446 | 116 | display_name=self.factory.getUniqueUnicode(), | ||
447 | 117 | distro_series=self.distroseries, build_channels={}, | ||
448 | 118 | processors=self.procs[:2]) | ||
449 | 119 | self.assertContentEqual(self.procs[:2], snap_base.processors) | ||
450 | 120 | |||
451 | 121 | def test_set(self): | ||
452 | 122 | # The property remembers its value correctly. | ||
453 | 123 | snap_base = self.factory.makeSnapBase() | ||
454 | 124 | snap_base.setProcessors(self.restricted_procs) | ||
455 | 125 | self.assertContentEqual(self.restricted_procs, snap_base.processors) | ||
456 | 126 | snap_base.setProcessors(self.procs) | ||
457 | 127 | self.assertContentEqual(self.procs, snap_base.processors) | ||
458 | 128 | snap_base.setProcessors([]) | ||
459 | 129 | self.assertContentEqual([], snap_base.processors) | ||
460 | 130 | |||
461 | 131 | |||
462 | 81 | class TestSnapBaseSet(TestCaseWithFactory): | 132 | class TestSnapBaseSet(TestCaseWithFactory): |
463 | 82 | 133 | ||
464 | 83 | layer = ZopelessDatabaseLayer | 134 | layer = ZopelessDatabaseLayer |
465 | @@ -368,6 +419,71 @@ class TestSnapBaseWebservice(TestCaseWithFactory): | |||
466 | 368 | with person_logged_in(person): | 419 | with person_logged_in(person): |
467 | 369 | self.assertEqual([], list(snap_base.dependencies)) | 420 | self.assertEqual([], list(snap_base.dependencies)) |
468 | 370 | 421 | ||
469 | 422 | def setUpProcessors(self): | ||
470 | 423 | self.unrestricted_procs = [ | ||
471 | 424 | self.factory.makeProcessor() for _ in range(3)] | ||
472 | 425 | self.unrestricted_proc_names = [ | ||
473 | 426 | processor.name for processor in self.unrestricted_procs] | ||
474 | 427 | self.restricted_procs = [ | ||
475 | 428 | self.factory.makeProcessor(restricted=True, build_by_default=False) | ||
476 | 429 | for _ in range(2)] | ||
477 | 430 | self.restricted_proc_names = [ | ||
478 | 431 | processor.name for processor in self.restricted_procs] | ||
479 | 432 | self.procs = self.unrestricted_procs + self.restricted_procs | ||
480 | 433 | self.factory.makeProcessor() | ||
481 | 434 | self.distroseries = self.factory.makeDistroSeries() | ||
482 | 435 | for processor in self.procs: | ||
483 | 436 | self.factory.makeDistroArchSeries( | ||
484 | 437 | distroseries=self.distroseries, architecturetag=processor.name, | ||
485 | 438 | processor=processor) | ||
486 | 439 | |||
487 | 440 | def setProcessors(self, user, snap_base_url, names): | ||
488 | 441 | ws = webservice_for_person( | ||
489 | 442 | user, permission=OAuthPermission.WRITE_PUBLIC) | ||
490 | 443 | return ws.named_post( | ||
491 | 444 | snap_base_url, "setProcessors", | ||
492 | 445 | processors=["/+processors/%s" % name for name in names], | ||
493 | 446 | api_version="devel") | ||
494 | 447 | |||
495 | 448 | def assertProcessors(self, user, snap_base_url, names): | ||
496 | 449 | body = webservice_for_person(user).get( | ||
497 | 450 | snap_base_url + "/processors", api_version="devel").jsonBody() | ||
498 | 451 | self.assertContentEqual( | ||
499 | 452 | names, [entry["name"] for entry in body["entries"]]) | ||
500 | 453 | |||
501 | 454 | def test_setProcessors_admin(self): | ||
502 | 455 | """An admin can change the supported processor set.""" | ||
503 | 456 | self.setUpProcessors() | ||
504 | 457 | with admin_logged_in(): | ||
505 | 458 | snap_base = self.factory.makeSnapBase( | ||
506 | 459 | distro_series=self.distroseries, | ||
507 | 460 | processors=self.unrestricted_procs) | ||
508 | 461 | snap_base_url = api_url(snap_base) | ||
509 | 462 | admin = self.factory.makeAdministrator() | ||
510 | 463 | self.assertProcessors( | ||
511 | 464 | admin, snap_base_url, self.unrestricted_proc_names) | ||
512 | 465 | |||
513 | 466 | response = self.setProcessors( | ||
514 | 467 | admin, snap_base_url, | ||
515 | 468 | [self.unrestricted_proc_names[0], self.restricted_proc_names[0]]) | ||
516 | 469 | self.assertEqual(200, response.status) | ||
517 | 470 | self.assertProcessors( | ||
518 | 471 | admin, snap_base_url, | ||
519 | 472 | [self.unrestricted_proc_names[0], self.restricted_proc_names[0]]) | ||
520 | 473 | |||
521 | 474 | def test_setProcessors_non_admin_forbidden(self): | ||
522 | 475 | """Only admins and registry experts can call setProcessors.""" | ||
523 | 476 | self.setUpProcessors() | ||
524 | 477 | with admin_logged_in(): | ||
525 | 478 | snap_base = self.factory.makeSnapBase( | ||
526 | 479 | distro_series=self.distroseries) | ||
527 | 480 | snap_base_url = api_url(snap_base) | ||
528 | 481 | person = self.factory.makePerson() | ||
529 | 482 | |||
530 | 483 | response = self.setProcessors( | ||
531 | 484 | person, snap_base_url, [self.unrestricted_proc_names[0]]) | ||
532 | 485 | self.assertEqual(401, response.status) | ||
533 | 486 | |||
534 | 371 | def test_collection(self): | 487 | def test_collection(self): |
535 | 372 | # lp.snap_bases is a collection of all SnapBases. | 488 | # lp.snap_bases is a collection of all SnapBases. |
536 | 373 | person = self.factory.makePerson() | 489 | 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 | 4911 | return snappy_series | 4911 | return snappy_series |
543 | 4912 | 4912 | ||
544 | 4913 | def makeSnapBase(self, registrant=None, name=None, display_name=None, | 4913 | def makeSnapBase(self, registrant=None, name=None, display_name=None, |
546 | 4914 | distro_series=None, build_channels=None, | 4914 | distro_series=None, build_channels=None, processors=None, |
547 | 4915 | date_created=DEFAULT): | 4915 | date_created=DEFAULT): |
548 | 4916 | """Make a new SnapBase.""" | 4916 | """Make a new SnapBase.""" |
549 | 4917 | if registrant is None: | 4917 | if registrant is None: |
550 | @@ -4927,7 +4927,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
551 | 4927 | build_channels = {u"snapcraft": u"stable"} | 4927 | build_channels = {u"snapcraft": u"stable"} |
552 | 4928 | return getUtility(ISnapBaseSet).new( | 4928 | return getUtility(ISnapBaseSet).new( |
553 | 4929 | registrant, name, display_name, distro_series, build_channels, | 4929 | registrant, name, display_name, distro_series, build_channels, |
555 | 4930 | date_created=date_created) | 4930 | processors=processors, date_created=date_created) |
556 | 4931 | 4931 | ||
557 | 4932 | def makeOCIProjectName(self, name=None): | 4932 | def makeOCIProjectName(self, name=None): |
558 | 4933 | if name is None: | 4933 | if name is None: |
Looks good and well tested!