Merge lp:~jtv/maas/extract-formtests-node into lp:~maas-committers/maas/trunk
- extract-formtests-node
- Merge into trunk
Proposed by
Jeroen T. Vermeulen
Status: | Merged |
---|---|
Approved by: | Jeroen T. Vermeulen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2816 |
Proposed branch: | lp:~jtv/maas/extract-formtests-node |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
722 lines (+353/-324) 2 files modified
src/maasserver/tests/test_forms.py (+1/-324) src/maasserver/tests/test_forms_node.py (+352/-0) |
To merge this branch: | bzr merge lp:~jtv/maas/extract-formtests-node |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+232170@code.launchpad.net |
Commit message
Extract Node form tests into their own test module.
Description of the change
For self-approval.
Jeroen
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 | === modified file 'src/maasserver/tests/test_forms.py' |
2 | --- src/maasserver/tests/test_forms.py 2014-08-26 05:43:19 +0000 |
3 | +++ src/maasserver/tests/test_forms.py 2014-08-26 07:57:29 +0000 |
4 | @@ -18,7 +18,6 @@ |
5 | |
6 | from django.conf import settings |
7 | from django.core.exceptions import ValidationError |
8 | -from maasserver.clusterrpc.power_parameters import get_power_type_choices |
9 | from maasserver.enum import ( |
10 | NODE_STATUS, |
11 | NODEGROUP_STATUS, |
12 | @@ -27,7 +26,6 @@ |
13 | from maasserver.forms import ( |
14 | AdminNodeForm, |
15 | AdminNodeWithMACAddressesForm, |
16 | - BLANK_CHOICE, |
17 | ERROR_MESSAGE_STATIC_IPS_OUTSIDE_RANGE, |
18 | ERROR_MESSAGE_STATIC_RANGE_IN_USE, |
19 | get_node_create_form, |
20 | @@ -37,7 +35,6 @@ |
21 | MACAddressForm, |
22 | MAX_MESSAGES, |
23 | merge_error_messages, |
24 | - NO_ARCHITECTURES_AVAILABLE, |
25 | NodeForm, |
26 | NodeGroupInterfaceForeignDHCPForm, |
27 | NodeGroupInterfaceForm, |
28 | @@ -56,17 +53,9 @@ |
29 | ) |
30 | from maasserver.models.network import get_name_and_vlan_from_cluster_interface |
31 | from maasserver.models.staticipaddress import StaticIPAddress |
32 | -from maasserver.testing.architecture import ( |
33 | - make_usable_architecture, |
34 | - patch_usable_architectures, |
35 | - ) |
36 | +from maasserver.testing.architecture import make_usable_architecture |
37 | from maasserver.testing.factory import factory |
38 | from maasserver.testing.orm import reload_object |
39 | -from maasserver.testing.osystems import ( |
40 | - make_osystem_with_releases, |
41 | - make_usable_osystem, |
42 | - patch_usable_osystems, |
43 | - ) |
44 | from maasserver.testing.testcase import MAASServerTestCase |
45 | from maastesting.matchers import MockCalledOnceWith |
46 | from netaddr import IPNetwork |
47 | @@ -202,318 +191,6 @@ |
48 | AdminNodeWithMACAddressesForm, get_node_create_form(admin)) |
49 | |
50 | |
51 | -class TestNodeForm(MAASServerTestCase): |
52 | - |
53 | - def test_contains_limited_set_of_fields(self): |
54 | - form = NodeForm() |
55 | - |
56 | - self.assertEqual( |
57 | - [ |
58 | - 'hostname', |
59 | - 'architecture', |
60 | - 'osystem', |
61 | - 'distro_series', |
62 | - 'license_key', |
63 | - 'disable_ipv4', |
64 | - 'nodegroup', |
65 | - ], list(form.fields)) |
66 | - |
67 | - def test_changes_node(self): |
68 | - node = factory.make_node() |
69 | - hostname = factory.make_string() |
70 | - patch_usable_architectures(self, [node.architecture]) |
71 | - |
72 | - form = NodeForm( |
73 | - data={ |
74 | - 'hostname': hostname, |
75 | - 'architecture': make_usable_architecture(self), |
76 | - }, |
77 | - instance=node) |
78 | - form.save() |
79 | - |
80 | - self.assertEqual(hostname, node.hostname) |
81 | - |
82 | - def test_accepts_usable_architecture(self): |
83 | - arch = make_usable_architecture(self) |
84 | - form = NodeForm(data={ |
85 | - 'hostname': factory.make_name('host'), |
86 | - 'architecture': arch, |
87 | - }) |
88 | - self.assertTrue(form.is_valid(), form._errors) |
89 | - |
90 | - def test_rejects_unusable_architecture(self): |
91 | - patch_usable_architectures(self) |
92 | - form = NodeForm(data={ |
93 | - 'hostname': factory.make_name('host'), |
94 | - 'architecture': factory.make_name('arch'), |
95 | - }) |
96 | - self.assertFalse(form.is_valid()) |
97 | - self.assertItemsEqual(['architecture'], form._errors.keys()) |
98 | - |
99 | - def test_starts_with_default_architecture(self): |
100 | - arches = sorted([factory.make_name('arch') for _ in range(5)]) |
101 | - patch_usable_architectures(self, arches) |
102 | - form = NodeForm() |
103 | - self.assertEqual( |
104 | - pick_default_architecture(arches), |
105 | - form.fields['architecture'].initial) |
106 | - |
107 | - def test_adds_blank_default_when_no_arches_available(self): |
108 | - patch_usable_architectures(self, []) |
109 | - form = NodeForm() |
110 | - self.assertEqual( |
111 | - [BLANK_CHOICE], |
112 | - form.fields['architecture'].choices) |
113 | - |
114 | - def test_adds_error_when_no_arches_available(self): |
115 | - patch_usable_architectures(self, []) |
116 | - form = NodeForm() |
117 | - self.assertFalse(form.is_valid()) |
118 | - self.assertEqual( |
119 | - [NO_ARCHITECTURES_AVAILABLE], |
120 | - form.errors['architecture']) |
121 | - |
122 | - def test_accepts_osystem(self): |
123 | - osystem = make_usable_osystem(self) |
124 | - form = NodeForm(data={ |
125 | - 'hostname': factory.make_name('host'), |
126 | - 'architecture': make_usable_architecture(self), |
127 | - 'osystem': osystem.name, |
128 | - }) |
129 | - self.assertTrue(form.is_valid(), form._errors) |
130 | - |
131 | - def test_rejects_invalid_osystem(self): |
132 | - patch_usable_osystems(self) |
133 | - form = NodeForm(data={ |
134 | - 'hostname': factory.make_name('host'), |
135 | - 'architecture': make_usable_architecture(self), |
136 | - 'osystem': factory.make_name('os'), |
137 | - }) |
138 | - self.assertFalse(form.is_valid()) |
139 | - self.assertItemsEqual(['osystem'], form._errors.keys()) |
140 | - |
141 | - def test_starts_with_default_osystem(self): |
142 | - osystems = [make_osystem_with_releases(self) for _ in range(5)] |
143 | - patch_usable_osystems(self, osystems) |
144 | - form = NodeForm() |
145 | - self.assertEqual( |
146 | - '', |
147 | - form.fields['osystem'].initial) |
148 | - |
149 | - def test_accepts_osystem_distro_series(self): |
150 | - osystem = make_usable_osystem(self) |
151 | - release = osystem.get_default_release() |
152 | - form = NodeForm(data={ |
153 | - 'hostname': factory.make_name('host'), |
154 | - 'architecture': make_usable_architecture(self), |
155 | - 'osystem': osystem.name, |
156 | - 'distro_series': '%s/%s' % (osystem.name, release), |
157 | - }) |
158 | - self.assertTrue(form.is_valid(), form._errors) |
159 | - |
160 | - def test_rejects_invalid_osystem_distro_series(self): |
161 | - osystem = make_usable_osystem(self) |
162 | - release = factory.make_name('release') |
163 | - form = NodeForm(data={ |
164 | - 'hostname': factory.make_name('host'), |
165 | - 'architecture': make_usable_architecture(self), |
166 | - 'osystem': osystem.name, |
167 | - 'distro_series': '%s/%s' % (osystem.name, release), |
168 | - }) |
169 | - self.assertFalse(form.is_valid()) |
170 | - self.assertItemsEqual(['distro_series'], form._errors.keys()) |
171 | - |
172 | - def test_starts_with_default_distro_series(self): |
173 | - osystems = [make_osystem_with_releases(self) for _ in range(5)] |
174 | - patch_usable_osystems(self, osystems) |
175 | - form = NodeForm() |
176 | - self.assertEqual( |
177 | - '', |
178 | - form.fields['distro_series'].initial) |
179 | - |
180 | - def test_rejects_mismatch_osystem_distro_series(self): |
181 | - osystem = make_usable_osystem(self) |
182 | - release = osystem.get_default_release() |
183 | - invalid = factory.make_name('invalid_os') |
184 | - form = NodeForm(data={ |
185 | - 'hostname': factory.make_name('host'), |
186 | - 'architecture': make_usable_architecture(self), |
187 | - 'osystem': osystem.name, |
188 | - 'distro_series': '%s/%s' % (invalid, release), |
189 | - }) |
190 | - self.assertFalse(form.is_valid()) |
191 | - self.assertItemsEqual(['distro_series'], form._errors.keys()) |
192 | - |
193 | - def test_rejects_missing_license_key(self): |
194 | - osystem = make_usable_osystem(self) |
195 | - release = osystem.get_default_release() |
196 | - self.patch(osystem, 'requires_license_key').return_value = True |
197 | - mock_validate = self.patch(osystem, 'validate_license_key') |
198 | - mock_validate.return_value = True |
199 | - form = NodeForm(data={ |
200 | - 'hostname': factory.make_name('host'), |
201 | - 'architecture': make_usable_architecture(self), |
202 | - 'osystem': osystem.name, |
203 | - 'distro_series': '%s/%s*' % (osystem.name, release), |
204 | - }) |
205 | - self.assertFalse(form.is_valid()) |
206 | - self.assertItemsEqual(['license_key'], form._errors.keys()) |
207 | - |
208 | - def test_calls_validate_license_key(self): |
209 | - osystem = make_usable_osystem(self) |
210 | - release = osystem.get_default_release() |
211 | - self.patch(osystem, 'requires_license_key').return_value = True |
212 | - mock_validate = self.patch(osystem, 'validate_license_key') |
213 | - mock_validate.return_value = True |
214 | - form = NodeForm(data={ |
215 | - 'hostname': factory.make_name('host'), |
216 | - 'architecture': make_usable_architecture(self), |
217 | - 'osystem': osystem.name, |
218 | - 'distro_series': '%s/%s*' % (osystem.name, release), |
219 | - 'license_key': factory.make_string(), |
220 | - }) |
221 | - self.assertTrue(form.is_valid()) |
222 | - mock_validate.assert_called_once() |
223 | - |
224 | - def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self): |
225 | - # If a host with a given hostname exists on a managed nodegroup, |
226 | - # new nodes on unmanaged nodegroups with hostnames that match |
227 | - # that FQDN will be rejected. |
228 | - nodegroup = factory.make_node_group( |
229 | - status=NODEGROUP_STATUS.ACCEPTED, |
230 | - management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
231 | - node = factory.make_node( |
232 | - hostname=factory.make_name("hostname"), nodegroup=nodegroup) |
233 | - other_nodegroup = factory.make_node_group() |
234 | - form = NodeForm(data={ |
235 | - 'nodegroup': other_nodegroup, |
236 | - 'hostname': node.fqdn, |
237 | - 'architecture': make_usable_architecture(self), |
238 | - }) |
239 | - form.instance.nodegroup = other_nodegroup |
240 | - self.assertFalse(form.is_valid()) |
241 | - |
242 | - def test_rejects_duplicate_fqdn_on_same_nodegroup(self): |
243 | - # If a node with a given FQDN exists on a managed nodegroup, new |
244 | - # nodes on that nodegroup with duplicate FQDNs will be rejected. |
245 | - nodegroup = factory.make_node_group( |
246 | - status=NODEGROUP_STATUS.ACCEPTED, |
247 | - management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
248 | - node = factory.make_node( |
249 | - hostname=factory.make_name("hostname"), nodegroup=nodegroup) |
250 | - form = NodeForm(data={ |
251 | - 'nodegroup': nodegroup, |
252 | - 'hostname': node.fqdn, |
253 | - 'architecture': make_usable_architecture(self), |
254 | - }) |
255 | - form.instance.nodegroup = nodegroup |
256 | - self.assertFalse(form.is_valid()) |
257 | - |
258 | - |
259 | -class TestAdminNodeForm(MAASServerTestCase): |
260 | - |
261 | - def test_AdminNodeForm_contains_limited_set_of_fields(self): |
262 | - node = factory.make_node() |
263 | - form = AdminNodeForm(instance=node) |
264 | - |
265 | - self.assertEqual( |
266 | - [ |
267 | - 'hostname', |
268 | - 'architecture', |
269 | - 'osystem', |
270 | - 'distro_series', |
271 | - 'license_key', |
272 | - 'disable_ipv4', |
273 | - 'power_type', |
274 | - 'power_parameters', |
275 | - 'cpu_count', |
276 | - 'memory', |
277 | - 'storage', |
278 | - 'zone', |
279 | - ], |
280 | - list(form.fields)) |
281 | - |
282 | - def test_AdminNodeForm_initialises_zone(self): |
283 | - # The zone field uses "to_field_name", so that it can refer to a zone |
284 | - # by name instead of by ID. A bug in Django breaks initialisation |
285 | - # from an instance: the field tries to initialise the field using a |
286 | - # zone's ID instead of its name, and ends up reverting to the default. |
287 | - # The code must work around this bug. |
288 | - zone = factory.make_zone() |
289 | - node = factory.make_node(zone=zone) |
290 | - # We'll create a form that makes a change, but not to the zone. |
291 | - data = {'hostname': factory.make_name('host')} |
292 | - form = AdminNodeForm(instance=node, data=data) |
293 | - # The Django bug would stop the initial field value from being set, |
294 | - # but the workaround ensures that it is initialised. |
295 | - self.assertEqual(zone.name, form.initial['zone']) |
296 | - |
297 | - def test_AdminNodeForm_changes_node(self): |
298 | - node = factory.make_node() |
299 | - zone = factory.make_zone() |
300 | - hostname = factory.make_string() |
301 | - power_type = factory.pick_power_type() |
302 | - form = AdminNodeForm( |
303 | - data={ |
304 | - 'hostname': hostname, |
305 | - 'power_type': power_type, |
306 | - 'architecture': make_usable_architecture(self), |
307 | - 'zone': zone.name, |
308 | - }, |
309 | - instance=node) |
310 | - form.save() |
311 | - |
312 | - node = reload_object(node) |
313 | - self.assertEqual( |
314 | - (node.hostname, node.power_type, node.zone), |
315 | - (hostname, power_type, zone)) |
316 | - |
317 | - def test_AdminNodeForm_populates_power_type_choices(self): |
318 | - form = AdminNodeForm() |
319 | - self.assertEqual( |
320 | - [''] + [choice[0] for choice in get_power_type_choices()], |
321 | - [choice[0] for choice in form.fields['power_type'].choices]) |
322 | - |
323 | - def test_AdminNodeForm_populates_power_type_initial(self): |
324 | - node = factory.make_node() |
325 | - form = AdminNodeForm(instance=node) |
326 | - self.assertEqual(node.power_type, form.fields['power_type'].initial) |
327 | - |
328 | - def test_AdminNodeForm_changes_node_with_skip_check(self): |
329 | - node = factory.make_node() |
330 | - hostname = factory.make_string() |
331 | - power_type = factory.pick_power_type() |
332 | - power_parameters_field = factory.make_string() |
333 | - arch = make_usable_architecture(self) |
334 | - form = AdminNodeForm( |
335 | - data={ |
336 | - 'hostname': hostname, |
337 | - 'architecture': arch, |
338 | - 'power_type': power_type, |
339 | - 'power_parameters_field': power_parameters_field, |
340 | - 'power_parameters_skip_check': True, |
341 | - }, |
342 | - instance=node) |
343 | - form.save() |
344 | - |
345 | - self.assertEqual( |
346 | - (hostname, power_type, {'field': power_parameters_field}), |
347 | - (node.hostname, node.power_type, node.power_parameters)) |
348 | - |
349 | - def test_AdminForm_does_not_permit_nodegroup_change(self): |
350 | - # We had to make Node.nodegroup editable to get Django to |
351 | - # validate it as non-blankable, but that doesn't mean that we |
352 | - # actually want to allow people to edit it through API or UI. |
353 | - old_nodegroup = factory.make_node_group() |
354 | - node = factory.make_node( |
355 | - nodegroup=old_nodegroup, |
356 | - architecture=make_usable_architecture(self)) |
357 | - new_nodegroup = factory.make_node_group() |
358 | - AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save() |
359 | - # The form saved without error, but the nodegroup change was ignored. |
360 | - self.assertEqual(old_nodegroup, node.nodegroup) |
361 | - |
362 | - |
363 | class TestMergeErrorMessages(MAASServerTestCase): |
364 | |
365 | def test_merge_error_messages_returns_summary_message(self): |
366 | |
367 | === added file 'src/maasserver/tests/test_forms_node.py' |
368 | --- src/maasserver/tests/test_forms_node.py 1970-01-01 00:00:00 +0000 |
369 | +++ src/maasserver/tests/test_forms_node.py 2014-08-26 07:57:29 +0000 |
370 | @@ -0,0 +1,352 @@ |
371 | +# Copyright 2014 Canonical Ltd. This software is licensed under the |
372 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
373 | + |
374 | +"""Tests for node forms.""" |
375 | + |
376 | +from __future__ import ( |
377 | + absolute_import, |
378 | + print_function, |
379 | + unicode_literals, |
380 | + ) |
381 | + |
382 | +str = None |
383 | + |
384 | +__metaclass__ = type |
385 | +__all__ = [] |
386 | + |
387 | +from maasserver.clusterrpc.power_parameters import get_power_type_choices |
388 | +from maasserver.enum import ( |
389 | + NODEGROUP_STATUS, |
390 | + NODEGROUPINTERFACE_MANAGEMENT, |
391 | + ) |
392 | +from maasserver.forms import ( |
393 | + AdminNodeForm, |
394 | + BLANK_CHOICE, |
395 | + NO_ARCHITECTURES_AVAILABLE, |
396 | + NodeForm, |
397 | + pick_default_architecture, |
398 | + ) |
399 | +from maasserver.testing.architecture import ( |
400 | + make_usable_architecture, |
401 | + patch_usable_architectures, |
402 | + ) |
403 | +from maasserver.testing.factory import factory |
404 | +from maasserver.testing.orm import reload_object |
405 | +from maasserver.testing.osystems import ( |
406 | + make_osystem_with_releases, |
407 | + make_usable_osystem, |
408 | + patch_usable_osystems, |
409 | + ) |
410 | +from maasserver.testing.testcase import MAASServerTestCase |
411 | + |
412 | + |
413 | +class TestNodeForm(MAASServerTestCase): |
414 | + |
415 | + def test_contains_limited_set_of_fields(self): |
416 | + form = NodeForm() |
417 | + |
418 | + self.assertEqual( |
419 | + [ |
420 | + 'hostname', |
421 | + 'architecture', |
422 | + 'osystem', |
423 | + 'distro_series', |
424 | + 'license_key', |
425 | + 'disable_ipv4', |
426 | + 'nodegroup', |
427 | + ], list(form.fields)) |
428 | + |
429 | + def test_changes_node(self): |
430 | + node = factory.make_node() |
431 | + hostname = factory.make_string() |
432 | + patch_usable_architectures(self, [node.architecture]) |
433 | + |
434 | + form = NodeForm( |
435 | + data={ |
436 | + 'hostname': hostname, |
437 | + 'architecture': make_usable_architecture(self), |
438 | + }, |
439 | + instance=node) |
440 | + form.save() |
441 | + |
442 | + self.assertEqual(hostname, node.hostname) |
443 | + |
444 | + def test_accepts_usable_architecture(self): |
445 | + arch = make_usable_architecture(self) |
446 | + form = NodeForm(data={ |
447 | + 'hostname': factory.make_name('host'), |
448 | + 'architecture': arch, |
449 | + }) |
450 | + self.assertTrue(form.is_valid(), form._errors) |
451 | + |
452 | + def test_rejects_unusable_architecture(self): |
453 | + patch_usable_architectures(self) |
454 | + form = NodeForm(data={ |
455 | + 'hostname': factory.make_name('host'), |
456 | + 'architecture': factory.make_name('arch'), |
457 | + }) |
458 | + self.assertFalse(form.is_valid()) |
459 | + self.assertItemsEqual(['architecture'], form._errors.keys()) |
460 | + |
461 | + def test_starts_with_default_architecture(self): |
462 | + arches = sorted([factory.make_name('arch') for _ in range(5)]) |
463 | + patch_usable_architectures(self, arches) |
464 | + form = NodeForm() |
465 | + self.assertEqual( |
466 | + pick_default_architecture(arches), |
467 | + form.fields['architecture'].initial) |
468 | + |
469 | + def test_adds_blank_default_when_no_arches_available(self): |
470 | + patch_usable_architectures(self, []) |
471 | + form = NodeForm() |
472 | + self.assertEqual( |
473 | + [BLANK_CHOICE], |
474 | + form.fields['architecture'].choices) |
475 | + |
476 | + def test_adds_error_when_no_arches_available(self): |
477 | + patch_usable_architectures(self, []) |
478 | + form = NodeForm() |
479 | + self.assertFalse(form.is_valid()) |
480 | + self.assertEqual( |
481 | + [NO_ARCHITECTURES_AVAILABLE], |
482 | + form.errors['architecture']) |
483 | + |
484 | + def test_accepts_osystem(self): |
485 | + osystem = make_usable_osystem(self) |
486 | + form = NodeForm(data={ |
487 | + 'hostname': factory.make_name('host'), |
488 | + 'architecture': make_usable_architecture(self), |
489 | + 'osystem': osystem.name, |
490 | + }) |
491 | + self.assertTrue(form.is_valid(), form._errors) |
492 | + |
493 | + def test_rejects_invalid_osystem(self): |
494 | + patch_usable_osystems(self) |
495 | + form = NodeForm(data={ |
496 | + 'hostname': factory.make_name('host'), |
497 | + 'architecture': make_usable_architecture(self), |
498 | + 'osystem': factory.make_name('os'), |
499 | + }) |
500 | + self.assertFalse(form.is_valid()) |
501 | + self.assertItemsEqual(['osystem'], form._errors.keys()) |
502 | + |
503 | + def test_starts_with_default_osystem(self): |
504 | + osystems = [make_osystem_with_releases(self) for _ in range(5)] |
505 | + patch_usable_osystems(self, osystems) |
506 | + form = NodeForm() |
507 | + self.assertEqual( |
508 | + '', |
509 | + form.fields['osystem'].initial) |
510 | + |
511 | + def test_accepts_osystem_distro_series(self): |
512 | + osystem = make_usable_osystem(self) |
513 | + release = osystem.get_default_release() |
514 | + form = NodeForm(data={ |
515 | + 'hostname': factory.make_name('host'), |
516 | + 'architecture': make_usable_architecture(self), |
517 | + 'osystem': osystem.name, |
518 | + 'distro_series': '%s/%s' % (osystem.name, release), |
519 | + }) |
520 | + self.assertTrue(form.is_valid(), form._errors) |
521 | + |
522 | + def test_rejects_invalid_osystem_distro_series(self): |
523 | + osystem = make_usable_osystem(self) |
524 | + release = factory.make_name('release') |
525 | + form = NodeForm(data={ |
526 | + 'hostname': factory.make_name('host'), |
527 | + 'architecture': make_usable_architecture(self), |
528 | + 'osystem': osystem.name, |
529 | + 'distro_series': '%s/%s' % (osystem.name, release), |
530 | + }) |
531 | + self.assertFalse(form.is_valid()) |
532 | + self.assertItemsEqual(['distro_series'], form._errors.keys()) |
533 | + |
534 | + def test_starts_with_default_distro_series(self): |
535 | + osystems = [make_osystem_with_releases(self) for _ in range(5)] |
536 | + patch_usable_osystems(self, osystems) |
537 | + form = NodeForm() |
538 | + self.assertEqual( |
539 | + '', |
540 | + form.fields['distro_series'].initial) |
541 | + |
542 | + def test_rejects_mismatch_osystem_distro_series(self): |
543 | + osystem = make_usable_osystem(self) |
544 | + release = osystem.get_default_release() |
545 | + invalid = factory.make_name('invalid_os') |
546 | + form = NodeForm(data={ |
547 | + 'hostname': factory.make_name('host'), |
548 | + 'architecture': make_usable_architecture(self), |
549 | + 'osystem': osystem.name, |
550 | + 'distro_series': '%s/%s' % (invalid, release), |
551 | + }) |
552 | + self.assertFalse(form.is_valid()) |
553 | + self.assertItemsEqual(['distro_series'], form._errors.keys()) |
554 | + |
555 | + def test_rejects_missing_license_key(self): |
556 | + osystem = make_usable_osystem(self) |
557 | + release = osystem.get_default_release() |
558 | + self.patch(osystem, 'requires_license_key').return_value = True |
559 | + mock_validate = self.patch(osystem, 'validate_license_key') |
560 | + mock_validate.return_value = True |
561 | + form = NodeForm(data={ |
562 | + 'hostname': factory.make_name('host'), |
563 | + 'architecture': make_usable_architecture(self), |
564 | + 'osystem': osystem.name, |
565 | + 'distro_series': '%s/%s*' % (osystem.name, release), |
566 | + }) |
567 | + self.assertFalse(form.is_valid()) |
568 | + self.assertItemsEqual(['license_key'], form._errors.keys()) |
569 | + |
570 | + def test_calls_validate_license_key(self): |
571 | + osystem = make_usable_osystem(self) |
572 | + release = osystem.get_default_release() |
573 | + self.patch(osystem, 'requires_license_key').return_value = True |
574 | + mock_validate = self.patch(osystem, 'validate_license_key') |
575 | + mock_validate.return_value = True |
576 | + form = NodeForm(data={ |
577 | + 'hostname': factory.make_name('host'), |
578 | + 'architecture': make_usable_architecture(self), |
579 | + 'osystem': osystem.name, |
580 | + 'distro_series': '%s/%s*' % (osystem.name, release), |
581 | + 'license_key': factory.make_string(), |
582 | + }) |
583 | + self.assertTrue(form.is_valid()) |
584 | + mock_validate.assert_called_once() |
585 | + |
586 | + def test_rejects_duplicate_fqdn_with_unmanaged_dns_on_one_nodegroup(self): |
587 | + # If a host with a given hostname exists on a managed nodegroup, |
588 | + # new nodes on unmanaged nodegroups with hostnames that match |
589 | + # that FQDN will be rejected. |
590 | + nodegroup = factory.make_node_group( |
591 | + status=NODEGROUP_STATUS.ACCEPTED, |
592 | + management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
593 | + node = factory.make_node( |
594 | + hostname=factory.make_name("hostname"), nodegroup=nodegroup) |
595 | + other_nodegroup = factory.make_node_group() |
596 | + form = NodeForm(data={ |
597 | + 'nodegroup': other_nodegroup, |
598 | + 'hostname': node.fqdn, |
599 | + 'architecture': make_usable_architecture(self), |
600 | + }) |
601 | + form.instance.nodegroup = other_nodegroup |
602 | + self.assertFalse(form.is_valid()) |
603 | + |
604 | + def test_rejects_duplicate_fqdn_on_same_nodegroup(self): |
605 | + # If a node with a given FQDN exists on a managed nodegroup, new |
606 | + # nodes on that nodegroup with duplicate FQDNs will be rejected. |
607 | + nodegroup = factory.make_node_group( |
608 | + status=NODEGROUP_STATUS.ACCEPTED, |
609 | + management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
610 | + node = factory.make_node( |
611 | + hostname=factory.make_name("hostname"), nodegroup=nodegroup) |
612 | + form = NodeForm(data={ |
613 | + 'nodegroup': nodegroup, |
614 | + 'hostname': node.fqdn, |
615 | + 'architecture': make_usable_architecture(self), |
616 | + }) |
617 | + form.instance.nodegroup = nodegroup |
618 | + self.assertFalse(form.is_valid()) |
619 | + |
620 | + |
621 | +class TestAdminNodeForm(MAASServerTestCase): |
622 | + |
623 | + def test_AdminNodeForm_contains_limited_set_of_fields(self): |
624 | + node = factory.make_node() |
625 | + form = AdminNodeForm(instance=node) |
626 | + |
627 | + self.assertEqual( |
628 | + [ |
629 | + 'hostname', |
630 | + 'architecture', |
631 | + 'osystem', |
632 | + 'distro_series', |
633 | + 'license_key', |
634 | + 'disable_ipv4', |
635 | + 'power_type', |
636 | + 'power_parameters', |
637 | + 'cpu_count', |
638 | + 'memory', |
639 | + 'storage', |
640 | + 'zone', |
641 | + ], |
642 | + list(form.fields)) |
643 | + |
644 | + def test_AdminNodeForm_initialises_zone(self): |
645 | + # The zone field uses "to_field_name", so that it can refer to a zone |
646 | + # by name instead of by ID. A bug in Django breaks initialisation |
647 | + # from an instance: the field tries to initialise the field using a |
648 | + # zone's ID instead of its name, and ends up reverting to the default. |
649 | + # The code must work around this bug. |
650 | + zone = factory.make_zone() |
651 | + node = factory.make_node(zone=zone) |
652 | + # We'll create a form that makes a change, but not to the zone. |
653 | + data = {'hostname': factory.make_name('host')} |
654 | + form = AdminNodeForm(instance=node, data=data) |
655 | + # The Django bug would stop the initial field value from being set, |
656 | + # but the workaround ensures that it is initialised. |
657 | + self.assertEqual(zone.name, form.initial['zone']) |
658 | + |
659 | + def test_AdminNodeForm_changes_node(self): |
660 | + node = factory.make_node() |
661 | + zone = factory.make_zone() |
662 | + hostname = factory.make_string() |
663 | + power_type = factory.pick_power_type() |
664 | + form = AdminNodeForm( |
665 | + data={ |
666 | + 'hostname': hostname, |
667 | + 'power_type': power_type, |
668 | + 'architecture': make_usable_architecture(self), |
669 | + 'zone': zone.name, |
670 | + }, |
671 | + instance=node) |
672 | + form.save() |
673 | + |
674 | + node = reload_object(node) |
675 | + self.assertEqual( |
676 | + (node.hostname, node.power_type, node.zone), |
677 | + (hostname, power_type, zone)) |
678 | + |
679 | + def test_AdminNodeForm_populates_power_type_choices(self): |
680 | + form = AdminNodeForm() |
681 | + self.assertEqual( |
682 | + [''] + [choice[0] for choice in get_power_type_choices()], |
683 | + [choice[0] for choice in form.fields['power_type'].choices]) |
684 | + |
685 | + def test_AdminNodeForm_populates_power_type_initial(self): |
686 | + node = factory.make_node() |
687 | + form = AdminNodeForm(instance=node) |
688 | + self.assertEqual(node.power_type, form.fields['power_type'].initial) |
689 | + |
690 | + def test_AdminNodeForm_changes_node_with_skip_check(self): |
691 | + node = factory.make_node() |
692 | + hostname = factory.make_string() |
693 | + power_type = factory.pick_power_type() |
694 | + power_parameters_field = factory.make_string() |
695 | + arch = make_usable_architecture(self) |
696 | + form = AdminNodeForm( |
697 | + data={ |
698 | + 'hostname': hostname, |
699 | + 'architecture': arch, |
700 | + 'power_type': power_type, |
701 | + 'power_parameters_field': power_parameters_field, |
702 | + 'power_parameters_skip_check': True, |
703 | + }, |
704 | + instance=node) |
705 | + form.save() |
706 | + |
707 | + self.assertEqual( |
708 | + (hostname, power_type, {'field': power_parameters_field}), |
709 | + (node.hostname, node.power_type, node.power_parameters)) |
710 | + |
711 | + def test_AdminForm_does_not_permit_nodegroup_change(self): |
712 | + # We had to make Node.nodegroup editable to get Django to |
713 | + # validate it as non-blankable, but that doesn't mean that we |
714 | + # actually want to allow people to edit it through API or UI. |
715 | + old_nodegroup = factory.make_node_group() |
716 | + node = factory.make_node( |
717 | + nodegroup=old_nodegroup, |
718 | + architecture=make_usable_architecture(self)) |
719 | + new_nodegroup = factory.make_node_group() |
720 | + AdminNodeForm(data={'nodegroup': new_nodegroup}, instance=node).save() |
721 | + # The form saved without error, but the nodegroup change was ignored. |
722 | + self.assertEqual(old_nodegroup, node.nodegroup) |
Looks OK. Self-approving.