~ubuntu-branches/ubuntu/utopic/maas/utopic-security

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)