1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for nodegroup forms."""
6
from __future__ import (
18
from random import randint
20
from django.forms import (
24
from maasserver.enum import (
27
NODEGROUPINTERFACE_MANAGEMENT,
29
from maasserver.forms import (
30
INTERFACES_VALIDATION_ERROR_MESSAGE,
34
from maasserver.models import (
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 (
50
class TestNodeGroupDefineForm(MAASServerTestCase):
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()
59
(uuid, name, NODEGROUP_STATUS.PENDING, 0),
64
nodegroup.nodegroupinterface_set.count(),
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)
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())
85
['Ensure this value has at most 36 characters (it has 120).']},
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(
94
'name': name, 'uuid': uuid, 'interfaces': invalid_interfaces})
95
self.assertFalse(form.is_valid())
97
{'interfaces': ['Invalid json value.']},
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(
106
'name': name, 'uuid': uuid, 'interfaces': invalid_interfaces})
107
self.assertFalse(form.is_valid())
109
{'interfaces': [INTERFACES_VALIDATION_ERROR_MESSAGE]},
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())
123
"Enter a valid IPv4 or IPv6 address",
124
form._errors['interfaces'][0])
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)
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.
139
key: (value if value != '' else None)
140
for key, value in interface.items()
143
nodegroup.nodegroupinterface_set.all()[0],
144
MatchesStructure.byEquality(**expected_result))
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(
153
'name': factory.make_name('cluster'),
155
'interfaces': interfaces,
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)
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(
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),
178
self.assertFalse(form.is_valid())
179
self.assertNotEqual([], form._errors['interfaces'])
181
form._errors['interfaces'][0],
182
StartsWith("Conflicting networks"))
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(
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),
200
is_valid = form.is_valid()
203
(is_valid, form._errors.get('interfaces')))
205
def test_creates_multiple_interfaces(self):
206
name = factory.make_name('name')
207
uuid = factory.make_UUID()
209
factory.get_interface_fields(management=management)
210
for management in map_enum(NODEGROUPINTERFACE_MANAGEMENT).values()
212
form = NodeGroupDefineForm(
216
'interfaces': json.dumps(interfaces),
218
self.assertTrue(form.is_valid(), form._errors)
220
nodegroup = NodeGroup.objects.get(uuid=uuid)
222
len(interfaces), nodegroup.nodegroupinterface_set.count())
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)
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)
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)
254
uuid_nodegroup = NodeGroup.objects.get(uuid=uuid)
256
[NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED],
258
nodegroup.management for nodegroup in
259
uuid_nodegroup.nodegroupinterface_set.all()
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.
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),
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(
284
'name': factory.make_name('cluster'),
285
'uuid': factory.make_UUID(),
286
'interfaces': json.dumps(interfaces),
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()
296
self.expectThat(interfaces_by_name, HasLength(len(interfaces)))
297
self.assertIn(network_interface, interfaces_by_name)
300
interfaces_by_name[network_interface].network)
303
class TestNodeGroupEdit(MAASServerTestCase):
305
def make_form_data(self, nodegroup):
306
"""Create `NodeGroupEdit` form data based on `nodegroup`."""
308
'name': nodegroup.name,
309
'cluster_name': nodegroup.cluster_name,
310
'status': nodegroup.status,
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())
321
self.assertEqual(new_name, reload_object(nodegroup).name)
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())
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())
337
self.assertEqual(original_name, reload_object(nodegroup).name)
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)
344
form = NodeGroupEdit(instance=nodegroup, data=data)
345
self.assertTrue(form.is_valid())
347
self.assertEqual(original_name, reload_object(nodegroup).name)
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())
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
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())
366
self.assertEqual(data['name'], reload_object(nodegroup).name)
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
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())
378
self.assertEqual(data['name'], reload_object(nodegroup).name)
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())
388
self.assertEqual(data['name'], reload_object(nodegroup).name)
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)
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)
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)