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

« back to all changes in this revision

Viewing changes to src/maasserver/tests/test_forms_nodegroup.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez, Jeroen Vermeulen, Andres Rodriguez, Jason Hobbs, Raphaël Badin, Louis Bouchard, Gavin Panella
  • Date: 2014-08-21 19:36:30 UTC
  • mfrom: (1.3.1)
  • Revision ID: package-import@ubuntu.com-20140821193630-kertpu5hd8yyss8h
Tags: 1.7.0~beta7+bzr3266-0ubuntu1
* New Upstream Snapshot, Beta 7 bzr3266

[ Jeroen Vermeulen ]
* debian/extras/99-maas-sudoers
  debian/maas-dhcp.postinst
  debian/rules
  - Add second DHCP server instance for IPv6.
* debian/maas-region-controller-min.install
  debian/maas-region-controller-min.lintian-overrides
  - Install deployment user-data: maas_configure_interfaces.py script.
* debian/maas-cluster-controller.links
  debian/maas-cluster-controller.install
  debian/maas-cluster-controller.postinst
  - Reflect Celery removal changes made in trunk r3067.
  - Don't install celeryconfig_cluster.py any longer. 
  - Don't install maas_local_celeryconfig_cluster.py any longer.
  - Don't symlink maas_local_celeryconfig_cluster.py from /etc to /usr.
  - Don't insert UUID into maas_local_celeryconfig_cluster.py.

[ Andres Rodriguez ]
* debian/maas-region-controller-min.postrm: Cleanup lefover files.
* debian/maas-dhcp.postrm: Clean leftover configs.
* Provide new maas-proxy package that replaces the usage of
  squid-deb-proxy:
  - debian/control: New maas-proxy package that replaces the usage
    of squid-deb-proxy; Drop depends on squid-deb-proxy.
  - Add upstrart job.
  - Ensure squid3 is stopped as maas-proxy uses a caching proxy.
* Remove Celery references to cluster controller:
  - Rename upstart job from maas-pserv to maas-cluster; rename
    maas-cluster-celery to maas-cluster-register. Ensure services
    are stopped on upgrade.
  - debian/maintscript: Cleanup config files.
  - Remove all references to the MAAS celery daemon and config
    files as we don't use it like that anymore
* Move some entries in debian/maintscript to
  debian/maas-cluster-controller.maintscript
* Remove usage of txlongpoll and rabbitmq-server. Handle upgrades
  to ensure these are removed correctly.

[ Jason Hobbs ]
* debian/maas-region-controller-min.install: Install
  maas-generate-winrm-cert script.

[ Raphaël Badin ]
* debian/extras/maas-region-admin: Bypass django-admin as it prints
  spurious messages to stdout (LP: #1365130).

[Louis Bouchard]
* debian/maas-cluster-controller.postinst:
  - Exclude /var/log/maas/rsyslog when changing ownership
    (LP: #1346703)

[Gavin Panella]
* debian/maas-cluster-controller.maas-clusterd.upstart:
  - Don't start-up the cluster controller unless a shared-secret has
    been installed.
* debian/maas-cluster-controller.maas-cluster-register.upstart: Drop.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)