Merge lp:~lamont/maas/duplicate-default-key into lp:~maas-committers/maas/trunk

Proposed by LaMont Jones
Status: Merged
Approved by: LaMont Jones
Approved revision: no longer in the source branch.
Merged at revision: 4888
Proposed branch: lp:~lamont/maas/duplicate-default-key
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 328 lines (+122/-37)
8 files modified
src/maasserver/models/domain.py (+20/-11)
src/maasserver/models/fabric.py (+19/-9)
src/maasserver/models/space.py (+18/-9)
src/maasserver/models/tests/test_domain.py (+12/-0)
src/maasserver/models/tests/test_fabric.py (+12/-0)
src/maasserver/models/tests/test_space.py (+12/-0)
src/maasserver/models/tests/test_zone.py (+12/-0)
src/maasserver/models/zone.py (+17/-8)
To merge this branch: bzr merge lp:~lamont/maas/duplicate-default-key
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+291158@code.launchpad.net

Commit message

Correctly handle duplicate key id=0 for domain, fabric, space, zone models.

Description of the change

get_default_{domain,fabric,space,zone} all need to handle the case where multiple regionds get there concurrently, and try to create it at the same time.

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

Looks good. Thanks for the tests.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.0 MiB)

The attempt to merge lp:~lamont/maas/duplicate-default-key into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main Sources [1,109 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/universe Sources [7,512 kB]
Get:7 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1,436 kB]
Get:8 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [7,239 kB]
Fetched 17.5 MB in 3s (5,112 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-2ubuntu1).
archdetect-deb is already the newest version (1.117ubuntu1).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
bind9 is already the newest version (1:9.10.3.dfsg.P4-5).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-5).
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
dh-apport is already the newest version (2.20.1-0ubuntu1).
dh-systemd is already the newest version (1.29ubuntu1).
distro-info is already the newest version (0.14build1).
dnsutils is alrea...

Revision history for this message
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/models/domain.py'
--- src/maasserver/models/domain.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/models/domain.py 2016-04-07 16:43:29 +0000
@@ -36,6 +36,7 @@
36 Q,36 Q,
37)37)
38from django.db.models.query import QuerySet38from django.db.models.query import QuerySet
39from django.db.utils import IntegrityError
39from maasserver import DefaultMeta40from maasserver import DefaultMeta
40from maasserver.fields import DomainNameField41from maasserver.fields import DomainNameField
41from maasserver.models.cleansave import CleanSave42from maasserver.models.cleansave import CleanSave
@@ -106,17 +107,25 @@
106 def get_default_domain(self):107 def get_default_domain(self):
107 """Return the default domain."""108 """Return the default domain."""
108 now = datetime.datetime.now()109 now = datetime.datetime.now()
109 domain, created = self.get_or_create(110 try:
110 id=0,111 domain, created = self.get_or_create(
111 defaults={112 id=0,
112 'id': 0,113 defaults={
113 'name': DEFAULT_DOMAIN_NAME,114 'id': 0,
114 'authoritative': True,115 'name': DEFAULT_DOMAIN_NAME,
115 'ttl': None,116 'authoritative': True,
116 'created': now,117 'ttl': None,
117 'updated': now,118 'created': now,
118 }119 'updated': now,
119 )120 }
121 )
122 except IntegrityError as err:
123 if (err.args[0].startswith(
124 'duplicate key value violates unique'
125 ' constraint "maasserver_domain_pkey"')):
126 domain = self.get(id=0)
127 else:
128 raise(err)
120 return domain129 return domain
121130
122 def get_domain_or_404(self, specifiers, user, perm):131 def get_domain_or_404(self, specifiers, user, perm):
123132
=== modified file 'src/maasserver/models/fabric.py'
--- src/maasserver/models/fabric.py 2016-04-07 13:48:58 +0000
+++ src/maasserver/models/fabric.py 2016-04-07 16:43:29 +0000
@@ -22,6 +22,7 @@
22 Manager,22 Manager,
23)23)
24from django.db.models.query import QuerySet24from django.db.models.query import QuerySet
25from django.db.utils import IntegrityError
25from maasserver import DefaultMeta26from maasserver import DefaultMeta
26from maasserver.models.cleansave import CleanSave27from maasserver.models.cleansave import CleanSave
27from maasserver.models.interface import Interface28from maasserver.models.interface import Interface
@@ -77,15 +78,24 @@
77 def get_default_fabric(self):78 def get_default_fabric(self):
78 """Return the default fabric."""79 """Return the default fabric."""
79 now = datetime.datetime.now()80 now = datetime.datetime.now()
80 fabric, created = self.get_or_create(81 try:
81 id=0,82 fabric, created = self.get_or_create(
82 defaults={83 id=0,
83 'id': 0,84 defaults={
84 'name': None,85 'id': 0,
85 'created': now,86 'name': None,
86 'updated': now,87 'created': now,
87 }88 'updated': now,
88 )89 }
90 )
91 except IntegrityError as err:
92 if (err.args[0].startswith(
93 'duplicate key value violates unique'
94 ' constraint "maasserver_fabric_pkey"')):
95 fabric = self.get(id=0)
96 created = False
97 else:
98 raise(err)
89 if created:99 if created:
90 fabric._create_default_vlan()100 fabric._create_default_vlan()
91 return fabric101 return fabric
92102
=== modified file 'src/maasserver/models/space.py'
--- src/maasserver/models/space.py 2016-04-05 20:01:58 +0000
+++ src/maasserver/models/space.py 2016-04-07 16:43:29 +0000
@@ -20,6 +20,7 @@
20 Manager,20 Manager,
21)21)
22from django.db.models.query import QuerySet22from django.db.models.query import QuerySet
23from django.db.utils import IntegrityError
23from maasserver import DefaultMeta24from maasserver import DefaultMeta
24from maasserver.models.cleansave import CleanSave25from maasserver.models.cleansave import CleanSave
25from maasserver.models.timestampedmodel import TimestampedModel26from maasserver.models.timestampedmodel import TimestampedModel
@@ -75,15 +76,23 @@
75 def get_default_space(self):76 def get_default_space(self):
76 """Return the default space."""77 """Return the default space."""
77 now = datetime.datetime.now()78 now = datetime.datetime.now()
78 space, _ = self.get_or_create(79 try:
79 id=0,80 space, _ = self.get_or_create(
80 defaults={81 id=0,
81 'id': 0,82 defaults={
82 'name': None,83 'id': 0,
83 'created': now,84 'name': None,
84 'updated': now,85 'created': now,
85 }86 'updated': now,
86 )87 }
88 )
89 except IntegrityError as err:
90 if (err.args[0].startswith(
91 'duplicate key value violates unique'
92 ' constraint "maasserver_space_pkey"')):
93 space = self.get(id=0)
94 else:
95 raise(err)
87 return space96 return space
8897
89 def get_space_or_404(self, specifiers, user, perm):98 def get_space_or_404(self, specifiers, user, perm):
9099
=== modified file 'src/maasserver/models/tests/test_domain.py'
--- src/maasserver/models/tests/test_domain.py 2016-02-11 14:33:51 +0000
+++ src/maasserver/models/tests/test_domain.py 2016-04-07 16:43:29 +0000
@@ -13,6 +13,7 @@
13 ValidationError,13 ValidationError,
14)14)
15from django.db.models import ProtectedError15from django.db.models import ProtectedError
16from django.db.utils import IntegrityError
16from maasserver.enum import NODE_PERMISSION17from maasserver.enum import NODE_PERMISSION
17from maasserver.models.config import Config18from maasserver.models.config import Config
18from maasserver.models.dnsdata import DNSData19from maasserver.models.dnsdata import DNSData
@@ -23,6 +24,7 @@
23from maasserver.models.staticipaddress import StaticIPAddress24from maasserver.models.staticipaddress import StaticIPAddress
24from maasserver.testing.factory import factory25from maasserver.testing.factory import factory
25from maasserver.testing.testcase import MAASServerTestCase26from maasserver.testing.testcase import MAASServerTestCase
27from maastesting.matchers import MockCalledOnce
26from netaddr import IPAddress28from netaddr import IPAddress
27from testtools.matchers import MatchesStructure29from testtools.matchers import MatchesStructure
28from testtools.testcase import ExpectedException30from testtools.testcase import ExpectedException
@@ -151,6 +153,16 @@
151 self.assertEqual(0, default_domain.id)153 self.assertEqual(0, default_domain.id)
152 self.assertEqual(DEFAULT_DOMAIN_NAME, default_domain.get_name())154 self.assertEqual(DEFAULT_DOMAIN_NAME, default_domain.get_name())
153155
156 def test_get_default_domain_handles_exception(self):
157 default_domain = Domain.objects.get_default_domain()
158 func = self.patch(Domain.objects, "get_or_create")
159 func.side_effect = IntegrityError(
160 'duplicate key value violates unique constraint '
161 '"maasserver_domain_pkey"')
162 domain = Domain.objects.get_default_domain()
163 self.assertThat(func, MockCalledOnce())
164 self.assertEqual(default_domain.id, domain.id)
165
154 def test_invalid_name_raises_exception(self):166 def test_invalid_name_raises_exception(self):
155 self.assertRaises(167 self.assertRaises(
156 ValidationError,168 ValidationError,
157169
=== modified file 'src/maasserver/models/tests/test_fabric.py'
--- src/maasserver/models/tests/test_fabric.py 2016-04-07 13:48:58 +0000
+++ src/maasserver/models/tests/test_fabric.py 2016-04-07 16:43:29 +0000
@@ -10,6 +10,7 @@
10 PermissionDenied,10 PermissionDenied,
11 ValidationError,11 ValidationError,
12)12)
13from django.db.utils import IntegrityError
13from maasserver.enum import (14from maasserver.enum import (
14 INTERFACE_TYPE,15 INTERFACE_TYPE,
15 NODE_PERMISSION,16 NODE_PERMISSION,
@@ -25,6 +26,7 @@
25)26)
26from maasserver.testing.factory import factory27from maasserver.testing.factory import factory
27from maasserver.testing.testcase import MAASServerTestCase28from maasserver.testing.testcase import MAASServerTestCase
29from maastesting.matchers import MockCalledOnce
28from testtools.matchers import MatchesStructure30from testtools.matchers import MatchesStructure
2931
3032
@@ -192,6 +194,16 @@
192 factory.make_Fabric,194 factory.make_Fabric,
193 name=fabric1.name)195 name=fabric1.name)
194196
197 def test_get_default_fabric_handles_exception(self):
198 default_fabric = Fabric.objects.get_default_fabric()
199 func = self.patch(Fabric.objects, "get_or_create")
200 func.side_effect = IntegrityError(
201 'duplicate key value violates unique constraint '
202 '"maasserver_fabric_pkey"')
203 fabric = Fabric.objects.get_default_fabric()
204 self.assertThat(func, MockCalledOnce())
205 self.assertEqual(default_fabric.id, fabric.id)
206
195 def test_get_default_fabric_is_idempotent(self):207 def test_get_default_fabric_is_idempotent(self):
196 default_fabric = Fabric.objects.get_default_fabric()208 default_fabric = Fabric.objects.get_default_fabric()
197 default_fabric2 = Fabric.objects.get_default_fabric()209 default_fabric2 = Fabric.objects.get_default_fabric()
198210
=== modified file 'src/maasserver/models/tests/test_space.py'
--- src/maasserver/models/tests/test_space.py 2016-04-07 13:48:58 +0000
+++ src/maasserver/models/tests/test_space.py 2016-04-07 16:43:29 +0000
@@ -12,6 +12,7 @@
12 ValidationError,12 ValidationError,
13)13)
14from django.db.models import ProtectedError14from django.db.models import ProtectedError
15from django.db.utils import IntegrityError
15from maasserver.enum import NODE_PERMISSION16from maasserver.enum import NODE_PERMISSION
16from maasserver.models.space import (17from maasserver.models.space import (
17 DEFAULT_SPACE_NAME,18 DEFAULT_SPACE_NAME,
@@ -19,6 +20,7 @@
19)20)
20from maasserver.testing.factory import factory21from maasserver.testing.factory import factory
21from maasserver.testing.testcase import MAASServerTestCase22from maasserver.testing.testcase import MAASServerTestCase
23from maastesting.matchers import MockCalledOnce
22from testtools.matchers import MatchesStructure24from testtools.matchers import MatchesStructure
23from testtools.testcase import ExpectedException25from testtools.testcase import ExpectedException
2426
@@ -141,6 +143,16 @@
141 self.assertEqual(DEFAULT_SPACE_NAME, default_space.get_name())143 self.assertEqual(DEFAULT_SPACE_NAME, default_space.get_name())
142 self.assertEqual(DEFAULT_SPACE_NAME, default_space.name)144 self.assertEqual(DEFAULT_SPACE_NAME, default_space.name)
143145
146 def test_get_default_space_handles_exception(self):
147 default_space = Space.objects.get_default_space()
148 func = self.patch(Space.objects, "get_or_create")
149 func.side_effect = IntegrityError(
150 'duplicate key value violates unique constraint '
151 '"maasserver_space_pkey"')
152 space = Space.objects.get_default_space()
153 self.assertThat(func, MockCalledOnce())
154 self.assertEqual(default_space.id, space.id)
155
144 def test_invalid_name_raises_exception(self):156 def test_invalid_name_raises_exception(self):
145 self.assertRaises(157 self.assertRaises(
146 ValidationError,158 ValidationError,
147159
=== modified file 'src/maasserver/models/tests/test_zone.py'
--- src/maasserver/models/tests/test_zone.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/models/tests/test_zone.py 2016-04-07 16:43:29 +0000
@@ -5,6 +5,7 @@
55
6__all__ = []6__all__ = []
77
8from django.db.utils import IntegrityError
8from maasserver.enum import NODE_TYPE9from maasserver.enum import NODE_TYPE
9from maasserver.models.zone import (10from maasserver.models.zone import (
10 DEFAULT_ZONE_NAME,11 DEFAULT_ZONE_NAME,
@@ -13,6 +14,7 @@
13from maasserver.testing.factory import factory14from maasserver.testing.factory import factory
14from maasserver.testing.testcase import MAASServerTestCase15from maasserver.testing.testcase import MAASServerTestCase
15from maasserver.utils.orm import reload_object16from maasserver.utils.orm import reload_object
17from maastesting.matchers import MockCalledOnce
1618
1719
18class TestZoneManager(MAASServerTestCase):20class TestZoneManager(MAASServerTestCase):
@@ -27,6 +29,16 @@
27 self.assertEqual(29 self.assertEqual(
28 DEFAULT_ZONE_NAME, Zone.objects.get_default_zone().name)30 DEFAULT_ZONE_NAME, Zone.objects.get_default_zone().name)
2931
32 def test_get_default_zone_handles_exception(self):
33 default_zone = Zone.objects.get_default_zone()
34 func = self.patch(Zone.objects, "get_or_create")
35 func.side_effect = IntegrityError(
36 'duplicate key value violates unique constraint '
37 '"maasserver_zone_name_key"')
38 zone = Zone.objects.get_default_zone()
39 self.assertThat(func, MockCalledOnce())
40 self.assertEqual(default_zone.id, zone.id)
41
3042
31class TestZone(MAASServerTestCase):43class TestZone(MAASServerTestCase):
32 """Tests for :class:`Zone`."""44 """Tests for :class:`Zone`."""
3345
=== modified file 'src/maasserver/models/zone.py'
--- src/maasserver/models/zone.py 2015-12-05 01:43:40 +0000
+++ src/maasserver/models/zone.py 2016-04-07 16:43:29 +0000
@@ -18,6 +18,7 @@
18 Manager,18 Manager,
19 TextField,19 TextField,
20)20)
21from django.db.utils import IntegrityError
21from maasserver import DefaultMeta22from maasserver import DefaultMeta
22from maasserver.enum import NODE_TYPE23from maasserver.enum import NODE_TYPE
23from maasserver.models.cleansave import CleanSave24from maasserver.models.cleansave import CleanSave
@@ -41,14 +42,22 @@
41 def get_default_zone(self):42 def get_default_zone(self):
42 """Return the default zone."""43 """Return the default zone."""
43 now = datetime.datetime.now()44 now = datetime.datetime.now()
44 zone, _ = self.get_or_create(45 try:
45 name=DEFAULT_ZONE_NAME,46 zone, _ = self.get_or_create(
46 defaults={47 name=DEFAULT_ZONE_NAME,
47 'name': DEFAULT_ZONE_NAME,48 defaults={
48 'created': now,49 'name': DEFAULT_ZONE_NAME,
49 'updated': now,50 'created': now,
50 }51 'updated': now,
51 )52 }
53 )
54 except IntegrityError as err:
55 if (err.args[0].startswith(
56 'duplicate key value violates unique'
57 ' constraint "maasserver_zone_name_key"')):
58 zone = self.get(name=DEFAULT_ZONE_NAME)
59 else:
60 raise(err)
52 return zone61 return zone
5362
5463