~rvb/maas/transaction-1.7-bug-1409852

« back to all changes in this revision

Viewing changes to src/maasserver/models/tests/test_macaddress.py

merged upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
__all__ = []
16
16
 
17
17
from operator import attrgetter
 
18
import random
18
19
 
19
20
from django.core.exceptions import ValidationError
20
21
from maasserver.enum import (
22
23
    NODEGROUPINTERFACE_MANAGEMENT,
23
24
    )
24
25
from maasserver.exceptions import StaticIPAddressTypeClash
 
26
from maasserver.forms import create_Network_from_NodeGroupInterface
25
27
from maasserver.models import (
26
 
    MACAddress,
27
28
    NodeGroupInterface,
28
29
    StaticIPAddress,
29
30
    )
30
31
from maasserver.models.macaddress import (
31
32
    find_cluster_interface_responsible_for_ip,
 
33
    MACAddress,
 
34
    update_mac_cluster_interfaces,
32
35
    )
 
36
from maasserver.models.network import Network
33
37
from maasserver.testing.factory import factory
34
38
from maasserver.testing.orm import reload_object
35
39
from maasserver.testing.testcase import MAASServerTestCase
36
40
from netaddr import (
37
41
    IPAddress,
38
42
    IPNetwork,
 
43
    IPRange,
39
44
    )
40
45
from testtools.matchers import (
41
46
    Equals,
42
47
    HasLength,
 
48
    MatchesStructure,
43
49
    )
44
50
 
45
51
 
 
52
def get_random_ip_from_interface_range(interface, use_static_range=None):
 
53
    """Return a random IP from the pool available to an interface.
 
54
 
 
55
    :return: An IP address as a string."""
 
56
    if use_static_range:
 
57
        ip_range = IPRange(
 
58
            interface.static_ip_range_low, interface.static_ip_range_high)
 
59
    else:
 
60
        ip_range = IPRange(
 
61
            interface.ip_range_low, interface.ip_range_high)
 
62
    chosen_ip = random.choice(ip_range)
 
63
    return unicode(chosen_ip)
 
64
 
 
65
 
46
66
class MACAddressTest(MAASServerTestCase):
47
67
 
48
68
    def test_stores_to_database(self):
49
 
        mac = factory.make_mac_address()
 
69
        mac = factory.make_MACAddress()
50
70
        self.assertEqual([mac], list(MACAddress.objects.all()))
51
71
 
52
72
    def test_invalid_address_raises_validation_error(self):
53
73
        mac = MACAddress(
54
 
            mac_address='aa:bb:ccxdd:ee:ff', node=factory.make_node())
 
74
            mac_address='aa:bb:ccxdd:ee:ff', node=factory.make_Node())
55
75
        self.assertRaises(ValidationError, mac.full_clean)
56
76
 
57
77
    def test_mac_not_in_any_network_by_default(self):
58
 
        mac = factory.make_mac_address()
 
78
        mac = factory.make_MACAddress()
59
79
        self.assertItemsEqual([], mac.networks.all())
60
80
 
61
81
    def test_mac_can_be_connected_to_multiple_networks(self):
62
 
        networks = factory.make_networks(3)
63
 
        mac = factory.make_mac_address(networks=networks)
 
82
        networks = factory.make_Networks(3)
 
83
        mac = factory.make_MACAddress(networks=networks)
64
84
        self.assertItemsEqual(networks, reload_object(mac).networks.all())
65
85
 
66
86
    def test_get_networks_returns_empty_if_no_networks(self):
67
 
        mac = factory.make_mac_address(networks=[])
 
87
        mac = factory.make_MACAddress(networks=[])
68
88
        self.assertEqual([], list(mac.get_networks()))
69
89
 
70
90
    def test_get_networks_returns_networks(self):
71
 
        network = factory.make_network()
72
 
        mac = factory.make_mac_address(networks=[network])
 
91
        network = factory.make_Network()
 
92
        mac = factory.make_MACAddress(networks=[network])
73
93
        self.assertEqual([network], list(mac.get_networks()))
74
94
 
75
95
    def test_get_networks_sorts_by_network_name(self):
76
 
        networks = factory.make_networks(3, sortable_name=True)
77
 
        mac = factory.make_mac_address(networks=networks)
 
96
        networks = factory.make_Networks(3, sortable_name=True)
 
97
        mac = factory.make_MACAddress(networks=networks)
78
98
        self.assertEqual(
79
99
            sorted(networks, key=attrgetter('name')),
80
100
            list(mac.get_networks()))
85
105
        # handles this.
86
106
        mac_str = "aa:bb:cc:dd:ee:ff"
87
107
        mac = MACAddress(
88
 
            mac_address=mac_str, node=factory.make_node())
 
108
            mac_address=mac_str, node=factory.make_Node())
89
109
        self.assertEqual(mac_str, unicode(mac))
90
110
 
91
111
    def test_unicode_copes_with_unclean_bytes_mac_address(self):
94
114
        # handles this.
95
115
        bytes_mac = bytes("aa:bb:cc:dd:ee:ff")
96
116
        mac = MACAddress(
97
 
            mac_address=bytes_mac, node=factory.make_node())
 
117
            mac_address=bytes_mac, node=factory.make_Node())
98
118
        self.assertEqual(bytes_mac, mac.__str__())
99
119
 
100
120
 
106
126
        self.assertIsNone(find_cluster_interface_responsible_for_ip([], ip))
107
127
 
108
128
    def test__finds_responsible_IPv4_interface(self):
109
 
        nodegroup = factory.make_node_group()
 
129
        nodegroup = factory.make_NodeGroup()
110
130
        networks = [
111
131
            IPNetwork('10.1.1.0/24'),
112
132
            IPNetwork('10.2.2.0/24'),
113
133
            IPNetwork('10.3.3.0/24'),
114
134
            ]
115
135
        interfaces = [
116
 
            factory.make_node_group_interface(nodegroup, network=network)
 
136
            factory.make_NodeGroupInterface(nodegroup, network=network)
117
137
            for network in networks
118
138
            ]
119
139
        self.assertEqual(
122
142
                interfaces, IPAddress('10.2.2.100')))
123
143
 
124
144
    def test__finds_responsible_IPv6_interface(self):
125
 
        nodegroup = factory.make_node_group()
 
145
        nodegroup = factory.make_NodeGroup()
126
146
        networks = [
127
147
            IPNetwork('2001:1::/64'),
128
148
            IPNetwork('2001:2::/64'),
129
149
            IPNetwork('2001:3::/64'),
130
150
            ]
131
151
        interfaces = [
132
 
            factory.make_node_group_interface(nodegroup, network=network)
 
152
            factory.make_NodeGroupInterface(nodegroup, network=network)
133
153
            for network in networks
134
154
            ]
135
155
        self.assertEqual(
138
158
                interfaces, IPAddress('2001:2::100')))
139
159
 
140
160
    def test__returns_None_if_none_match(self):
141
 
        nodegroup = factory.make_node_group()
 
161
        nodegroup = factory.make_NodeGroup()
142
162
        networks = [
143
163
            IPNetwork('10.1.1.0/24'),
144
164
            IPNetwork('10.2.2.0/24'),
145
165
            IPNetwork('10.3.3.0/24'),
146
166
            ]
147
167
        interfaces = [
148
 
            factory.make_node_group_interface(nodegroup, network=network)
 
168
            factory.make_NodeGroupInterface(nodegroup, network=network)
149
169
            for network in networks
150
170
            ]
151
171
        self.assertIsNone(
153
173
                interfaces, IPAddress('10.9.9.1')))
154
174
 
155
175
    def test__combines_IPv4_and_IPv6(self):
156
 
        nodegroup = factory.make_node_group()
 
176
        nodegroup = factory.make_NodeGroup()
157
177
        networks = [
158
178
            IPNetwork('10.1.1.0/24'),
159
179
            IPNetwork('2001:2::/64'),
160
180
            IPNetwork('10.3.3.0/24'),
161
181
            ]
162
182
        interfaces = [
163
 
            factory.make_node_group_interface(nodegroup, network=network)
 
183
            factory.make_NodeGroupInterface(nodegroup, network=network)
164
184
            for network in networks
165
185
            ]
166
186
        self.assertEqual(
195
215
    # The IPv6 cluster interface must be for the same network interface
196
216
    # as the IPv4 one; that's how we know the MAC is attached to both.
197
217
    ipv4_interface = find_cluster_interface(node.nodegroup, 4)
198
 
    factory.make_node_group_interface(
 
218
    factory.make_NodeGroupInterface(
199
219
        node.nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
200
220
        network=ipv6_network, interface=ipv4_interface.interface)
201
221
    return node
205
225
    """Tests for `_map_allocated_addresses`."""
206
226
 
207
227
    def test__returns_empty_if_no_interfaces_given(self):
208
 
        mac = factory.make_mac_address()
 
228
        mac = factory.make_MACAddress()
209
229
        self.assertEqual({}, mac._map_allocated_addresses([]))
210
230
 
211
231
    def test__maps_interface_without_allocation_to_None(self):
212
 
        cluster = factory.make_node_group()
213
 
        interface = factory.make_node_group_interface(cluster)
214
 
        mac = factory.make_mac_address(cluster_interface=interface)
 
232
        cluster = factory.make_NodeGroup()
 
233
        interface = factory.make_NodeGroupInterface(cluster)
 
234
        mac = factory.make_MACAddress(cluster_interface=interface)
215
235
        self.assertEqual(
216
236
            {interface: None},
217
237
            mac._map_allocated_addresses([interface]))
221
241
            network=factory.getRandomNetwork())
222
242
        interface = find_cluster_interface(node.nodegroup, 4)
223
243
        mac = node.get_primary_mac()
224
 
        sip = factory.make_staticipaddress(
 
244
        sip = factory.make_StaticIPAddress(
225
245
            mac=mac, ip=interface.static_ip_range_low)
226
246
 
227
247
        self.assertEqual(
232
252
            network=factory.make_ipv6_network())
233
253
        interface = find_cluster_interface(node.nodegroup, 6)
234
254
        mac = node.get_primary_mac()
235
 
        sip = factory.make_staticipaddress(
 
255
        sip = factory.make_StaticIPAddress(
236
256
            mac=mac, ip=interface.static_ip_range_low)
237
257
 
238
258
        self.assertEqual(
243
263
        mac = node.get_primary_mac()
244
264
        ipv4_interface = find_cluster_interface(node.nodegroup, 4)
245
265
        ipv6_interface = find_cluster_interface(node.nodegroup, 6)
246
 
        factory.make_staticipaddress(
 
266
        factory.make_StaticIPAddress(
247
267
            mac=mac, ip=ipv4_interface.static_ip_range_low)
248
 
        ipv6_sip = factory.make_staticipaddress(
 
268
        ipv6_sip = factory.make_StaticIPAddress(
249
269
            mac=mac, ip=ipv6_interface.static_ip_range_low)
250
270
 
251
271
        self.assertEqual(
318
338
 
319
339
    def test__returns_empty_if_no_cluster_interface(self):
320
340
        # If mac.cluster_interface is None, we can't allocate any IP.
321
 
        mac = factory.make_mac_address()
 
341
        mac = factory.make_MACAddress()
322
342
        self.assertEquals([], mac.claim_static_ips())
323
343
 
324
344
    def test__reserves_an_ip_address(self):
622
642
    def test__returns_nothing_if_none_known(self):
623
643
        self.assertItemsEqual(
624
644
            [],
625
 
            factory.make_mac_address().get_cluster_interfaces())
 
645
            factory.make_MACAddress().get_cluster_interfaces())
626
646
 
627
647
    def test__returns_cluster_interface_if_known(self):
628
 
        cluster = factory.make_node_group()
629
 
        cluster_interface = factory.make_node_group_interface(cluster)
630
 
        mac = factory.make_mac_address(cluster_interface=cluster_interface)
 
648
        cluster = factory.make_NodeGroup()
 
649
        cluster_interface = factory.make_NodeGroupInterface(cluster)
 
650
        mac = factory.make_MACAddress(cluster_interface=cluster_interface)
631
651
        self.assertItemsEqual(
632
652
            [cluster_interface],
633
653
            mac.get_cluster_interfaces())
638
658
        # both those cluster interfaces are included.
639
659
        # XXX jtv 2014-08-18 bug=1358130: The way we look up the IPv6 interface
640
660
        # from the IPv4 one is set to change.  It may affect this test.
641
 
        cluster = factory.make_node_group()
 
661
        cluster = factory.make_NodeGroup()
642
662
        network_interface = factory.make_name('eth', sep='')
643
 
        ipv4_interface = factory.make_node_group_interface(
 
663
        ipv4_interface = factory.make_NodeGroupInterface(
644
664
            nodegroup=cluster, network=factory.getRandomNetwork(),
645
665
            interface=network_interface)
646
 
        ipv6_interface = factory.make_node_group_interface(
 
666
        ipv6_interface = factory.make_NodeGroupInterface(
647
667
            nodegroup=cluster, network=factory.make_ipv6_network(),
648
668
            interface=network_interface)
649
 
        mac = factory.make_mac_address(cluster_interface=ipv4_interface)
 
669
        mac = factory.make_MACAddress(cluster_interface=ipv4_interface)
650
670
        self.assertItemsEqual(
651
671
            [ipv4_interface, ipv6_interface],
652
672
            mac.get_cluster_interfaces())
653
673
 
654
674
    def test__ignores_other_cluster_interfaces(self):
655
 
        cluster = factory.make_node_group()
656
 
        factory.make_node_group_interface(
 
675
        cluster = factory.make_NodeGroup()
 
676
        factory.make_NodeGroupInterface(
657
677
            nodegroup=cluster, network=factory.getRandomNetwork())
658
 
        factory.make_node_group_interface(
 
678
        factory.make_NodeGroupInterface(
659
679
            nodegroup=cluster, network=factory.make_ipv6_network())
660
 
        node = factory.make_node(nodegroup=cluster)
 
680
        node = factory.make_Node(nodegroup=cluster)
661
681
        self.assertItemsEqual(
662
682
            [],
663
 
            factory.make_mac_address(node=node).get_cluster_interfaces())
 
683
            factory.make_MACAddress(node=node).get_cluster_interfaces())
664
684
 
665
685
    def test__ignores_other_clusters(self):
666
 
        my_cluster = factory.make_node_group()
667
 
        unrelated_cluster = factory.make_node_group()
668
 
        my_interface = factory.make_node_group_interface(
 
686
        my_cluster = factory.make_NodeGroup()
 
687
        unrelated_cluster = factory.make_NodeGroup()
 
688
        my_interface = factory.make_NodeGroupInterface(
669
689
            my_cluster, network=factory.getRandomNetwork(),
670
690
            name='eth0', interface='eth0')
671
 
        factory.make_node_group_interface(
 
691
        factory.make_NodeGroupInterface(
672
692
            unrelated_cluster, network=factory.make_ipv6_network(),
673
693
            name='eth0', interface='eth0')
674
 
        my_node = factory.make_node(nodegroup=my_cluster)
675
 
        my_mac = factory.make_mac_address(
 
694
        my_node = factory.make_Node(nodegroup=my_cluster)
 
695
        my_mac = factory.make_MACAddress(
676
696
            node=my_node, cluster_interface=my_interface)
677
697
        self.assertItemsEqual([my_interface], my_mac.get_cluster_interfaces())
 
698
 
 
699
 
 
700
class TestUpdateMacClusterInterfaces(MAASServerTestCase):
 
701
    """Tests for `update_mac_cluster_interfaces`()."""
 
702
 
 
703
    def make_cluster_with_macs_and_leases(self, use_static_range=False):
 
704
        cluster = factory.make_NodeGroup()
 
705
        mac_addresses = {
 
706
            factory.make_MACAddress(): factory.make_NodeGroupInterface(
 
707
                nodegroup=cluster)
 
708
            for i in range(4)
 
709
            }
 
710
        leases = {
 
711
            get_random_ip_from_interface_range(interface, use_static_range): (
 
712
                mac_address.mac_address)
 
713
            for mac_address, interface in mac_addresses.viewitems()
 
714
        }
 
715
        return cluster, mac_addresses, leases
 
716
 
 
717
    def test_updates_mac_cluster_interfaces(self):
 
718
        cluster, mac_addresses, leases = (
 
719
            self.make_cluster_with_macs_and_leases())
 
720
        update_mac_cluster_interfaces(leases, cluster)
 
721
        results = {
 
722
            mac_address: mac_address.cluster_interface
 
723
            for mac_address in MACAddress.objects.filter(
 
724
                mac_address__in=leases.values())
 
725
            }
 
726
        self.assertEqual(mac_addresses, results)
 
727
 
 
728
    def test_considers_static_range_when_updating_interfaces(self):
 
729
        cluster, mac_addresses, leases = (
 
730
            self.make_cluster_with_macs_and_leases(use_static_range=True))
 
731
        update_mac_cluster_interfaces(leases, cluster)
 
732
        results = {
 
733
            mac_address: mac_address.cluster_interface
 
734
            for mac_address in MACAddress.objects.filter(
 
735
                mac_address__in=leases.values())
 
736
            }
 
737
        self.assertEqual(mac_addresses, results)
 
738
 
 
739
    def test_updates_network_relations(self):
 
740
        # update_mac_cluster_interfaces should also associate the mac
 
741
        # with the network on which it resides.
 
742
        cluster, mac_addresses, leases = (
 
743
            self.make_cluster_with_macs_and_leases())
 
744
        expected_relations = []
 
745
        for mac, interface in mac_addresses.viewitems():
 
746
            net = create_Network_from_NodeGroupInterface(interface)
 
747
            expected_relations.append((net, mac))
 
748
        update_mac_cluster_interfaces(leases, cluster)
 
749
        # Doing a single giant comparison here would be unintuitive and
 
750
        # fugly, so I'm iterating.
 
751
        for net, mac in expected_relations:
 
752
            [observed_macddress] = net.macaddress_set.all()
 
753
            self.expectThat(mac, Equals(observed_macddress))
 
754
            interface = mac_addresses[mac]
 
755
            self.expectThat(
 
756
                net, MatchesStructure.byEquality(
 
757
                    default_gateway=interface.router_ip,
 
758
                    netmask=interface.subnet_mask,
 
759
                ))
 
760
 
 
761
    def test_does_not_overwrite_network_with_same_name(self):
 
762
        cluster = factory.make_NodeGroup()
 
763
        ngi = factory.make_NodeGroupInterface(nodegroup=cluster)
 
764
        net1 = create_Network_from_NodeGroupInterface(ngi)
 
765
 
 
766
        other_ngi = factory.make_NodeGroupInterface(nodegroup=cluster)
 
767
        other_ngi.interface = ngi.interface
 
768
        net2 = create_Network_from_NodeGroupInterface(ngi)
 
769
        self.assertEqual(None, net2)
 
770
        self.assertItemsEqual([net1], Network.objects.all())
 
771
 
 
772
    def test_ignores_mac_not_attached_to_cluster(self):
 
773
        cluster = factory.make_NodeGroup()
 
774
        mac_address = factory.make_MACAddress()
 
775
        leases = {
 
776
            factory.getRandomIPAddress(): mac_address.mac_address
 
777
            }
 
778
        update_mac_cluster_interfaces(leases, cluster)
 
779
        mac_address = MACAddress.objects.get(
 
780
            id=mac_address.id)
 
781
        self.assertIsNone(mac_address.cluster_interface)
 
782
 
 
783
    def test_ignores_unknown_macs(self):
 
784
        cluster = factory.make_NodeGroup()
 
785
        mac_address = factory.getRandomMACAddress()
 
786
        leases = {
 
787
            factory.getRandomIPAddress(): mac_address
 
788
            }
 
789
        # This is a test to show that update_mac_cluster_interfaces()
 
790
        # doesn't raise an Http404 when it comes across something it
 
791
        # doesn't know, hence the lack of meaningful assertions.
 
792
        update_mac_cluster_interfaces(leases, cluster)
 
793
        self.assertFalse(
 
794
            MACAddress.objects.filter(mac_address=mac_address).exists())
 
795
 
 
796
    def test_ignores_unconfigured_interfaces(self):
 
797
        cluster = factory.make_NodeGroup()
 
798
        factory.make_NodeGroupInterface(
 
799
            nodegroup=cluster, subnet_mask='', broadcast_ip='',
 
800
            static_ip_range_low='', static_ip_range_high='',
 
801
            ip_range_low='', ip_range_high='', router_ip='',
 
802
            ip=factory.getRandomIPAddress(),
 
803
            management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
 
804
        mac_address = factory.getRandomMACAddress()
 
805
        leases = {
 
806
            factory.getRandomIPAddress(): mac_address
 
807
            }
 
808
        self.assertIsNone(update_mac_cluster_interfaces(leases, cluster))
 
809
        # The real test is that update_mac_cluster_interfaces() doesn't
 
810
        # stacktrace because of the unconfigured interface (see bug
 
811
        # 1332596).