Merge lp:~allenap/maas/dns-serials--bug-1571645--model into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 4962
Proposed branch: lp:~allenap/maas/dns-serials--bug-1571645--model
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 348 lines (+203/-59)
4 files modified
src/maasserver/migrations/builtin/maasserver/0055_dns_publications.py (+28/-0)
src/maasserver/models/__init__.py (+17/-59)
src/maasserver/models/dnspublication.py (+74/-0)
src/maasserver/models/tests/test_dnspublication.py (+84/-0)
To merge this branch: bzr merge lp:~allenap/maas/dns-serials--bug-1571645--model
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+292564@code.launchpad.net

Commit message

New DNSPublication model to ensure that zone serials are consistent across an HA region.

Description of the change

This is the first part of addressing the linked bug, just a new model for system triggers to populate.

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

Looks good.

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

The attempt to merge lp:~allenap/maas/dns-serials--bug-1571645--model into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [92.2 kB]
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [92.2 kB]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 184 kB in 0s (384 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 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-simplejson 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-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
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-8).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-8).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.3.dfsg.P4-8).
firefox is already the newest version (45.0.2+build1-0ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libj...

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.

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

The attempt to merge lp:~allenap/maas/dns-serials--bug-1571645--model into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [92.2 kB]
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [92.2 kB]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 184 kB in 0s (433 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 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-simplejson 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-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
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-8).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-8).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.3.dfsg.P4-8).
firefox is already the newest version (45.0.2+build1-0ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libj...

Revision history for this message
Gavin Panella (allenap) wrote :

Spurious failure that lp:~allenap/maas/maas-crochet-run-test will help to address. For now, trying again.

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/0055_dns_publications.py'
2--- src/maasserver/migrations/builtin/maasserver/0055_dns_publications.py 1970-01-01 00:00:00 +0000
3+++ src/maasserver/migrations/builtin/maasserver/0055_dns_publications.py 2016-04-26 16:41:19 +0000
4@@ -0,0 +1,28 @@
5+# -*- coding: utf-8 -*-
6+from __future__ import unicode_literals
7+
8+import django.core.validators
9+from django.db import (
10+ migrations,
11+ models,
12+)
13+import maasserver.models.dnspublication
14+
15+
16+class Migration(migrations.Migration):
17+
18+ dependencies = [
19+ ('maasserver', '0054_controller'),
20+ ]
21+
22+ operations = [
23+ migrations.CreateModel(
24+ name='DNSPublication',
25+ fields=[
26+ ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)),
27+ ('serial', models.IntegerField(editable=False, default=maasserver.models.dnspublication.next_serial, validators=(django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4294967295)))),
28+ ('created', models.DateTimeField(auto_now_add=True)),
29+ ('source', models.CharField(blank=True, editable=False, max_length=255, help_text='A brief explanation why DNS was published.')),
30+ ],
31+ ),
32+ ]
33
34=== modified file 'src/maasserver/models/__init__.py'
35--- src/maasserver/models/__init__.py 2016-04-15 22:14:33 +0000
36+++ src/maasserver/models/__init__.py 2016-04-26 16:41:19 +0000
37@@ -4,61 +4,69 @@
38 """Model export and helpers for maasserver."""
39
40 __all__ = [
41- 'BMC',
42 'Bcache',
43 'BlockDevice',
44+ 'BMC',
45+ 'BondInterface',
46 'BootResource',
47 'BootResourceFile',
48 'BootResourceSet',
49 'BootSource',
50 'BootSourceCache',
51 'BootSourceSelection',
52+ 'BridgeInterface',
53 'CacheSet',
54 'ComponentError',
55 'Config',
56 'Controller',
57+ 'Device',
58 'DHCPSnippet',
59 'DNSData',
60+ 'DNSPublication',
61 'DNSResource',
62- 'Device',
63 'Domain',
64 'Event',
65+ 'EventType',
66 'Fabric',
67 'FanNetwork',
68 'FileStorage',
69 'Filesystem',
70 'FilesystemGroup',
71+ 'Interface',
72 'IPRange',
73- 'Interface',
74 'LargeFile',
75 'LicenseKey',
76+ 'logger',
77 'Machine',
78 'Node',
79+ 'NodeGroupToRackController',
80 'OwnerData',
81 'Partition',
82 'PartitionTable',
83 'PhysicalBlockDevice',
84 'PhysicalInterface',
85+ 'RackController',
86 'RAID',
87- 'RackController',
88 'RegionController',
89 'RegionControllerProcess',
90 'RegionControllerProcessEndpoint',
91 'RegionRackRPCConnection',
92+ 'Service',
93+ 'Space',
94 'SSHKey',
95 'SSLKey',
96- 'Service',
97- 'Space',
98+ 'StaticIPAddress',
99 'Subnet',
100 'Tag',
101 'Template',
102+ 'UnknownInterface',
103 'UserProfile',
104- 'VLAN',
105 'VersionedTextFile',
106 'VirtualBlockDevice',
107+ 'VLAN',
108+ 'VLANInterface',
109 'VolumeGroup',
110 'Zone',
111- 'logger',
112 ]
113
114 from django.contrib.auth.backends import ModelBackend
115@@ -87,6 +95,7 @@
116 from maasserver.models.config import Config
117 from maasserver.models.dhcpsnippet import DHCPSnippet
118 from maasserver.models.dnsdata import DNSData
119+from maasserver.models.dnspublication import DNSPublication
120 from maasserver.models.dnsresource import DNSResource
121 from maasserver.models.domain import Domain
122 from maasserver.models.event import Event
123@@ -147,57 +156,6 @@
124 from maasserver.utils import ignore_unused
125 from piston3.doc import HandlerDocumentation
126
127-# Suppress warning about symbols being imported, but only used for
128-# export in __all__.
129-ignore_unused(
130- BMC,
131- Bcache,
132- BondInterface,
133- BridgeInterface,
134- BootResource,
135- BootResourceFile,
136- BootResourceSet,
137- CacheSet,
138- ComponentError,
139- Config,
140- Controller,
141- DHCPSnippet,
142- Event,
143- EventType,
144- Fabric,
145- FileStorage,
146- Filesystem,
147- FilesystemGroup,
148- IPRange,
149- Interface,
150- LargeFile,
151- LicenseKey,
152- NodeGroupToRackController,
153- OwnerData,
154- Partition,
155- PartitionTable,
156- RAID,
157- RackController,
158- RegionController,
159- RegionControllerProcess,
160- RegionControllerProcessEndpoint,
161- SSHKey,
162- Service,
163- StaticIPAddress,
164- Tag,
165- Template,
166- UnknownInterface,
167- UserProfile,
168- VLAN,
169- VLANInterface,
170- VersionedTextFile,
171- VirtualBlockDevice,
172- VolumeGroup,
173- Zone,
174- logger,
175-)
176-
177-
178 # Connect the 'create_user' method to the post save signal of User.
179 post_save.connect(create_user, sender=User)
180
181
182=== added file 'src/maasserver/models/dnspublication.py'
183--- src/maasserver/models/dnspublication.py 1970-01-01 00:00:00 +0000
184+++ src/maasserver/models/dnspublication.py 2016-04-26 16:41:19 +0000
185@@ -0,0 +1,74 @@
186+# Copyright 2016 Canonical Ltd. This software is licensed under the
187+# GNU Affero General Public License version 3 (see the file LICENSE).
188+
189+"""DNS publication model objects."""
190+
191+__all__ = [
192+ "DNSPublication",
193+]
194+
195+from django.core.validators import (
196+ MaxValueValidator,
197+ MinValueValidator,
198+)
199+from django.db.models import (
200+ Manager,
201+ Model,
202+)
203+from django.db.models.fields import (
204+ CharField,
205+ DateTimeField,
206+ IntegerField,
207+)
208+from maasserver import DefaultMeta
209+from maasserver.dns.config import zone_serial
210+
211+
212+def next_serial():
213+ return next(zone_serial)
214+
215+
216+class DNSPublicationManager(Manager):
217+ """Manager for DNS publishing records."""
218+
219+ def get_most_recent(self):
220+ """Return the most recently inserted `DNSPublication`."""
221+ return self.order_by("-id")[0]
222+
223+ def collect_garbage(self):
224+ """Delete all but the most recently inserted `DNSPublication`."""
225+ self.filter(id__lt=self.get_most_recent().id).delete()
226+
227+
228+class DNSPublication(Model):
229+ """A row in this table denotes a DNS publication request.
230+
231+ Typically this will be populated by a trigger within the database. A
232+ listeners in regiond will be notified and consult the most recent record
233+ in this table. This way we can consistently publish zones with the same
234+ serial in an HA environment, and newly starting regiond processes can
235+ immediately be consistent with their peers.
236+ """
237+
238+ class Meta(DefaultMeta):
239+ """Needed for South to recognize this model."""
240+
241+ objects = DNSPublicationManager()
242+
243+ # The serial number with which to publish the zone. We don't use the
244+ # primary key for this because zone serials are allowed to cycle.
245+ serial = IntegerField(
246+ editable=False, null=False, default=next_serial, unique=False,
247+ validators=(
248+ MinValueValidator(zone_serial.minvalue),
249+ MaxValueValidator(zone_serial.maxvalue),
250+ ))
251+
252+ # This field is informational.
253+ created = DateTimeField(
254+ editable=False, null=False, auto_now=False, auto_now_add=True)
255+
256+ # This field is informational.
257+ source = CharField(
258+ editable=False, max_length=255, null=False, blank=True,
259+ help_text="A brief explanation why DNS was published.")
260
261=== added file 'src/maasserver/models/tests/test_dnspublication.py'
262--- src/maasserver/models/tests/test_dnspublication.py 1970-01-01 00:00:00 +0000
263+++ src/maasserver/models/tests/test_dnspublication.py 2016-04-26 16:41:19 +0000
264@@ -0,0 +1,84 @@
265+# Copyright 2016 Canonical Ltd. This software is licensed under the
266+# GNU Affero General Public License version 3 (see the file LICENSE).
267+
268+"""Tests for general DNS models."""
269+
270+__all__ = []
271+
272+from datetime import (
273+ datetime,
274+ timedelta,
275+)
276+from random import randint
277+
278+from maasserver.models.dnspublication import DNSPublication
279+from maasserver.testing.factory import factory
280+from maasserver.testing.testcase import MAASServerTestCase
281+from testtools.matchers import (
282+ Equals,
283+ HasLength,
284+ IsInstance,
285+ MatchesAll,
286+ MatchesStructure,
287+ Not,
288+)
289+
290+
291+class TestDNSPublication(MAASServerTestCase):
292+ """Test the `DNSPublication` model."""
293+
294+ def test_create_empty(self):
295+ pub = DNSPublication()
296+ pub.save()
297+ self.assertThat(
298+ pub, MatchesStructure(
299+ serial=IsInstance(int),
300+ created=IsInstance(datetime),
301+ source=Equals(""),
302+ ))
303+
304+ def test_create_with_values(self):
305+ serial = randint(1, 5000)
306+ created = datetime.now() - timedelta(minutes=1098)
307+ source = factory.make_name("source")
308+ pub = DNSPublication(serial=serial, created=created, source=source)
309+ pub.save()
310+ self.assertThat(
311+ pub, MatchesStructure(
312+ serial=Equals(serial),
313+ created=MatchesAll(
314+ IsInstance(datetime),
315+ # `created` is always set; given values are ignored.
316+ Not(Equals(created)),
317+ first_only=True,
318+ ),
319+ source=Equals(source),
320+ ))
321+
322+
323+class TestDNSPublicationManager(MAASServerTestCase):
324+ """Test `DNSPublicationManager`."""
325+
326+ def test_get_most_recent_returns_record_with_highest_id(self):
327+ DNSPublication(serial=3).save()
328+ DNSPublication(serial=30).save()
329+ DNSPublication(serial=10).save()
330+ self.assertThat(
331+ DNSPublication.objects.get_most_recent(),
332+ MatchesStructure(serial=Equals(10)))
333+
334+ def test_get_most_recent_crashes_when_no_publications(self):
335+ # This is okay because we're going to ensure (using a migration) that
336+ # there is never less than one publication in the table. If this crash
337+ # happens we have bigger problems.
338+ self.assertRaises(IndexError, DNSPublication.objects.get_most_recent)
339+
340+ def test_collect_garbage_removes_all_but_most_recent_record(self):
341+ for serial in range(10):
342+ DNSPublication(serial=serial).save()
343+ self.assertThat(DNSPublication.objects.all(), HasLength(10))
344+ DNSPublication.objects.collect_garbage()
345+ self.assertThat(DNSPublication.objects.all(), HasLength(1))
346+ self.assertThat(
347+ DNSPublication.objects.get_most_recent(),
348+ MatchesStructure(serial=Equals(serial)))