Merge lp:~lamont/maas/bug-1356012 into lp:maas/trunk

Proposed by LaMont Jones on 2016-01-28
Status: Merged
Approved by: LaMont Jones on 2016-01-29
Approved revision: 4626
Merged at revision: 4621
Proposed branch: lp:~lamont/maas/bug-1356012
Merge into: lp:maas/trunk
Diff against target: 661 lines (+238/-26)
14 files modified
src/maasserver/api/subnets.py (+10/-1)
src/maasserver/api/tests/test_subnets.py (+7/-0)
src/maasserver/dns/tests/test_config.py (+12/-6)
src/maasserver/dns/tests/test_zonegenerator.py (+53/-9)
src/maasserver/dns/zonegenerator.py (+38/-2)
src/maasserver/enum.py (+26/-0)
src/maasserver/forms_subnet.py (+5/-0)
src/maasserver/migrations/builtin/maasserver/0023_add_rdns_mode.py (+22/-0)
src/maasserver/models/subnet.py (+7/-0)
src/maasserver/models/tests/test_subnet.py (+5/-2)
src/maasserver/testing/factory.py (+3/-2)
src/maasserver/websockets/handlers/tests/test_subnet.py (+1/-0)
src/provisioningserver/dns/tests/test_zoneconfig.py (+4/-2)
src/provisioningserver/dns/zoneconfig.py (+45/-2)
To merge this branch: bzr merge lp:~lamont/maas/bug-1356012
Reviewer Review Type Date Requested Status
Blake Rouse (community) 2016-01-28 Approve on 2016-01-29
Review via email: mp+284354@code.launchpad.net

Commit message

Generate rfc2317 glue zones in the DNS by default, allow the admin to disable that if desired.

Description of the change

If a subnet is smaller than a /24 (or /124 for IPv6 networks), then reverse DNS needs to be delegated from the respective /24 (/124) DNS zone. MAAS now defaults to doing that, and allows the admin to override that.

Additionally, the admin can now turn off DNS PTR zone (reverse dns) for an individual Subnet.

To post a comment you must log in.
Blake Rouse (blake-rouse) wrote :

Overall looks good. Not going to block you on it. Would really like the see the missing documentation be done on the API calls before this lands.

review: Approve
LaMont Jones (lamont) wrote :

some replies, everything addressed.

MAAS Lander (maas-lander) wrote :
Download full text (1.1 MiB)

The attempt to merge lp:~lamont/maas/bug-1356012 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 [227 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,129 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/universe Sources [7,635 kB]
Get:7 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/multiverse Sources [178 kB]
Get:8 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1,481 kB]
Get:9 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/main Translation-en [855 kB]
Get:10 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [7,220 kB]
Get:11 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/universe Translation-en [4,828 kB]
Get:12 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial/multiverse amd64 Packages [139 kB]
Fetched 23.7 MB in 7s (3,134 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 authbind 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-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-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-seamicroclient python3-simplejson 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-1ubuntu1).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.9.5.dfsg-12.1ubuntu1).
bind9utils is already the newest version (1:9.9.5.dfsg-12.1ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu1).
debhelper is already the newest ve...

lp:~lamont/maas/bug-1356012 updated on 2016-01-29
4626. By LaMont Jones on 2016-01-29

Formatting changes.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/subnets.py'
2--- src/maasserver/api/subnets.py 2015-12-01 18:12:59 +0000
3+++ src/maasserver/api/subnets.py 2016-01-29 18:06:17 +0000
4@@ -26,6 +26,7 @@
5 'cidr',
6 'gateway_ip',
7 'dns_servers',
8+ 'rdns_mode',
9 )
10
11
12@@ -60,7 +61,14 @@
13 :param space: Space this subnet is in. Defaults to the default space.
14 :param cidr: The network CIDR for this subnet.
15 :param gateway_ip: The gateway IP address for this subnet.
16- :param dns_servers: Comma-seperated list of DNS servers for this \
17+ :param rdns_mode: How reverse DNS is handled for this subnet.
18+ One of: 0 (Disabled), 1 (Enabled), or 2 (RFC2317). Disabled means
19+ no reverse zone is created; Enabled means generate the reverse
20+ zone; RFC2317 extends Enabled to create the necessary parent zone
21+ with the appropriate CNAME resource records for the network, if the
22+ network is small enough to require the support described in
23+ RFC2317.
24+ :param dns_servers: Comma-seperated list of DNS servers for this
25 subnet.
26 """
27 form = SubnetForm(data=request.data)
28@@ -111,6 +119,7 @@
29 :param space: Space this subnet is in.
30 :param cidr: The network CIDR for this subnet.
31 :param gateway_ip: The gateway IP address for this subnet.
32+ :param rdns_mode: How reverse DNS is handled for this subnet.
33 :param dns_servers: Comma-seperated list of DNS servers for this \
34 subnet.
35
36
37=== modified file 'src/maasserver/api/tests/test_subnets.py'
38--- src/maasserver/api/tests/test_subnets.py 2016-01-25 14:10:29 +0000
39+++ src/maasserver/api/tests/test_subnets.py 2016-01-29 18:06:17 +0000
40@@ -15,6 +15,7 @@
41 IPADDRESS_TYPE,
42 NODE_STATUS,
43 NODEGROUP_STATUS,
44+ RDNS_MODE_CHOICES,
45 )
46 from maasserver.testing.api import (
47 APITestCase,
48@@ -82,6 +83,7 @@
49 space = factory.make_Space()
50 network = factory.make_ip4_or_6_network()
51 cidr = str(network.cidr)
52+ rdns_mode = factory.pick_choice(RDNS_MODE_CHOICES)
53 gateway_ip = factory.pick_ip_in_network(network)
54 dns_servers = []
55 for _ in range(2):
56@@ -96,6 +98,7 @@
57 "cidr": cidr,
58 "gateway_ip": gateway_ip,
59 "dns_servers": ','.join(dns_servers),
60+ "rdns_mode": rdns_mode,
61 })
62 self.assertEqual(
63 http.client.OK, response.status_code, response.content)
64@@ -107,6 +110,7 @@
65 self.assertEqual(cidr, created_subnet['cidr'])
66 self.assertEqual(gateway_ip, created_subnet['gateway_ip'])
67 self.assertEqual(dns_servers, created_subnet['dns_servers'])
68+ self.assertEqual(rdns_mode, created_subnet['rdns_mode'])
69
70 def test_create_admin_only(self):
71 subnet_name = factory.make_name("subnet")
72@@ -185,9 +189,11 @@
73 self.become_admin()
74 subnet = factory.make_Subnet()
75 new_name = factory.make_name("subnet")
76+ new_rdns_mode = factory.pick_choice(RDNS_MODE_CHOICES)
77 uri = get_subnet_uri(subnet)
78 response = self.client.put(uri, {
79 "name": new_name,
80+ "rdns_mode": new_rdns_mode,
81 })
82 self.assertEqual(
83 http.client.OK, response.status_code, response.content)
84@@ -195,6 +201,7 @@
85 new_name, json.loads(
86 response.content.decode(settings.DEFAULT_CHARSET))['name'])
87 self.assertEqual(new_name, reload_object(subnet).name)
88+ self.assertEqual(new_rdns_mode, reload_object(subnet).rdns_mode)
89
90 def test_update_admin_only(self):
91 subnet = factory.make_Subnet()
92
93=== modified file 'src/maasserver/dns/tests/test_config.py'
94--- src/maasserver/dns/tests/test_config.py 2016-01-25 12:08:51 +0000
95+++ src/maasserver/dns/tests/test_config.py 2016-01-29 18:06:17 +0000
96@@ -558,8 +558,10 @@
97 commands=['-x', ip, '+short', '-%i' % version])
98 return output.split('\n')
99
100- def assertDNSMatches(self, hostname, domain, ip, version=4):
101+ def assertDNSMatches(self, hostname, domain, ip, version=-1):
102 # A forward lookup on the hostname returns the IP address.
103+ if version == -1:
104+ version = IPAddress(ip).version
105 fqdn = "%s.%s" % (hostname, domain)
106 forward_lookup_result = self.dig_resolve(fqdn, version=version)
107 self.expectThat(
108@@ -725,7 +727,8 @@
109
110 def test_changing_interface_management_updates_DNS_zone(self):
111 self.patch(settings, "DNS_CONNECT", True)
112- network = IPNetwork('192.168.7.1/24')
113+ network = factory.make_ip4_or_6_network(
114+ host_bits=random.randint(3, 10))
115 ip = factory.pick_ip_in_network(network)
116 nodegroup = factory.make_NodeGroup(
117 network=network, status=NODEGROUP_STATUS.ENABLED,
118@@ -737,7 +740,8 @@
119
120 def test_delete_nodegroup_disables_DNS_zone(self):
121 self.patch(settings, "DNS_CONNECT", True)
122- network = IPNetwork('192.168.7.1/24')
123+ network = factory.make_ip4_or_6_network(
124+ host_bits=random.randint(3, 10))
125 ip = factory.pick_ip_in_network(network)
126 nodegroup = factory.make_NodeGroup(
127 network=network, status=NODEGROUP_STATUS.ENABLED,
128@@ -782,7 +786,8 @@
129
130 def test_bind_configuration_includes_dynamic_ips_of_deployed_nodes(self):
131 self.patch(settings, "DNS_CONNECT", True)
132- network = IPNetwork('192.168.7.1/24')
133+ network = factory.make_ip4_or_6_network(
134+ host_bits=random.randint(3, 10))
135 nodegroup = self.create_managed_nodegroup(network=network)
136 [interface] = nodegroup.get_managed_interfaces()
137 node = factory.make_Node(
138@@ -805,7 +810,8 @@
139
140 def test_dnsresources_are_in_the_dns(self):
141 self.patch(settings, "DNS_CONNECT", True)
142- network = IPNetwork('192.168.7.1/24')
143+ network = factory.make_ip4_or_6_network(
144+ host_bits=random.randint(3, 10))
145 nodegroup = self.create_managed_nodegroup(network=network)
146 [interface] = nodegroup.get_managed_interfaces()
147 node = factory.make_Node(
148@@ -829,7 +835,7 @@
149
150 def test_bind_configuration_includes_ipv6_zone(self):
151 self.patch(settings, "DNS_CONNECT", True)
152- network = IPNetwork('fe80::/16')
153+ network = factory.make_ipv6_network(slash=random.randint(118, 125))
154 nodegroup = self.create_managed_nodegroup(network=network)
155 nodegroup, node, static = self.create_nodegroup_with_static_ip(
156 nodegroup=nodegroup)
157
158=== modified file 'src/maasserver/dns/tests/test_zonegenerator.py'
159--- src/maasserver/dns/tests/test_zonegenerator.py 2016-01-25 14:07:49 +0000
160+++ src/maasserver/dns/tests/test_zonegenerator.py 2016-01-29 18:06:17 +0000
161@@ -27,6 +27,7 @@
162 NODE_STATUS,
163 NODEGROUP_STATUS,
164 NODEGROUPINTERFACE_MANAGEMENT,
165+ RDNS_MODE,
166 )
167 from maasserver.models import (
168 Config,
169@@ -423,7 +424,8 @@
170 self.assertThat(
171 zones, MatchesSetwise(
172 forward_zone("henry"),
173- reverse_zone(default_domain, "10/29")))
174+ reverse_zone(default_domain, "10/29"),
175+ reverse_zone(default_domain, "10/24")))
176
177 def test_with_one_nodegroup_with_node_yields_fwd_and_rev_zone(self):
178 self.useFixture(RegionConfigurationFixture())
179@@ -442,7 +444,8 @@
180 self.assertThat(
181 zones, MatchesSetwise(
182 forward_zone("henry"),
183- reverse_zone(default_domain, "10/29")))
184+ reverse_zone(default_domain, "10/29"),
185+ reverse_zone(default_domain, "10/24")))
186
187 def test_returns_interface_ips_but_no_nulls(self):
188 self.useFixture(RegionConfigurationFixture())
189@@ -473,7 +476,8 @@
190 self.assertThat(
191 zones, MatchesSetwise(
192 forward_zone("henry"),
193- reverse_zone(default_domain, "10/29")))
194+ reverse_zone(default_domain, "10/29"),
195+ reverse_zone(default_domain, "10/24")))
196 self.assertEqual(
197 {node.hostname: (30, ['%s' % boot_ip.ip])}, zones[0]._mapping)
198 self.assertEqual(
199@@ -488,21 +492,37 @@
200 default_ttl, ['%s' % boot_ip.ip])},
201 zones[1]._mapping)
202
203+ def rfc2317_network(self, network):
204+ """Returns the network that rfc2317 glue goes in, if any."""
205+ net = network
206+ if net.version == 4 and net.prefixlen > 24:
207+ net = IPNetwork("%s/24" % net.network)
208+ net = IPNetwork("%s/24" % net.network)
209+ if net.version == 6 and net.prefixlen > 124:
210+ net = IPNetwork("%s/124" % net.network)
211+ net = IPNetwork("%s/124" % net.network)
212+ if net != network:
213+ return net
214+ return None
215+
216 def test_two_managed_interfaces_yields_one_forward_two_reverse_zones(self):
217 self.useFixture(RegionConfigurationFixture())
218 nodegroup = self.make_node_group()
219 factory.make_NodeGroupInterface(
220 nodegroup=nodegroup,
221 management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
222- [interface1, interface2] = nodegroup.get_managed_interfaces()
223+ interfaces = nodegroup.get_managed_interfaces()
224 default_domain = Domain.objects.get_default_domain().name
225
226 factory.make_Domain(name=nodegroup.name)
227- expected_zones = [
228- forward_zone(nodegroup.name),
229- reverse_zone(default_domain, interface1.network),
230- reverse_zone(default_domain, interface2.network),
231- ]
232+ expected_zones = [forward_zone(nodegroup.name)] + [
233+ reverse_zone(default_domain, iface.network)
234+ for iface in interfaces
235+ ] + [
236+ reverse_zone(default_domain, self.rfc2317_network(iface.network))
237+ for iface in interfaces
238+ if self.rfc2317_network(iface.network) is not None
239+ ]
240 domain = Domain.objects.get(name=nodegroup.name)
241 subnet = Subnet.objects.filter(
242 nodegroupinterface__nodegroup=nodegroup)
243@@ -541,6 +561,30 @@
244 reverse_zone(default_domain.name, "11/29"),
245 reverse_zone(default_domain.name, "20/29"),
246 reverse_zone(default_domain.name, "21/29"),
247+ reverse_zone(default_domain.name, "10/24"),
248+ reverse_zone(default_domain.name, "11/24"),
249+ reverse_zone(default_domain.name, "20/24"),
250+ reverse_zone(default_domain.name, "21/24"),
251+ )
252+ self.assertThat(
253+ ZoneGenerator(domains, subnets, serial_generator=Mock()).as_list(),
254+ MatchesSetwise(*expected_zones))
255+
256+ def test_zone_generator_handles_rdns_mode_equal_enabled(self):
257+ self.useFixture(RegionConfigurationFixture())
258+ Domain.objects.get_or_create(name="one")
259+ nodegroup = self.make_node_group(
260+ name="one", network=IPNetwork("10/29"))
261+ subnet = Subnet.objects.get(cidr="10.0.0.0/29")
262+ subnet.rdns_mode = RDNS_MODE.ENABLED
263+ subnet.save()
264+ default_domain = Domain.objects.get_default_domain()
265+ domains = Domain.objects.filter(name="one")
266+ subnets = Subnet.objects.filter(
267+ nodegroupinterface__nodegroup_id=nodegroup.id)
268+ expected_zones = (
269+ forward_zone("one"),
270+ reverse_zone(default_domain.name, "10/29"),
271 )
272 self.assertThat(
273 ZoneGenerator(domains, subnets, serial_generator=Mock()).as_list(),
274
275=== modified file 'src/maasserver/dns/zonegenerator.py'
276--- src/maasserver/dns/zonegenerator.py 2016-01-25 14:07:49 +0000
277+++ src/maasserver/dns/zonegenerator.py 2016-01-29 18:06:17 +0000
278@@ -16,7 +16,10 @@
279 import socket
280
281 from maasserver import logger
282-from maasserver.enum import NODEGROUP_STATUS
283+from maasserver.enum import (
284+ NODEGROUP_STATUS,
285+ RDNS_MODE,
286+)
287 from maasserver.exceptions import MAASException
288 from maasserver.models.config import Config
289 from maasserver.models.dnsresource import DNSResource
290@@ -287,6 +290,24 @@
291 # 2. node: ip mapping(subnet), including DNSResource records for
292 # StaticIPAddresses in this subnet
293 # 3. interfaces on any node that have IP addresses in this subnet
294+ rfc2317_glue = {}
295+ for subnet in subnets:
296+ network = IPNetwork(subnet.cidr)
297+ # If this is a small subnet and we are doing RFC2317 glue for it,
298+ # then we need to combine that with any other such subnets
299+ # We need to know this before we start creating reverse DNS zones.
300+ if subnet.rdns_mode == RDNS_MODE.RFC2317:
301+ if network.version == 4 and network.prefixlen > 24:
302+ # Turn 192.168.99.32/29 into 192.168.99.0/24
303+ basenet = IPNetwork(
304+ "%s/24" %
305+ IPNetwork("%s/24" % network.network).network)
306+ rfc2317_glue.setdefault(basenet, set()).add(network)
307+ elif network.version == 6 and network.prefixlen > 124:
308+ basenet = IPNetwork(
309+ "%s/124" %
310+ IPNetwork("%s/124" % network.network).network)
311+ rfc2317_glue.setdefault(basenet, set()).add(network)
312 for subnet in subnets:
313 network = IPNetwork(subnet.cidr)
314 nodegroups = set(
315@@ -333,12 +354,27 @@
316 })
317
318 # Use the default_domain as the name for the NS host in the reverse
319- # zones.
320+ # zones. If this network is actually a parent rfc2317 glue
321+ # network, then we need to generate the glue records.
322+ if network in rfc2317_glue:
323+ glue = rfc2317_glue[network]
324+ del(rfc2317_glue[network])
325+ else:
326+ glue = set()
327 yield DNSReverseZoneConfig(
328 Domain.objects.get_default_domain().name, serial=serial,
329 default_ttl=default_ttl,
330 mapping=mapping, network=network,
331 dynamic_ranges=dynamic_ranges,
332+ rfc2317_ranges=glue,
333+ )
334+ # Now provide any remaining rfc2317 glue networks.
335+ for network, ranges in rfc2317_glue.items():
336+ yield DNSReverseZoneConfig(
337+ Domain.objects.get_default_domain().name, serial=serial,
338+ default_ttl=default_ttl,
339+ network=network,
340+ rfc2317_ranges=ranges,
341 )
342
343 def __iter__(self):
344
345=== modified file 'src/maasserver/enum.py'
346--- src/maasserver/enum.py 2016-01-20 00:07:16 +0000
347+++ src/maasserver/enum.py 2016-01-29 18:06:17 +0000
348@@ -27,6 +27,9 @@
349 'PARTITION_TABLE_TYPE',
350 'PARTITION_TABLE_TYPE_CHOICES',
351 'PRESEED_TYPE',
352+ 'RDNS_MODE',
353+ 'RDNS_MODE_CHOICES',
354+ 'RDNS_MODE_CHOICES_DICT',
355 'USERDATA_TYPE',
356 ]
357
358@@ -221,6 +224,29 @@
359 OrderedDict(NODEGROUPINTERFACE_MANAGEMENT_CHOICES))
360
361
362+class RDNS_MODE:
363+ """The vocabulary of a `Subnet`'s possible reverse DNS modes."""
364+ # By default, we do what we've always done: assume we rule the DNS world.
365+ DEFAULT = 2
366+ #: Do not generate reverse DNS for this Subnet.
367+ DISABLED = 0
368+ #: Generate reverse DNS only for the CIDR.
369+ ENABLED = 1
370+ #: Generate RFC2317 glue if needed (Subnet is too small for its own zone.)
371+ RFC2317 = 2
372+
373+
374+# Django choices for RDNS_MODE: sequence of tuples (key, UI representation.)
375+RDNS_MODE_CHOICES = (
376+ (RDNS_MODE.DISABLED, "Disabled"),
377+ (RDNS_MODE.ENABLED, "Enabled"),
378+ (RDNS_MODE.RFC2317, "Enabled, with rfc2317 glue zone."),
379+)
380+
381+
382+RDNS_MODE_CHOICES_DICT = OrderedDict(RDNS_MODE_CHOICES)
383+
384+
385 class IPADDRESS_FAMILY:
386 """The vocabulary of possible IP family for `StaticIPAddress`."""
387 IPv4 = 4
388
389=== modified file 'src/maasserver/forms_subnet.py'
390--- src/maasserver/forms_subnet.py 2015-12-23 17:21:20 +0000
391+++ src/maasserver/forms_subnet.py 2016-01-29 18:06:17 +0000
392@@ -8,6 +8,7 @@
393 ]
394
395 from django import forms
396+from maasserver.enum import RDNS_MODE_CHOICES
397 from maasserver.fields import IPListFormField
398 from maasserver.forms import MAASModelForm
399 from maasserver.models.fabric import Fabric
400@@ -33,6 +34,9 @@
401 space = forms.ModelChoiceField(
402 queryset=Space.objects.all(), required=False)
403
404+ rdns_mode = forms.ChoiceField(
405+ choices=RDNS_MODE_CHOICES, required=False)
406+
407 class Meta:
408 model = Subnet
409 fields = (
410@@ -42,6 +46,7 @@
411 'cidr',
412 'gateway_ip',
413 'dns_servers',
414+ 'rdns_mode',
415 )
416
417 def __init__(self, *args, **kwargs):
418
419=== added file 'src/maasserver/migrations/builtin/maasserver/0023_add_rdns_mode.py'
420--- src/maasserver/migrations/builtin/maasserver/0023_add_rdns_mode.py 1970-01-01 00:00:00 +0000
421+++ src/maasserver/migrations/builtin/maasserver/0023_add_rdns_mode.py 2016-01-29 18:06:17 +0000
422@@ -0,0 +1,22 @@
423+# -*- coding: utf-8 -*-
424+from __future__ import unicode_literals
425+
426+from django.db import (
427+ migrations,
428+ models,
429+)
430+
431+
432+class Migration(migrations.Migration):
433+
434+ dependencies = [
435+ ('maasserver', '0022_create_zone_serial_sequence'),
436+ ]
437+
438+ operations = [
439+ migrations.AddField(
440+ model_name='subnet',
441+ name='rdns_mode',
442+ field=models.IntegerField(choices=[(0, 'Disabled'), (1, 'Enabled'), (2, 'Enabled, with rfc2317 glue zone.')], default=2),
443+ ),
444+ ]
445
446=== modified file 'src/maasserver/models/subnet.py'
447--- src/maasserver/models/subnet.py 2015-12-23 17:21:20 +0000
448+++ src/maasserver/models/subnet.py 2016-01-29 18:06:17 +0000
449@@ -18,6 +18,7 @@
450 from django.db.models import (
451 CharField,
452 ForeignKey,
453+ IntegerField,
454 Manager,
455 PROTECT,
456 Q,
457@@ -28,6 +29,8 @@
458 from maasserver.enum import (
459 NODEGROUP_STATUS,
460 NODEGROUPINTERFACE_MANAGEMENT,
461+ RDNS_MODE,
462+ RDNS_MODE_CHOICES,
463 )
464 from maasserver.fields import (
465 CIDRField,
466@@ -347,6 +350,10 @@
467 cidr = CIDRField(
468 blank=False, unique=True, editable=True, null=False)
469
470+ rdns_mode = IntegerField(
471+ choices=RDNS_MODE_CHOICES, editable=True,
472+ default=RDNS_MODE.DEFAULT)
473+
474 gateway_ip = MAASIPAddressField(blank=True, editable=True, null=True)
475
476 dns_servers = ArrayField(
477
478=== modified file 'src/maasserver/models/tests/test_subnet.py'
479--- src/maasserver/models/tests/test_subnet.py 2015-12-01 18:12:59 +0000
480+++ src/maasserver/models/tests/test_subnet.py 2016-01-29 18:06:17 +0000
481@@ -17,6 +17,7 @@
482 NODE_PERMISSION,
483 NODEGROUP_STATUS,
484 NODEGROUPINTERFACE_MANAGEMENT,
485+ RDNS_MODE_CHOICES,
486 )
487 from maasserver.models.subnet import (
488 create_cidr,
489@@ -417,14 +418,16 @@
490 dns_servers = [
491 factory.make_ip_address()
492 for _ in range(random.randint(1, 3))]
493+ rdns_mode = factory.pick_choice(RDNS_MODE_CHOICES)
494 subnet = Subnet(
495 name=name, vlan=vlan, cidr=cidr, gateway_ip=gateway_ip,
496- space=space, dns_servers=dns_servers)
497+ space=space, dns_servers=dns_servers, rdns_mode=rdns_mode)
498 subnet.save()
499 subnet_from_db = Subnet.objects.get(name=name)
500 self.assertThat(subnet_from_db, MatchesStructure.byEquality(
501 name=name, vlan=vlan, cidr=cidr, space=space,
502- gateway_ip=gateway_ip, dns_servers=dns_servers))
503+ gateway_ip=gateway_ip, dns_servers=dns_servers,
504+ rdns_mode=rdns_mode))
505
506 def test_validates_gateway_ip(self):
507 error = self.assertRaises(
508
509=== modified file 'src/maasserver/testing/factory.py'
510--- src/maasserver/testing/factory.py 2016-01-27 17:44:54 +0000
511+++ src/maasserver/testing/factory.py 2016-01-29 18:06:17 +0000
512@@ -37,6 +37,7 @@
513 NODEGROUPINTERFACE_MANAGEMENT,
514 PARTITION_TABLE_TYPE,
515 POWER_STATE,
516+ RDNS_MODE,
517 )
518 from maasserver.fields import (
519 LargeObjectFile,
520@@ -832,7 +833,7 @@
521
522 def make_Subnet(self, name=None, vlan=None, space=None, cidr=None,
523 gateway_ip=None, dns_servers=None, host_bits=None,
524- fabric=None, vid=None):
525+ fabric=None, vid=None, rdns_mode=RDNS_MODE.DEFAULT):
526 if name is None:
527 name = factory.make_name('name')
528 if vlan is None:
529@@ -849,7 +850,7 @@
530 self.make_ip_address() for _ in range(random.randint(1, 3))]
531 subnet = Subnet(
532 name=name, vlan=vlan, cidr=cidr, gateway_ip=gateway_ip,
533- space=space, dns_servers=dns_servers)
534+ space=space, dns_servers=dns_servers, rdns_mode=rdns_mode)
535 subnet.save()
536 return subnet
537
538
539=== modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py'
540--- src/maasserver/websockets/handlers/tests/test_subnet.py 2015-12-01 18:12:59 +0000
541+++ src/maasserver/websockets/handlers/tests/test_subnet.py 2016-01-29 18:06:17 +0000
542@@ -25,6 +25,7 @@
543 "dns_servers": [server for server in subnet.dns_servers],
544 "vlan": subnet.vlan_id,
545 "space": subnet.space_id,
546+ "rdns_mode": subnet.rdns_mode,
547 "cidr": subnet.cidr,
548 "gateway_ip": subnet.gateway_ip,
549 }
550
551=== modified file 'src/provisioningserver/dns/tests/test_zoneconfig.py'
552--- src/provisioningserver/dns/tests/test_zoneconfig.py 2016-01-19 22:34:22 +0000
553+++ src/provisioningserver/dns/tests/test_zoneconfig.py 2016-01-29 18:06:17 +0000
554@@ -359,7 +359,8 @@
555 factory.make_string(): factory.pick_ip_in_network(network),
556 }
557 expected = [
558- (IPAddress(ip).reverse_dns, 30, '%s.%s.' % (hostname, name))
559+ (IPAddress(ip).reverse_dns.split('.')[0], 30,
560+ '%s.%s.' % (hostname, name))
561 for hostname, ip in hosts.items()
562 ]
563 mapping = {
564@@ -378,7 +379,8 @@
565 factory.make_string(): factory.pick_ip_in_network(network),
566 }
567 expected = [
568- (IPAddress(ip).reverse_dns, 30, '%s.%s.' % (hostname, name))
569+ (IPAddress(ip).reverse_dns.split('.')[0], 30,
570+ '%s.%s.' % (hostname, name))
571 for hostname, ip in in_network_mapping.items()
572 ]
573 mapping = {
574
575=== modified file 'src/provisioningserver/dns/zoneconfig.py'
576--- src/provisioningserver/dns/zoneconfig.py 2016-01-19 22:34:22 +0000
577+++ src/provisioningserver/dns/zoneconfig.py 2016-01-29 18:06:17 +0000
578@@ -327,10 +327,13 @@
579 :param default_ttl: The default TTL for the zone.
580 :param network: The network that the mapping exists within.
581 :type network: :class:`netaddr.IPNetwork`
582+ :param rfc2317_ranges: List of ranges to generate RFC2317 CNAMEs for
583+ :type rfc2317_ranges: [:class:`netaddr.IPNetwork`]
584 """
585 self._mapping = kwargs.pop('mapping', {})
586 self._network = kwargs.pop("network", None)
587 self._dynamic_ranges = kwargs.pop('dynamic_ranges', [])
588+ self._rfc2317_ranges = kwargs.pop('rfc2317_ranges', [])
589 zone_info = self.compose_zone_info(self._network)
590 super(DNSReverseZoneConfig, self).__init__(
591 domain, zone_info=zone_info, **kwargs)
592@@ -428,13 +431,23 @@
593 :param mapping: A hostname: (ttl, [ip-addresseses]) mapping for all
594 known hosts in the reverse zone, to their FQDN (without trailing
595 dot).
596- :param network: DNS Zone's network.
597+ :param network: DNS Zone's network. (Not a supernet.)
598 :type network: :class:`netaddr.IPNetwork`
599 """
600+ def short_name(ip, network):
601+ long_name = IPAddress(ip).reverse_dns
602+ if network.version == 4:
603+ short_name = ".".join(long_name.split('.')[
604+ :(31 - network.prefixlen) // 8 + 1])
605+ else:
606+ short_name = ".".join(long_name.split('.')[
607+ :(127 - network.prefixlen) // 4 + 1])
608+ return short_name
609+
610 if mapping is None:
611 return ()
612 return (
613- (IPAddress(ip).reverse_dns, ttl, '%s.' % (hostname))
614+ (short_name(ip, network), ttl, '%s.' % (hostname))
615 for hostname, ttl, ip in enumerate_mapping(mapping)
616 # Filter out the IP addresses that are not in `network`.
617 if IPAddress(ip) in network
618@@ -479,6 +492,32 @@
619 return sorted(
620 generate_directives, key=lambda directive: directive[2])
621
622+ @classmethod
623+ def get_rfc2317_GENERATE_directives(cls, network, rfc2317_ranges, domain):
624+ """Return the GENERATE directives for any needed rfc2317 glue."""
625+ # A non-empty rfc2317_ranges means that the network is the most
626+ # specific that it can be (IPv4/24 or IPv6/124), so we can make some
627+ # simplifications in the GENERATE directives.
628+ generate_directives = set()
629+ for subnet in rfc2317_ranges:
630+ if network.version == 4:
631+ iterator = "%d-%d" % (
632+ (subnet.first & 0x000000ff),
633+ (subnet.last & 0x000000ff))
634+ hostname = "$.%d-%d" % (
635+ (subnet.first & 0x000000ff),
636+ subnet.prefixlen)
637+ generate_directives.add((iterator, '$', hostname))
638+ else:
639+ iterator = "%x-%d" % (
640+ (subnet.first & 0x0000000f),
641+ (subnet.last & 0x0000000f))
642+ hostname = "${0,1,x}.%x-%d" % (
643+ (subnet.first & 0x0000000f),
644+ subnet.prefixlen)
645+ generate_directives.add((iterator, '${0,1,x}', hostname))
646+ return sorted(generate_directives)
647+
648 def write_config(self):
649 """Write the zone file."""
650 # Create GENERATE directives for IPv4 ranges.
651@@ -502,6 +541,10 @@
652 'other_mapping': [],
653 'generate_directives': {
654 'PTR': generate_directives,
655+ 'CNAME': self.get_rfc2317_GENERATE_directives(
656+ zi.subnetwork,
657+ self._rfc2317_ranges,
658+ self.domain),
659 }
660 }
661 )