Merge lp:~mpontillo/maas/fix-discovered-ip-uniqueness--bug-1686736 into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 6028
Proposed branch: lp:~mpontillo/maas/fix-discovered-ip-uniqueness--bug-1686736
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 106 lines (+77/-1)
3 files modified
src/maasserver/migrations/builtin/maasserver/0121_relax_staticipaddress_unique_constraint.py (+36/-0)
src/maasserver/models/staticipaddress.py (+2/-1)
src/maasserver/models/tests/test_staticipaddress.py (+39/-0)
To merge this branch: bzr merge lp:~mpontillo/maas/fix-discovered-ip-uniqueness--bug-1686736
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+323343@code.launchpad.net

Commit message

Relax unique constraint on StaticIPAddress model to allow DISCOVERED IP addresses to coexist with other types.

 * Switches from unique=True on the 'ip' field to Django's unique_together.
 * Adds a partial index to allow only DISCOVERED IP addresses to coexist with other allocation types (all the other types' addresses still must be unique among themselves).

Description of the change

This was also tested manually as follows:

    http://paste.ubuntu.com/24467822/

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'src/maasserver/migrations/builtin/maasserver/0121_relax_staticipaddress_unique_constraint.py'
2--- src/maasserver/migrations/builtin/maasserver/0121_relax_staticipaddress_unique_constraint.py 1970-01-01 00:00:00 +0000
3+++ src/maasserver/migrations/builtin/maasserver/0121_relax_staticipaddress_unique_constraint.py 2017-04-27 18:18:29 +0000
4@@ -0,0 +1,36 @@
5+# -*- coding: utf-8 -*-
6+from __future__ import unicode_literals
7+
8+from django.db import (
9+ migrations,
10+ models,
11+)
12+import maasserver.fields
13+
14+# IPs must be unique across the entire table, except DISCOVERED addresses
15+# (which are type 6). The unique_together ensures that IP addresses are also
16+# unique amongst their own alloc_type. This index strengthens that.
17+staticipaddress_unique_index_create = (
18+ "CREATE UNIQUE INDEX maasserver_staticipaddress__discovered_unique"
19+ " ON maasserver_staticipaddress (ip)"
20+ " WHERE alloc_type != 6"
21+)
22+
23+class Migration(migrations.Migration):
24+
25+ dependencies = [
26+ ('maasserver', '0120_bootsourcecache_extra'),
27+ ]
28+
29+ operations = [
30+ migrations.AlterField(
31+ model_name='staticipaddress',
32+ name='ip',
33+ field=maasserver.fields.MAASIPAddressField(editable=False, verbose_name='IP', blank=True, null=True, default=None),
34+ ),
35+ migrations.AlterUniqueTogether(
36+ name='staticipaddress',
37+ unique_together=set([('alloc_type', 'ip')]),
38+ ),
39+ migrations.RunSQL(staticipaddress_unique_index_create),
40+ ]
41
42=== modified file 'src/maasserver/models/staticipaddress.py'
43--- src/maasserver/models/staticipaddress.py 2017-04-06 18:19:08 +0000
44+++ src/maasserver/models/staticipaddress.py 2017-04-27 18:18:29 +0000
45@@ -668,12 +668,13 @@
46 class Meta(DefaultMeta):
47 verbose_name = "Static IP Address"
48 verbose_name_plural = "Static IP Addresses"
49+ unique_together = ('alloc_type', 'ip')
50
51 # IP can be none when a DHCP lease has expired: in this case the entry
52 # in the StaticIPAddress only materializes the connection between an
53 # interface and a subnet.
54 ip = MAASIPAddressField(
55- unique=True, null=True, editable=False, blank=True,
56+ unique=False, null=True, editable=False, blank=True,
57 default=None, verbose_name='IP')
58
59 alloc_type = IntegerField(
60
61=== modified file 'src/maasserver/models/tests/test_staticipaddress.py'
62--- src/maasserver/models/tests/test_staticipaddress.py 2017-04-06 16:38:44 +0000
63+++ src/maasserver/models/tests/test_staticipaddress.py 2017-04-27 18:18:29 +0000
64@@ -1358,3 +1358,42 @@
65 self.assertThat(
66 ip.alloc_type_name,
67 Equals(""))
68+
69+
70+class TestUniqueConstraints(MAASServerTestCase):
71+
72+ def test__rejects_duplicate_address_of_same_type(self):
73+ subnet = factory.make_Subnet(cidr='10.0.0.0/8')
74+ factory.make_StaticIPAddress(
75+ ip='10.0.0.1', subnet=subnet,
76+ alloc_type=IPADDRESS_TYPE.USER_RESERVED)
77+ with ExpectedException(IntegrityError):
78+ factory.make_StaticIPAddress(
79+ ip='10.0.0.1', alloc_type=IPADDRESS_TYPE.USER_RESERVED)
80+
81+ def test__rejects_duplicate_address_for_two_different_static_types(self):
82+ subnet = factory.make_Subnet(cidr='10.0.0.0/8')
83+ factory.make_StaticIPAddress(
84+ ip='10.0.0.1', subnet=subnet, alloc_type=IPADDRESS_TYPE.STICKY)
85+ with ExpectedException(IntegrityError):
86+ factory.make_StaticIPAddress(
87+ ip='10.0.0.1', subnet=subnet,
88+ alloc_type=IPADDRESS_TYPE.USER_RESERVED)
89+
90+ def test__rejects_duplicate_discovered(self):
91+ subnet = factory.make_Subnet(cidr='10.0.0.0/8')
92+ factory.make_StaticIPAddress(
93+ ip='10.0.0.1', subnet=subnet, alloc_type=IPADDRESS_TYPE.DISCOVERED)
94+ with ExpectedException(IntegrityError):
95+ factory.make_StaticIPAddress(
96+ ip='10.0.0.1', subnet=subnet,
97+ alloc_type=IPADDRESS_TYPE.DISCOVERED)
98+
99+ def test__allows_discovered_to_coexist_with_static(self):
100+ subnet = factory.make_Subnet(cidr='10.0.0.0/8')
101+ factory.make_StaticIPAddress(
102+ ip='10.0.0.1', subnet=subnet, alloc_type=IPADDRESS_TYPE.DISCOVERED)
103+ factory.make_StaticIPAddress(
104+ ip='10.0.0.1', subnet=subnet, alloc_type=IPADDRESS_TYPE.STICKY)
105+ self.assertThat(
106+ StaticIPAddress.objects.filter(ip='10.0.0.1').count(), Equals(2))