1.3.1
by Andres Rodriguez
Import upstream version 1.7.0~beta7+bzr3266 |
1 |
# Copyright 2014 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
4 |
"""Tests for nodegroup forms."""
|
|
5 |
||
6 |
from __future__ import ( |
|
7 |
absolute_import, |
|
8 |
print_function, |
|
9 |
unicode_literals, |
|
10 |
)
|
|
11 |
||
12 |
str = None |
|
13 |
||
14 |
__metaclass__ = type |
|
15 |
__all__ = [] |
|
16 |
||
17 |
import json |
|
18 |
from random import randint |
|
19 |
||
20 |
from django.forms import ( |
|
21 |
CheckboxInput, |
|
22 |
HiddenInput, |
|
23 |
)
|
|
24 |
from maasserver.enum import ( |
|
25 |
NODE_STATUS, |
|
26 |
NODEGROUP_STATUS, |
|
27 |
NODEGROUPINTERFACE_MANAGEMENT, |
|
28 |
)
|
|
29 |
from maasserver.forms import ( |
|
30 |
INTERFACES_VALIDATION_ERROR_MESSAGE, |
|
31 |
NodeGroupDefineForm, |
|
32 |
NodeGroupEdit, |
|
33 |
)
|
|
34 |
from maasserver.models import ( |
|
35 |
NodeGroup, |
|
36 |
NodeGroupInterface, |
|
37 |
)
|
|
38 |
from maasserver.testing.factory import factory |
|
39 |
from maasserver.testing.orm import reload_object |
|
40 |
from maasserver.testing.testcase import MAASServerTestCase |
|
41 |
from netaddr import IPNetwork |
|
42 |
from provisioningserver.utils.enum import map_enum |
|
43 |
from testtools.matchers import ( |
|
44 |
HasLength, |
|
45 |
MatchesStructure, |
|
46 |
StartsWith, |
|
47 |
)
|
|
48 |
||
49 |
||
50 |
class TestNodeGroupDefineForm(MAASServerTestCase): |
|
51 |
||
52 |
def test_creates_pending_nodegroup(self): |
|
53 |
name = factory.make_name('name') |
|
54 |
uuid = factory.make_UUID() |
|
55 |
form = NodeGroupDefineForm(data={'name': name, 'uuid': uuid}) |
|
56 |
self.assertTrue(form.is_valid(), form._errors) |
|
57 |
nodegroup = form.save() |
|
58 |
self.assertEqual( |
|
59 |
(uuid, name, NODEGROUP_STATUS.PENDING, 0), |
|
60 |
(
|
|
61 |
nodegroup.uuid, |
|
62 |
nodegroup.name, |
|
63 |
nodegroup.status, |
|
64 |
nodegroup.nodegroupinterface_set.count(), |
|
65 |
))
|
|
66 |
||
67 |
def test_creates_nodegroup_with_status(self): |
|
68 |
name = factory.make_name('name') |
|
69 |
uuid = factory.make_UUID() |
|
70 |
form = NodeGroupDefineForm( |
|
71 |
status=NODEGROUP_STATUS.ACCEPTED, |
|
72 |
data={'name': name, 'uuid': uuid}) |
|
73 |
self.assertTrue(form.is_valid(), form._errors) |
|
74 |
nodegroup = form.save() |
|
75 |
self.assertEqual(NODEGROUP_STATUS.ACCEPTED, nodegroup.status) |
|
76 |
||
77 |
def test_validates_parameters(self): |
|
78 |
name = factory.make_name('name') |
|
79 |
too_long_uuid = 'test' * 30 |
|
80 |
form = NodeGroupDefineForm( |
|
81 |
data={'name': name, 'uuid': too_long_uuid}) |
|
82 |
self.assertFalse(form.is_valid()) |
|
83 |
self.assertEquals( |
|
84 |
{'uuid': |
|
85 |
['Ensure this value has at most 36 characters (it has 120).']}, |
|
86 |
form._errors) |
|
87 |
||
88 |
def test_rejects_invalid_json_interfaces(self): |
|
89 |
name = factory.make_name('name') |
|
90 |
uuid = factory.make_UUID() |
|
91 |
invalid_interfaces = factory.make_name('invalid_json_interfaces') |
|
92 |
form = NodeGroupDefineForm( |
|
93 |
data={ |
|
94 |
'name': name, 'uuid': uuid, 'interfaces': invalid_interfaces}) |
|
95 |
self.assertFalse(form.is_valid()) |
|
96 |
self.assertEquals( |
|
97 |
{'interfaces': ['Invalid json value.']}, |
|
98 |
form._errors) |
|
99 |
||
100 |
def test_rejects_invalid_list_interfaces(self): |
|
101 |
name = factory.make_name('name') |
|
102 |
uuid = factory.make_UUID() |
|
103 |
invalid_interfaces = json.dumps('invalid interface list') |
|
104 |
form = NodeGroupDefineForm( |
|
105 |
data={ |
|
106 |
'name': name, 'uuid': uuid, 'interfaces': invalid_interfaces}) |
|
107 |
self.assertFalse(form.is_valid()) |
|
108 |
self.assertEquals( |
|
109 |
{'interfaces': [INTERFACES_VALIDATION_ERROR_MESSAGE]}, |
|
110 |
form._errors) |
|
111 |
||
112 |
def test_rejects_invalid_interface(self): |
|
113 |
name = factory.make_name('name') |
|
114 |
uuid = factory.make_UUID() |
|
115 |
interface = factory.get_interface_fields() |
|
116 |
# Make the interface invalid.
|
|
117 |
interface['ip_range_high'] = 'invalid IP address' |
|
118 |
interfaces = json.dumps([interface]) |
|
119 |
form = NodeGroupDefineForm( |
|
120 |
data={'name': name, 'uuid': uuid, 'interfaces': interfaces}) |
|
121 |
self.assertFalse(form.is_valid()) |
|
122 |
self.assertIn( |
|
123 |
"Enter a valid IPv4 or IPv6 address", |
|
124 |
form._errors['interfaces'][0]) |
|
125 |
||
126 |
def test_creates_interface_from_params(self): |
|
127 |
name = factory.make_name('name') |
|
128 |
uuid = factory.make_UUID() |
|
129 |
interface = factory.get_interface_fields() |
|
130 |
interfaces = json.dumps([interface]) |
|
131 |
form = NodeGroupDefineForm( |
|
132 |
data={'name': name, 'uuid': uuid, 'interfaces': interfaces}) |
|
133 |
self.assertTrue(form.is_valid(), form._errors) |
|
134 |
form.save() |
|
135 |
nodegroup = NodeGroup.objects.get(uuid=uuid) |
|
136 |
# Replace empty strings with None as empty strings are converted into
|
|
137 |
# None for fields with null=True.
|
|
138 |
expected_result = { |
|
139 |
key: (value if value != '' else None) |
|
140 |
for key, value in interface.items() |
|
141 |
}
|
|
142 |
self.assertThat( |
|
143 |
nodegroup.nodegroupinterface_set.all()[0], |
|
144 |
MatchesStructure.byEquality(**expected_result)) |
|
145 |
||
146 |
def test_accepts_unnamed_cluster_interface(self): |
|
147 |
uuid = factory.make_UUID() |
|
148 |
interface = factory.get_interface_fields() |
|
149 |
del interface['name'] |
|
150 |
interfaces = json.dumps([interface]) |
|
151 |
form = NodeGroupDefineForm( |
|
152 |
data={ |
|
153 |
'name': factory.make_name('cluster'), |
|
154 |
'uuid': uuid, |
|
155 |
'interfaces': interfaces, |
|
156 |
})
|
|
157 |
self.assertTrue(form.is_valid(), form._errors) |
|
158 |
cluster = form.save() |
|
159 |
[cluster_interface] = cluster.nodegroupinterface_set.all() |
|
160 |
self.assertEqual(interface['interface'], cluster_interface.name) |
|
161 |
self.assertEqual(interface['interface'], cluster_interface.interface) |
|
162 |
||
163 |
def test_checks_against_conflicting_managed_networks(self): |
|
164 |
big_network = IPNetwork('10.0.0.0/255.255.0.0') |
|
165 |
nested_network = IPNetwork('10.0.100.0/255.255.255.0') |
|
166 |
managed = NODEGROUPINTERFACE_MANAGEMENT.DHCP |
|
167 |
form = NodeGroupDefineForm( |
|
168 |
data={ |
|
169 |
'name': factory.make_name('cluster'), |
|
170 |
'uuid': factory.make_UUID(), |
|
171 |
'interfaces': json.dumps([ |
|
172 |
factory.get_interface_fields( |
|
173 |
network=big_network, management=managed), |
|
174 |
factory.get_interface_fields( |
|
175 |
network=nested_network, management=managed), |
|
176 |
]),
|
|
177 |
})
|
|
178 |
self.assertFalse(form.is_valid()) |
|
179 |
self.assertNotEqual([], form._errors['interfaces']) |
|
180 |
self.assertThat( |
|
181 |
form._errors['interfaces'][0], |
|
182 |
StartsWith("Conflicting networks")) |
|
183 |
||
184 |
def test_ignores_conflicts_on_unmanaged_interfaces(self): |
|
185 |
big_network = IPNetwork('10.0.0.0/255.255.0.0') |
|
186 |
nested_network = IPNetwork('10.100.100.0/255.255.255.0') |
|
187 |
managed = NODEGROUPINTERFACE_MANAGEMENT.DHCP |
|
188 |
unmanaged = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED |
|
189 |
form = NodeGroupDefineForm( |
|
190 |
data={ |
|
191 |
'name': factory.make_name('cluster'), |
|
192 |
'uuid': factory.make_UUID(), |
|
193 |
'interfaces': json.dumps([ |
|
194 |
factory.get_interface_fields( |
|
195 |
network=big_network, management=managed), |
|
196 |
factory.get_interface_fields( |
|
197 |
network=nested_network, management=unmanaged), |
|
198 |
]),
|
|
199 |
})
|
|
200 |
is_valid = form.is_valid() |
|
201 |
self.assertEqual( |
|
202 |
(True, None), |
|
203 |
(is_valid, form._errors.get('interfaces'))) |
|
204 |
||
205 |
def test_creates_multiple_interfaces(self): |
|
206 |
name = factory.make_name('name') |
|
207 |
uuid = factory.make_UUID() |
|
208 |
interfaces = [ |
|
209 |
factory.get_interface_fields(management=management) |
|
210 |
for management in map_enum(NODEGROUPINTERFACE_MANAGEMENT).values() |
|
211 |
]
|
|
212 |
form = NodeGroupDefineForm( |
|
213 |
data={ |
|
214 |
'name': name, |
|
215 |
'uuid': uuid, |
|
216 |
'interfaces': json.dumps(interfaces), |
|
217 |
})
|
|
218 |
self.assertTrue(form.is_valid(), form._errors) |
|
219 |
form.save() |
|
220 |
nodegroup = NodeGroup.objects.get(uuid=uuid) |
|
221 |
self.assertEqual( |
|
222 |
len(interfaces), nodegroup.nodegroupinterface_set.count()) |
|
223 |
||
224 |
def test_populates_cluster_name_default(self): |
|
225 |
name = factory.make_name('name') |
|
226 |
uuid = factory.make_UUID() |
|
227 |
form = NodeGroupDefineForm( |
|
228 |
status=NODEGROUP_STATUS.ACCEPTED, |
|
229 |
data={'name': name, 'uuid': uuid}) |
|
230 |
self.assertTrue(form.is_valid(), form._errors) |
|
231 |
nodegroup = form.save() |
|
232 |
self.assertIn(uuid, nodegroup.cluster_name) |
|
233 |
||
234 |
def test_populates_cluster_name(self): |
|
235 |
cluster_name = factory.make_name('cluster_name') |
|
236 |
uuid = factory.make_UUID() |
|
237 |
form = NodeGroupDefineForm( |
|
238 |
status=NODEGROUP_STATUS.ACCEPTED, |
|
239 |
data={'cluster_name': cluster_name, 'uuid': uuid}) |
|
240 |
self.assertTrue(form.is_valid(), form._errors) |
|
241 |
nodegroup = form.save() |
|
242 |
self.assertEqual(cluster_name, nodegroup.cluster_name) |
|
243 |
||
244 |
def test_creates_unmanaged_interfaces(self): |
|
245 |
name = factory.make_name('name') |
|
246 |
uuid = factory.make_UUID() |
|
247 |
interface = factory.get_interface_fields() |
|
248 |
del interface['management'] |
|
249 |
interfaces = json.dumps([interface]) |
|
250 |
form = NodeGroupDefineForm( |
|
251 |
data={'name': name, 'uuid': uuid, 'interfaces': interfaces}) |
|
252 |
self.assertTrue(form.is_valid(), form._errors) |
|
253 |
form.save() |
|
254 |
uuid_nodegroup = NodeGroup.objects.get(uuid=uuid) |
|
255 |
self.assertEqual( |
|
256 |
[NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED], |
|
257 |
[
|
|
258 |
nodegroup.management for nodegroup in |
|
259 |
uuid_nodegroup.nodegroupinterface_set.all() |
|
260 |
])
|
|
261 |
||
262 |
def test_gives_disambiguation_preference_to_IPv4(self): |
|
263 |
network_interface = factory.make_name('eth', sep='') |
|
264 |
ipv4_network = factory.make_ipv4_network() |
|
265 |
# We'll be creating a cluster with two interfaces, both using the same
|
|
266 |
# network interface: an IPv4 one and an IPv6 one.
|
|
267 |
# We randomise the ordering of this list to rule out special treatment
|
|
268 |
# based on definition order.
|
|
269 |
interfaces = sorted( |
|
270 |
[
|
|
271 |
factory.get_interface_fields( |
|
272 |
network=factory.make_ipv6_network(slash=64), |
|
273 |
interface=network_interface), |
|
274 |
factory.get_interface_fields( |
|
275 |
network=ipv4_network, interface=network_interface), |
|
276 |
],
|
|
277 |
cmp=lambda left, right: randint(-1, 1)) |
|
278 |
# We're not going to pass names for these cluster interfaces, so the
|
|
279 |
# form will have to make some up based on the network interface name.
|
|
280 |
for definition in interfaces: |
|
281 |
del definition['name'] |
|
282 |
form = NodeGroupDefineForm( |
|
283 |
data={ |
|
284 |
'name': factory.make_name('cluster'), |
|
285 |
'uuid': factory.make_UUID(), |
|
286 |
'interfaces': json.dumps(interfaces), |
|
287 |
})
|
|
288 |
self.assertTrue(form.is_valid(), form._errors) |
|
289 |
cluster = form.save() |
|
290 |
# All of the cluster interfaces' names are unique and based on the
|
|
291 |
# network interface name, but the IPv4 one gets the unadorned name.
|
|
292 |
interfaces_by_name = { |
|
293 |
interface.name: interface |
|
294 |
for interface in cluster.nodegroupinterface_set.all() |
|
295 |
}
|
|
296 |
self.expectThat(interfaces_by_name, HasLength(len(interfaces))) |
|
297 |
self.assertIn(network_interface, interfaces_by_name) |
|
298 |
self.assertEqual( |
|
299 |
ipv4_network, |
|
300 |
interfaces_by_name[network_interface].network) |
|
301 |
||
302 |
||
303 |
class TestNodeGroupEdit(MAASServerTestCase): |
|
304 |
||
305 |
def make_form_data(self, nodegroup): |
|
306 |
"""Create `NodeGroupEdit` form data based on `nodegroup`."""
|
|
307 |
return { |
|
308 |
'name': nodegroup.name, |
|
309 |
'cluster_name': nodegroup.cluster_name, |
|
310 |
'status': nodegroup.status, |
|
311 |
}
|
|
312 |
||
313 |
def test_changes_name(self): |
|
314 |
nodegroup = factory.make_NodeGroup(name=factory.make_name('old-name')) |
|
315 |
new_name = factory.make_name('new-name') |
|
316 |
data = self.make_form_data(nodegroup) |
|
317 |
data['name'] = new_name |
|
318 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
319 |
self.assertTrue(form.is_valid()) |
|
320 |
form.save() |
|
321 |
self.assertEqual(new_name, reload_object(nodegroup).name) |
|
322 |
||
323 |
def test_refuses_name_change_if_dns_managed_and_nodes_in_use(self): |
|
324 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
325 |
data = self.make_form_data(nodegroup) |
|
326 |
data['name'] = factory.make_name('new-name') |
|
327 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
328 |
self.assertFalse(form.is_valid()) |
|
329 |
||
330 |
def test_accepts_unchanged_name(self): |
|
331 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
332 |
original_name = nodegroup.name |
|
333 |
form = NodeGroupEdit( |
|
334 |
instance=nodegroup, data=self.make_form_data(nodegroup)) |
|
335 |
self.assertTrue(form.is_valid()) |
|
336 |
form.save() |
|
337 |
self.assertEqual(original_name, reload_object(nodegroup).name) |
|
338 |
||
339 |
def test_accepts_omitted_name(self): |
|
340 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
341 |
original_name = nodegroup.name |
|
342 |
data = self.make_form_data(nodegroup) |
|
343 |
del data['name'] |
|
344 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
345 |
self.assertTrue(form.is_valid()) |
|
346 |
form.save() |
|
347 |
self.assertEqual(original_name, reload_object(nodegroup).name) |
|
348 |
||
349 |
def test_accepts_name_change_if_nodegroup_not_accepted(self): |
|
350 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
351 |
nodegroup.status = NODEGROUP_STATUS.PENDING |
|
352 |
data = self.make_form_data(nodegroup) |
|
353 |
data['name'] = factory.make_name('new-name') |
|
354 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
355 |
self.assertTrue(form.is_valid()) |
|
356 |
||
357 |
def test_accepts_name_change_if_dns_managed_but_no_nodes_in_use(self): |
|
358 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
359 |
node.status = NODE_STATUS.READY |
|
360 |
node.save() |
|
361 |
data = self.make_form_data(nodegroup) |
|
362 |
data['name'] = factory.make_name('new-name') |
|
363 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
364 |
self.assertTrue(form.is_valid()) |
|
365 |
form.save() |
|
366 |
self.assertEqual(data['name'], reload_object(nodegroup).name) |
|
367 |
||
368 |
def test_accepts_name_change_if_nodes_in_use_but_dns_not_managed(self): |
|
369 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
370 |
[interface] = nodegroup.get_managed_interfaces() |
|
371 |
interface.management = NODEGROUPINTERFACE_MANAGEMENT.DHCP |
|
372 |
interface.save() |
|
373 |
data = self.make_form_data(nodegroup) |
|
374 |
data['name'] = factory.make_name('new-name') |
|
375 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
376 |
self.assertTrue(form.is_valid()) |
|
377 |
form.save() |
|
378 |
self.assertEqual(data['name'], reload_object(nodegroup).name) |
|
379 |
||
380 |
def test_accepts_name_change_if_nodegroup_has_no_interface(self): |
|
381 |
nodegroup, node = factory.make_unrenamable_NodeGroup_with_Node() |
|
382 |
NodeGroupInterface.objects.filter(nodegroup=nodegroup).delete() |
|
383 |
data = self.make_form_data(nodegroup) |
|
384 |
data['name'] = factory.make_name('new-name') |
|
385 |
form = NodeGroupEdit(instance=nodegroup, data=data) |
|
386 |
self.assertTrue(form.is_valid()) |
|
387 |
form.save() |
|
388 |
self.assertEqual(data['name'], reload_object(nodegroup).name) |
|
389 |
||
390 |
def test_shows_default_disable_ipv4_if_managed_ipv6_configured(self): |
|
391 |
nodegroup = factory.make_NodeGroup() |
|
392 |
factory.make_NodeGroupInterface( |
|
393 |
nodegroup, network=factory.make_ipv6_network(), |
|
394 |
management=NODEGROUPINTERFACE_MANAGEMENT.DHCP) |
|
395 |
form = NodeGroupEdit(instance=nodegroup) |
|
396 |
self.assertIsInstance( |
|
397 |
form.fields['default_disable_ipv4'].widget, CheckboxInput) |
|
398 |
||
399 |
def test_hides_default_disable_ipv4_if_no_managed_ipv6_configured(self): |
|
400 |
nodegroup = factory.make_NodeGroup() |
|
401 |
eth = factory.make_name('eth') |
|
402 |
factory.make_NodeGroupInterface( |
|
403 |
nodegroup, network=factory.make_ipv4_network(), interface=eth, |
|
404 |
management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
|
405 |
factory.make_NodeGroupInterface( |
|
406 |
nodegroup, network=factory.make_ipv6_network(), interface=eth, |
|
407 |
management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED) |
|
408 |
form = NodeGroupEdit(instance=nodegroup) |
|
409 |
self.assertIsInstance( |
|
410 |
form.fields['default_disable_ipv4'].widget, HiddenInput) |
|
411 |
||
412 |
def test_default_disable_ipv4_field_ignores_other_nodegroups(self): |
|
413 |
factory.make_NodeGroupInterface( |
|
414 |
factory.make_NodeGroup(), network=factory.make_ipv6_network(), |
|
415 |
management=NODEGROUPINTERFACE_MANAGEMENT.DHCP) |
|
416 |
nodegroup = factory.make_NodeGroup() |
|
417 |
form = NodeGroupEdit(instance=nodegroup) |
|
418 |
self.assertIsInstance( |
|
419 |
form.fields['default_disable_ipv4'].widget, HiddenInput) |