Merge lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280 into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 3367
Proposed branch: lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~julian-edwards/maas/dup-boot-source-selections-bug-1360280
Diff against target: 466 lines (+321/-38)
4 files modified
src/maasserver/api/tests/test_boot_source_selections.py (+24/-13)
src/maasserver/forms.py (+56/-0)
src/maasserver/testing/factory.py (+6/-0)
src/maasserver/tests/test_forms_bootsourceselection.py (+235/-25)
To merge this branch: bzr merge lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280
Reviewer Review Type Date Requested Status
Graham Binns (community) Approve
Review via email: mp+241472@code.launchpad.net

Commit message

Validate BootSourceSelection changes against the available selection that lives in BootSourceCache. Previously, bogus entries could be added.

Description of the change

Whoever reviews this, please double check that this won't affect custom images. I don't think it will, but ...

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

Looks good. There's a don't-make-me-think nit inline, but otherwise this is fine.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Wednesday 12 Nov 2014 07:37:38 you wrote:
> I can *hear* jtv, here and elsewhere, saying "What is this obsession with
> creating 3 _things_?"

Arf :)

> Also, I don't actually understand from the test why you're creating three
> mostly identical BootSourceCaches, here and elsewhere. What does it
> actually achieve? Wouldn't one do just as well?

You figured this out....

> > + boot_caches.append(factory.make_BootSourceCache(
> > + boot_source, arch=factory.make_name('arch'),
> > + os=os, release=release))
> > +
> > + params = {
> > + 'os': os,
> > + 'release': release,
> > + 'arches': [boot_caches[0].arch, boot_caches[2].arch],
>
> Ah, I get it now; you're combining the valid values for the field you're
> testing on from some but not all of the BootSourceCaches you've created.
>
> Consider moving the for loop into a helper. Also, add a comment (or a
> docstring on the helper so that you're not having to C&P comments all over
> the shop) explaining why you're creating multiple BootSourceCaches and what
> they'll be used for.

I've been staring at the code for so long that it didn't look that bad any
more. I guess I'll try for a helper - I ran out of brain juice on this one.

Thanks for reviewing!

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (22.0 KiB)

The attempt to merge lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://security.ubuntu.com trusty-security Release [62.0 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [62.0 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [49.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [13.1 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [153 kB]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [60.5 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [136 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [89.5 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [356 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [217 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,200 kB in 2s (411 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twi...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Thanks for pushing me to write a helper Graham, it was needed in the API tests that had become broken because of the extra validation!

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (22.9 KiB)

The attempt to merge lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://security.ubuntu.com trusty-security Release [62.0 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [62.0 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [49.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [13.1 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [153 kB]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [60.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [136 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [89.5 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [356 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [217 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,200 kB in 2s (411 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twi...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (21.7 KiB)

The attempt to merge lp:~julian-edwards/maas/dup-boot-source-selections-part2-bug-1360280 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [62.0 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [62.0 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [49.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [13.1 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [153 kB]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [60.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [136 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [89.5 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [356 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [217 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,200 kB in 2s (418 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twi...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

It would help if I remembered to push my changes up.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/tests/test_boot_source_selections.py'
2--- src/maasserver/api/tests/test_boot_source_selections.py 2014-10-23 15:26:37 +0000
3+++ src/maasserver/api/tests/test_boot_source_selections.py 2014-11-13 03:13:37 +0000
4@@ -117,17 +117,20 @@
5 boot_source_selection = factory.make_BootSourceSelection()
6 new_os = factory.make_name('os')
7 new_release = factory.make_name('release')
8+ boot_source_caches = factory.make_many_BootSourceCaches(
9+ 2, boot_source=boot_source_selection.boot_source, os=new_os,
10+ release=new_release)
11 new_values = {
12 'os': new_os,
13 'release': new_release,
14- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
15+ 'arches': [boot_source_caches[0].arch, boot_source_caches[1].arch],
16 'subarches': [
17- factory.make_name('subarch'), factory.make_name('subarch')],
18- 'labels': [factory.make_name('label')],
19+ boot_source_caches[0].subarch, boot_source_caches[1].subarch],
20+ 'labels': [boot_source_caches[0].label],
21 }
22 response = self.client_put(
23 get_boot_source_selection_uri(boot_source_selection), new_values)
24- self.assertEqual(httplib.OK, response.status_code)
25+ self.assertEqual(httplib.OK, response.status_code, response.content)
26 boot_source_selection = reload_object(boot_source_selection)
27 self.assertAttributes(boot_source_selection, new_values)
28
29@@ -216,12 +219,15 @@
30 self.become_admin()
31 boot_source_selection = factory.make_BootSourceSelection()
32 new_release = factory.make_name('release')
33+ boot_source_caches = factory.make_many_BootSourceCaches(
34+ 2, boot_source=boot_source_selection.boot_source,
35+ release=new_release)
36 new_values = {
37 'release': new_release,
38- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
39+ 'arches': [boot_source_caches[0].arch, boot_source_caches[1].arch],
40 'subarches': [
41- factory.make_name('subarch'), factory.make_name('subarch')],
42- 'labels': [factory.make_name('label')],
43+ boot_source_caches[0].subarch, boot_source_caches[1].subarch],
44+ 'labels': [boot_source_caches[0].label],
45 }
46 response = self.client_put(
47 get_boot_source_selection_backward_uri(
48@@ -279,12 +285,15 @@
49 self.become_admin()
50 boot_source = factory.make_BootSource()
51 new_release = factory.make_name('release')
52+ boot_source_caches = factory.make_many_BootSourceCaches(
53+ 2, boot_source=boot_source,
54+ release=new_release)
55 params = {
56 'release': new_release,
57- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
58+ 'arches': [boot_source_caches[0].arch, boot_source_caches[1].arch],
59 'subarches': [
60- factory.make_name('subarch'), factory.make_name('subarch')],
61- 'labels': [factory.make_name('label')],
62+ boot_source_caches[0].subarch, boot_source_caches[1].subarch],
63+ 'labels': [boot_source_caches[0].label],
64 }
65 response = self.client.post(
66 reverse(
67@@ -373,12 +382,14 @@
68 self.become_admin()
69 boot_source = factory.make_BootSource()
70 new_release = factory.make_name('release')
71+ boot_source_caches = factory.make_many_BootSourceCaches(
72+ 2, boot_source=boot_source, release=new_release)
73 params = {
74 'release': new_release,
75- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
76+ 'arches': [boot_source_caches[0].arch, boot_source_caches[1].arch],
77 'subarches': [
78- factory.make_name('subarch'), factory.make_name('subarch')],
79- 'labels': [factory.make_name('label')],
80+ boot_source_caches[0].subarch, boot_source_caches[1].subarch],
81+ 'labels': [boot_source_caches[0].label],
82 }
83 response = self.client.post(self.get_uri(boot_source), params)
84 self.assertEqual(httplib.OK, response.status_code)
85
86=== modified file 'src/maasserver/forms.py'
87--- src/maasserver/forms.py 2014-11-07 15:33:47 +0000
88+++ src/maasserver/forms.py 2014-11-13 03:13:37 +0000
89@@ -109,6 +109,7 @@
90 BootResourceFile,
91 BootResourceSet,
92 BootSource,
93+ BootSourceCache,
94 BootSourceSelection,
95 Config,
96 DownloadProgress,
97@@ -2354,6 +2355,61 @@
98 else:
99 self.boot_source = boot_source
100
101+ def clean(self):
102+ cleaned_data = super(BootSourceSelectionForm, self).clean()
103+
104+ # Don't filter on OS if not provided. This is to maintain
105+ # backwards compatibility for when OS didn't exist in the API.
106+ if cleaned_data['os']:
107+ cache = BootSourceCache.objects.filter(
108+ boot_source=self.boot_source, os=cleaned_data['os'],
109+ release=cleaned_data['release'])
110+ else:
111+ cache = BootSourceCache.objects.filter(
112+ boot_source=self.boot_source, release=cleaned_data['release'])
113+
114+ if not cache.exists():
115+ set_form_error(
116+ self, "os",
117+ "OS %s with release %s has no available images for download" %
118+ (cleaned_data['os'], cleaned_data['release']))
119+ return cleaned_data
120+
121+ values = cache.values_list("arch", "subarch", "label")
122+ arches, subarches, labels = zip(*values)
123+
124+ # Validate architectures.
125+ required_arches_set = set(arch for arch in cleaned_data['arches'])
126+ wildcard_arches = '*' in required_arches_set
127+ if not wildcard_arches and not required_arches_set <= set(arches):
128+ set_form_error(
129+ self, "arches",
130+ "No available images to download for %s" %
131+ cleaned_data['arches'])
132+
133+ # Validate subarchitectures.
134+ required_subarches_set = set(sa for sa in cleaned_data['subarches'])
135+ wildcard_subarches = '*' in required_subarches_set
136+ if (
137+ not wildcard_subarches and
138+ not required_subarches_set <= set(subarches)
139+ ):
140+ set_form_error(
141+ self, "subarches",
142+ "No available images to download for %s" %
143+ cleaned_data['subarches'])
144+
145+ # Validate labels.
146+ required_labels_set = set(label for label in cleaned_data['labels'])
147+ wildcard_labels = '*' in required_labels_set
148+ if not wildcard_labels and not required_labels_set <= set(labels):
149+ set_form_error(
150+ self, "labels",
151+ "No available images to download for %s" %
152+ cleaned_data['labels'])
153+
154+ return cleaned_data
155+
156 def save(self, *args, **kwargs):
157 boot_source_selection = super(
158 BootSourceSelectionForm, self).save(commit=False)
159
160=== modified file 'src/maasserver/testing/factory.py'
161--- src/maasserver/testing/factory.py 2014-11-07 16:54:51 +0000
162+++ src/maasserver/testing/factory.py 2014-11-13 03:13:37 +0000
163@@ -891,6 +891,12 @@
164 boot_source=boot_source, os=os, arch=arch,
165 subarch=subarch, release=release, label=label)
166
167+ def make_many_BootSourceCaches(self, number, **kwargs):
168+ caches = list()
169+ for _ in range(number):
170+ caches.append(self.make_BootSourceCache(**kwargs))
171+ return caches
172+
173 def make_BootSourceSelection(self, boot_source=None, os=None,
174 release=None, arches=None, subarches=None,
175 labels=None):
176
177=== modified file 'src/maasserver/tests/test_forms_bootsourceselection.py'
178--- src/maasserver/tests/test_forms_bootsourceselection.py 2014-11-11 05:15:23 +0000
179+++ src/maasserver/tests/test_forms_bootsourceselection.py 2014-11-13 03:13:37 +0000
180@@ -24,16 +24,47 @@
181 class TestBootSourceSelectionForm(MAASServerTestCase):
182 """Tests for `BootSourceSelectionForm`."""
183
184+ def make_valid_source_selection_params(self, boot_source=None):
185+ # Helper that creates a valid BootSourceCache and parameters for
186+ # a BootSourceSelectionForm that will validate against the
187+ # cache.
188+ if boot_source is None:
189+ boot_source = factory.make_BootSource()
190+ arch = factory.make_name('arch')
191+ arch2 = factory.make_name('arch')
192+ subarch = factory.make_name('subarch')
193+ subarch2 = factory.make_name('subarch')
194+ label = factory.make_name('label')
195+ label2 = factory.make_name('label')
196+ params = {
197+ 'os': factory.make_name('os'),
198+ 'release': factory.make_name('release'),
199+ 'arches': [arch, arch2],
200+ 'subarches': [subarch, subarch2],
201+ 'labels': [label, label2],
202+ }
203+ factory.make_BootSourceCache(
204+ boot_source=boot_source,
205+ os=params['os'],
206+ release=params['release'],
207+ arch=arch,
208+ subarch=subarch,
209+ label=label,
210+ )
211+ factory.make_BootSourceCache(
212+ boot_source=boot_source,
213+ os=params['os'],
214+ release=params['release'],
215+ arch=arch2,
216+ subarch=subarch2,
217+ label=label2,
218+ )
219+ return params
220+
221 def test_edits_boot_source_selection_object(self):
222 boot_source_selection = factory.make_BootSourceSelection()
223- params = {
224- 'os': factory.make_name('os'),
225- 'release': factory.make_name('release'),
226- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
227- 'subarches': [
228- factory.make_name('subarch'), factory.make_name('subarch')],
229- 'labels': [factory.make_name('label'), factory.make_name('label')],
230- }
231+ boot_source = boot_source_selection.boot_source
232+ params = self.make_valid_source_selection_params(boot_source)
233 form = BootSourceSelectionForm(
234 instance=boot_source_selection, data=params)
235 self.assertTrue(form.is_valid(), form._errors)
236@@ -43,14 +74,7 @@
237
238 def test_creates_boot_source_selection_object(self):
239 boot_source = factory.make_BootSource()
240- params = {
241- 'os': factory.make_name('os'),
242- 'release': factory.make_name('release'),
243- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
244- 'subarches': [
245- factory.make_name('subarch'), factory.make_name('subarch')],
246- 'labels': [factory.make_name('label'), factory.make_name('label')],
247- }
248+ params = self.make_valid_source_selection_params(boot_source)
249 form = BootSourceSelectionForm(boot_source=boot_source, data=params)
250 self.assertTrue(form.is_valid(), form._errors)
251 boot_source_selection = form.save()
252@@ -58,15 +82,9 @@
253
254 def test_cannot_create_duplicate_entry(self):
255 boot_source = factory.make_BootSource()
256- params = {
257- 'os': factory.make_name('os'),
258- 'release': factory.make_name('release'),
259- 'arches': [factory.make_name('arch'), factory.make_name('arch')],
260- 'subarches': [
261- factory.make_name('subarch'), factory.make_name('subarch')],
262- 'labels': [factory.make_name('label'), factory.make_name('label')],
263- }
264- form = BootSourceSelectionForm(boot_source=boot_source, data=params)
265+ params = self.make_valid_source_selection_params(boot_source)
266+ form = BootSourceSelectionForm(
267+ boot_source=boot_source, data=params)
268 self.assertTrue(form.is_valid(), form._errors)
269 form.save()
270
271@@ -79,3 +97,195 @@
272 form = BootSourceSelectionForm(
273 boot_source=boot_source, data=dup_params)
274 self.assertRaises(ValidationError, form.save)
275+
276+ def test_validates_if_boot_source_cache_has_same_os_and_release(self):
277+ boot_source = factory.make_BootSource()
278+ boot_cache = factory.make_BootSourceCache(boot_source)
279+
280+ params = {
281+ 'os': boot_cache.os,
282+ 'release': boot_cache.release,
283+ }
284+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
285+ self.assertTrue(form.is_valid(), form._errors)
286+
287+ def test_rejects_if_boot_source_cache_has_different_os(self):
288+ boot_source = factory.make_BootSource()
289+ boot_cache = factory.make_BootSourceCache(boot_source)
290+
291+ params = {
292+ 'os': factory.make_name('os'),
293+ 'release': boot_cache.release,
294+ }
295+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
296+ self.assertFalse(form.is_valid())
297+ self.assertEqual(
298+ {
299+ "os": [
300+ "OS %s with release %s has no available images "
301+ "for download" % (params['os'], boot_cache.release)
302+ ]
303+ },
304+ form._errors)
305+
306+ def test_rejects_if_boot_source_cache_has_different_release(self):
307+ boot_source = factory.make_BootSource()
308+ boot_cache = factory.make_BootSourceCache(boot_source)
309+
310+ params = {
311+ 'os': boot_cache.os,
312+ 'release': factory.make_name('release'),
313+ }
314+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
315+ self.assertFalse(form.is_valid())
316+ self.assertEqual(
317+ {
318+ "os": [
319+ "OS %s with release %s has no available images "
320+ "for download" % (boot_cache.os, params['release'])
321+ ]
322+ },
323+ form._errors)
324+
325+ def make_some_caches(self, boot_source, os, release):
326+ # Make a few BootSourceCache records that the following tests can use
327+ # to validate against when using BootSourceSelectionForm.
328+ return factory.make_many_BootSourceCaches(
329+ 3, boot_source=boot_source, os=os, release=release)
330+
331+ def test_validates_if_boot_source_cache_has_arch(self):
332+ boot_source = factory.make_BootSource()
333+ os = factory.make_name('os')
334+ release = factory.make_name('release')
335+ boot_caches = self.make_some_caches(boot_source, os, release)
336+
337+ # Request arches that are in two of the cache records.
338+ params = {
339+ 'os': os,
340+ 'release': release,
341+ 'arches': [boot_caches[0].arch, boot_caches[2].arch],
342+ }
343+
344+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
345+ self.assertTrue(form.is_valid(), form._errors)
346+
347+ def test_rejects_if_boot_source_cache_does_not_have_arch(self):
348+ boot_source = factory.make_BootSource()
349+ os = factory.make_name('os')
350+ release = factory.make_name('release')
351+ factory.make_BootSourceCache(
352+ boot_source, os=os, release=release)
353+
354+ params = {
355+ 'os': os,
356+ 'release': release,
357+ 'arches': [factory.make_name('arch')],
358+ }
359+
360+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
361+ self.assertFalse(form.is_valid())
362+ self.assertEqual(
363+ {
364+ "arches": [
365+ "No available images to download for %s" %
366+ params['arches']
367+ ]
368+ },
369+ form._errors)
370+
371+ def test_validates_if_boot_source_cache_has_subarch(self):
372+ boot_source = factory.make_BootSource()
373+ os = factory.make_name('os')
374+ release = factory.make_name('release')
375+ boot_caches = self.make_some_caches(boot_source, os, release)
376+
377+ # Request subarches that are in two of the cache records.
378+ params = {
379+ 'os': os,
380+ 'release': release,
381+ 'subarches': [boot_caches[0].subarch, boot_caches[2].subarch],
382+ }
383+
384+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
385+ self.assertTrue(form.is_valid(), form._errors)
386+
387+ def test_rejects_if_boot_source_cache_does_not_have_subarch(self):
388+ boot_source = factory.make_BootSource()
389+ os = factory.make_name('os')
390+ release = factory.make_name('release')
391+ factory.make_BootSourceCache(
392+ boot_source, os=os, release=release)
393+
394+ params = {
395+ 'os': os,
396+ 'release': release,
397+ 'subarches': [factory.make_name('subarch')],
398+ }
399+
400+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
401+ self.assertFalse(form.is_valid())
402+ self.assertEqual(
403+ {
404+ "subarches": [
405+ "No available images to download for %s" %
406+ params['subarches']
407+ ]
408+ },
409+ form._errors)
410+
411+ def test_validates_if_boot_source_cache_has_label(self):
412+ boot_source = factory.make_BootSource()
413+ os = factory.make_name('os')
414+ release = factory.make_name('release')
415+ boot_caches = self.make_some_caches(boot_source, os, release)
416+
417+ # Request labels that are in two of the cache records.
418+ params = {
419+ 'os': os,
420+ 'release': release,
421+ 'labels': [boot_caches[0].label, boot_caches[2].label],
422+ }
423+
424+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
425+ self.assertTrue(form.is_valid(), form._errors)
426+
427+ def test_rejects_if_boot_source_cache_does_not_have_label(self):
428+ boot_source = factory.make_BootSource()
429+ os = factory.make_name('os')
430+ release = factory.make_name('release')
431+ factory.make_BootSourceCache(
432+ boot_source, os=os, release=release)
433+
434+ params = {
435+ 'os': os,
436+ 'release': release,
437+ 'labels': [factory.make_name('label')],
438+ }
439+
440+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
441+ self.assertFalse(form.is_valid())
442+ self.assertEqual(
443+ {
444+ "labels": [
445+ "No available images to download for %s" %
446+ params['labels']
447+ ]
448+ },
449+ form._errors)
450+
451+ def test_star_values_in_request_validate_against_any_cache(self):
452+ boot_source = factory.make_BootSource()
453+ os = factory.make_name('os')
454+ release = factory.make_name('release')
455+ factory.make_BootSourceCache(
456+ boot_source, os=os, release=release)
457+ params = {
458+ 'os': os,
459+ 'release': release,
460+ 'arches': ['*'],
461+ 'subarches': ['*'],
462+ 'labels': ['*'],
463+ }
464+
465+ form = BootSourceSelectionForm(boot_source=boot_source, data=params)
466+ self.assertTrue(form.is_valid(), form._errors)