Merge lp:~lamont/maas/dns-1.9 into lp:maas/1.9

Proposed by LaMont Jones
Status: Rejected
Rejected by: LaMont Jones
Proposed branch: lp:~lamont/maas/dns-1.9
Merge into: lp:maas/1.9
Diff against target: 917 lines (+389/-151)
7 files modified
etc/maas/templates/dns/named.conf.template (+4/-2)
src/maasserver/dns/config.py (+4/-3)
src/provisioningserver/dns/actions.py (+17/-12)
src/provisioningserver/dns/config.py (+15/-4)
src/provisioningserver/dns/tests/test_actions.py (+4/-4)
src/provisioningserver/dns/tests/test_zoneconfig.py (+179/-52)
src/provisioningserver/dns/zoneconfig.py (+166/-74)
To merge this branch: bzr merge lp:~lamont/maas/dns-1.9
Reviewer Review Type Date Requested Status
MAAS Committers Pending
Review via email: mp+281879@code.launchpad.net

Commit message

Correctly manage reverse DNS: do not manage a /16 because the user added a /22 network.

Description of the change

Correctly manage reverse DNS: do not manage a /16 because the user added a /22 network.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

I thought that we agarres that since this was a change of behavior we were
going to defer this?

On Thursday, January 7, 2016, LaMont Jones <email address hidden>
wrote:

> LaMont Jones has proposed merging lp:~lamont/maas/dns-1.9 into lp:maas/1.9.
>
> Commit message:
> Correctly manage reverse DNS: do not manage a /16 because the user added a
> /22 network.
>
> Requested reviews:
> MAAS Committers (maas-committers)
> Related bugs:
> Bug #1356012 in MAAS: "maas incorrectly overmanages DNS reverse zones"
> https://bugs.launchpad.net/maas/+bug/1356012
> Bug #1531868 in MAAS: "Add network fails for multiple bigger-than-/24
> networks in a single /16"
> https://bugs.launchpad.net/maas/+bug/1531868
>
> For more details, see:
> https://code.launchpad.net/~lamont/maas/dns-1.9/+merge/281879
>
> Correctly manage reverse DNS: do not manage a /16 because the user added a
> /22 network.
> --
> Your team MAAS Committers is requested to review the proposed merge of
> lp:~lamont/maas/dns-1.9 into lp:maas/1.9.
>

--
Andres Rodriguez (RoAkSoAx)
Ubuntu Server Developer
MSc. Telecom & Networking
Systems Engineer

Revision history for this message
LaMont Jones (lamont) wrote :

That was before a maas 1.8 user reported #1531868, wherein maas tries to reload DNS for changes, and fails utterly to do so, because the named.conf is invalid.

I think that this bugfix warrants reconsideration since it's breaking maas 1.8 users in the field.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Ok, let's discuss this next week.

Thanks!

On Thu, Jan 14, 2016 at 2:00 AM, LaMont Jones <email address hidden>
wrote:

> That was before a maas 1.8 user reported #1531868, wherein maas tries to
> reload DNS for changes, and fails utterly to do so, because the named.conf
> is invalid.
>
> I think that this bugfix warrants reconsideration since it's breaking maas
> 1.8 users in the field.
> --
> https://code.launchpad.net/~lamont/maas/dns-1.9/+merge/281879
> Your team MAAS Committers is requested to review the proposed merge of
> lp:~lamont/maas/dns-1.9 into lp:maas/1.9.
>

--
Andres Rodriguez
Engineering Manager, MAAS
Canonical USA, Inc.

lp:~lamont/maas/dns-1.9 updated
4520. By LaMont Jones

Merge from maas/1.9.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Does this still need review, or is it WIP? (I was under the impression that this code has changed significantly in trunk, and would need to be ported back to 1.9 in order for it to be usable there. Is that correct?)

Unmerged revisions

4520. By LaMont Jones

Merge from maas/1.9.

4519. By LaMont Jones

Cleanup errors introduced in doing review feedback

4518. By LaMont Jones

Review feedback

4517. By LaMont Jones

For reverse zones, get_GENERATE_directives needs to follow rfc2317.

4516. By LaMont Jones

Correctly cast subnet.first to avoid errors with trusty's netaddr.

4515. By LaMont Jones

Merge DNS fixes from trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'etc/maas/templates/dns/named.conf.template'
--- etc/maas/templates/dns/named.conf.template 2014-11-14 21:50:42 +0000
+++ etc/maas/templates/dns/named.conf.template 2016-02-17 21:43:45 +0000
@@ -2,11 +2,13 @@
22
3# Zone declarations.3# Zone declarations.
4{{for zone in zones}}4{{for zone in zones}}
5zone "{{zone.zone_name}}" {5{{for zoneinfo in zone.zone_info}}
6zone "{{zoneinfo.zone_name}}" {
6 type master;7 type master;
7 file "{{zone.target_path}}";8 file "{{zoneinfo.target_path}}";
8};9};
9{{endfor}}10{{endfor}}
11{{endfor}}
1012
11# Access control for recursive queries. See named.conf.options.inside.maas13# Access control for recursive queries. See named.conf.options.inside.maas
12# for the directives used on this ACL.14# for the directives used on this ACL.
1315
=== modified file 'src/maasserver/dns/config.py'
--- src/maasserver/dns/config.py 2015-09-24 16:22:12 +0000
+++ src/maasserver/dns/config.py 2016-02-17 21:43:45 +0000
@@ -50,7 +50,7 @@
50 bind_reconfigure,50 bind_reconfigure,
51 bind_reload,51 bind_reload,
52 bind_reload_with_retries,52 bind_reload_with_retries,
53 bind_reload_zone,53 bind_reload_zones,
54 bind_write_configuration,54 bind_write_configuration,
55 bind_write_options,55 bind_write_options,
56 bind_write_zones,56 bind_write_zones,
@@ -144,9 +144,10 @@
144144
145 serial = next_zone_serial()145 serial = next_zone_serial()
146 for zone in ZoneGenerator(clusters, serial):146 for zone in ZoneGenerator(clusters, serial):
147 maaslog.info("Generating new DNS zone file for %s", zone.zone_name)147 names = [zi.zone_name for zi in zone.zone_info]
148 maaslog.info("Generating new DNS zone file for %s", " ".join(names))
148 bind_write_zones([zone])149 bind_write_zones([zone])
149 bind_reload_zone(zone.zone_name)150 bind_reload_zones(names)
150151
151152
152def dns_update_zones(clusters):153def dns_update_zones(clusters):
153154
=== modified file 'src/provisioningserver/dns/actions.py'
--- src/provisioningserver/dns/actions.py 2015-10-22 00:06:43 +0000
+++ src/provisioningserver/dns/actions.py 2016-02-17 21:43:45 +0000
@@ -15,7 +15,7 @@
15__all__ = [15__all__ = [
16 "bind_reconfigure",16 "bind_reconfigure",
17 "bind_reload",17 "bind_reload",
18 "bind_reload_zone",18 "bind_reload_zones",
19 "bind_write_configuration",19 "bind_write_configuration",
20 "bind_write_options",20 "bind_write_options",
21 "bind_write_zones",21 "bind_write_zones",
@@ -87,21 +87,26 @@
87 sleep(interval)87 sleep(interval)
8888
8989
90def bind_reload_zone(zone_name):90def bind_reload_zones(zone_list):
91 """Ask BIND to reload the zone file for the given zone.91 """Ask BIND to reload the zone file for the given zone.
9292
93 :param zone_name: The name of the zone to reload.93 :param zone_list: A list of zone names to reload, or a single name as a
94 string.
94 :return: True if success, False otherwise.95 :return: True if success, False otherwise.
95 """96 """
96 try:97 ret = True
97 execute_rndc_command(("reload", zone_name))98 if not isinstance(zone_list, list):
98 return True99 zone_list = [zone_list]
99 except CalledProcessError as exc:100 for name in zone_list:
100 maaslog.error(101 try:
101 "Reloading BIND zone %r failed (is it running?): %s",102 execute_rndc_command(("reload", name))
102 zone_name,103 except CalledProcessError as exc:
103 exc)104 maaslog.error(
104 return False105 "Reloading BIND zone %r failed (is it running?): %s",
106 name,
107 exc)
108 ret = False
109 return ret
105110
106111
107def bind_write_configuration(zones, trusted_networks):112def bind_write_configuration(zones, trusted_networks):
108113
=== modified file 'src/provisioningserver/dns/config.py'
--- src/provisioningserver/dns/config.py 2015-07-09 22:25:27 +0000
+++ src/provisioningserver/dns/config.py 2016-02-17 21:43:45 +0000
@@ -29,6 +29,7 @@
29import re29import re
30import sys30import sys
3131
32from provisioningserver.logger import get_maas_logger
32from provisioningserver.utils import locate_config33from provisioningserver.utils import locate_config
33from provisioningserver.utils.fs import atomic_write34from provisioningserver.utils.fs import atomic_write
34from provisioningserver.utils.isc import read_isc_file35from provisioningserver.utils.isc import read_isc_file
@@ -36,6 +37,7 @@
36import tempita37import tempita
3738
3839
40maaslog = get_maas_logger("dns")
39NAMED_CONF_OPTIONS = 'named.conf.options'41NAMED_CONF_OPTIONS = 'named.conf.options'
40MAAS_NAMED_CONF_NAME = 'named.conf.maas'42MAAS_NAMED_CONF_NAME = 'named.conf.maas'
41MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME = 'named.conf.options.inside.maas'43MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME = 'named.conf.options.inside.maas'
@@ -319,7 +321,16 @@
319321
320 @classmethod322 @classmethod
321 def get_include_snippet(cls):323 def get_include_snippet(cls):
322 target_path = compose_config_path(cls.target_file_name)324 snippet = ""
323 assert '"' not in target_path, (325 if isinstance(cls.target_file_name, list):
324 "DNS config path contains quote: %s." % target_path)326 target_file_names = cls.target_file_name
325 return 'include "%s";\n' % target_path327 else:
328 target_file_names = [cls.target_file_name]
329 for target_file_name in target_file_names:
330 target_path = compose_config_path(target_file_name)
331 if '"' in target_path:
332 maaslog.error(
333 "DNS config path contains quote: %s." % target_path)
334 else:
335 snippet += 'include "%s";\n' % target_path
336 return snippet
326337
=== modified file 'src/provisioningserver/dns/tests/test_actions.py'
--- src/provisioningserver/dns/tests/test_actions.py 2015-10-22 00:06:43 +0000
+++ src/provisioningserver/dns/tests/test_actions.py 2016-02-17 21:43:45 +0000
@@ -144,11 +144,11 @@
144144
145145
146class TestReloadZone(MAASTestCase):146class TestReloadZone(MAASTestCase):
147 """Tests for :py:func:`actions.bind_reload_zone`."""147 """Tests for :py:func:`actions.bind_reload_zones`."""
148148
149 def test__executes_rndc_command(self):149 def test__executes_rndc_command(self):
150 self.patch_autospec(actions, "execute_rndc_command")150 self.patch_autospec(actions, "execute_rndc_command")
151 self.assertTrue(actions.bind_reload_zone(sentinel.zone))151 self.assertTrue(actions.bind_reload_zones(sentinel.zone))
152 self.assertThat(152 self.assertThat(
153 actions.execute_rndc_command,153 actions.execute_rndc_command,
154 MockCalledOnceWith(("reload", sentinel.zone)))154 MockCalledOnceWith(("reload", sentinel.zone)))
@@ -157,7 +157,7 @@
157 erc = self.patch_autospec(actions, "execute_rndc_command")157 erc = self.patch_autospec(actions, "execute_rndc_command")
158 erc.side_effect = factory.make_CalledProcessError()158 erc.side_effect = factory.make_CalledProcessError()
159 with FakeLogger("maas") as logger:159 with FakeLogger("maas") as logger:
160 self.assertFalse(actions.bind_reload_zone(sentinel.zone))160 self.assertFalse(actions.bind_reload_zones(sentinel.zone))
161 self.assertDocTestMatches(161 self.assertDocTestMatches(
162 "Reloading BIND zone ... failed (is it running?): "162 "Reloading BIND zone ... failed (is it running?): "
163 "Command ... returned non-zero exit status ...",163 "Command ... returned non-zero exit status ...",
@@ -166,7 +166,7 @@
166 def test__false_on_subprocess_error(self):166 def test__false_on_subprocess_error(self):
167 erc = self.patch_autospec(actions, "execute_rndc_command")167 erc = self.patch_autospec(actions, "execute_rndc_command")
168 erc.side_effect = factory.make_CalledProcessError()168 erc.side_effect = factory.make_CalledProcessError()
169 self.assertFalse(actions.bind_reload_zone(sentinel.zone))169 self.assertFalse(actions.bind_reload_zones(sentinel.zone))
170170
171171
172class TestConfiguration(PservTestCase):172class TestConfiguration(PservTestCase):
173173
=== modified file 'src/provisioningserver/dns/tests/test_zoneconfig.py'
--- src/provisioningserver/dns/tests/test_zoneconfig.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/dns/tests/test_zoneconfig.py 2016-02-17 21:43:45 +0000
@@ -37,6 +37,7 @@
37from provisioningserver.dns.zoneconfig import (37from provisioningserver.dns.zoneconfig import (
38 DNSForwardZoneConfig,38 DNSForwardZoneConfig,
39 DNSReverseZoneConfig,39 DNSReverseZoneConfig,
40 DNSZoneInfo,
40)41)
41from testtools.matchers import (42from testtools.matchers import (
42 Contains,43 Contains,
@@ -102,7 +103,7 @@
102 dns_zone_config = DNSForwardZoneConfig(domain)103 dns_zone_config = DNSForwardZoneConfig(domain)
103 self.assertEqual(104 self.assertEqual(
104 os.path.join(get_dns_config_dir(), 'zone.%s' % domain),105 os.path.join(get_dns_config_dir(), 'zone.%s' % domain),
105 dns_zone_config.target_path)106 dns_zone_config.zone_info[0].target_path)
106107
107 def test_get_a_mapping_returns_ipv4_mapping(self):108 def test_get_a_mapping_returns_ipv4_mapping(self):
108 name = factory.make_string()109 name = factory.make_string()
@@ -266,7 +267,7 @@
266 factory.make_string(), serial=random.randint(1, 100),267 factory.make_string(), serial=random.randint(1, 100),
267 dns_ip=factory.make_ipv4_address())268 dns_ip=factory.make_ipv4_address())
268 dns_zone_config.write_config()269 dns_zone_config.write_config()
269 filepath = FilePath(dns_zone_config.target_path)270 filepath = FilePath(dns_zone_config.zone_info[0].target_path)
270 self.assertTrue(filepath.getPermissions().other.read)271 self.assertTrue(filepath.getPermissions().other.read)
271272
272273
@@ -290,34 +291,109 @@
290291
291 def test_computes_dns_config_file_paths(self):292 def test_computes_dns_config_file_paths(self):
292 domain = factory.make_name('zone')293 domain = factory.make_name('zone')
293 reverse_file_name = 'zone.168.192.in-addr.arpa'294 reverse_file_name = [
295 'zone.%d.168.192.in-addr.arpa' % i for i in range(4)]
294 dns_zone_config = DNSReverseZoneConfig(296 dns_zone_config = DNSReverseZoneConfig(
295 domain, network=IPNetwork("192.168.0.0/22"))297 domain, network=IPNetwork("192.168.0.0/22"))
298 for i in range(4):
299 self.assertEqual(
300 os.path.join(get_dns_config_dir(), reverse_file_name[i]),
301 dns_zone_config.zone_info[i].target_path)
302
303 def test_computes_dns_config_file_paths_for_small_network(self):
304 domain = factory.make_name('zone')
305 reverse_file_name = 'zone.192-27.0.168.192.in-addr.arpa'
306 dns_zone_config = DNSReverseZoneConfig(
307 domain, network=IPNetwork("192.168.0.192/27"))
308 self.assertEqual(1, len(dns_zone_config.zone_info))
296 self.assertEqual(309 self.assertEqual(
297 os.path.join(get_dns_config_dir(), reverse_file_name),310 os.path.join(get_dns_config_dir(), reverse_file_name),
298 dns_zone_config.target_path)311 dns_zone_config.zone_info[0].target_path)
299312
300 def test_reverse_zone_file(self):313 def test_reverse_zone_file(self):
301 # DNSReverseZoneConfig calculates the reverse zone file name314 # DNSReverseZoneConfig calculates the reverse zone file name
302 # correctly for IPv4 and IPv6 networks.315 # correctly for IPv4 and IPv6 networks.
316 # As long as the network size ends on a "nice" boundary (multiple of
317 # 8 for IPv4, multiple of 4 for IPv6), then there will be one reverse
318 # zone for the subnet. When it isn't, then there will be 2-128
319 # individual reverse zones for the subnet.
320 # A special case is the small subnet (less than 256 hosts for IPv4,
321 # less than 16 hosts for IPv6), in which case, we follow RFC2317 with
322 # the modern adjustment of using '-' instead of '/'.
323 zn = "%d.0.0.0.0.0.0.0.0.0.0.0.4.0.1.f.1.0.8.a.b.0.1.0.0.2.ip6.arpa"
303 expected = [324 expected = [
304 # IPv4 networks.325 # IPv4 networks.
305 (IPNetwork('192.168.0.1/22'), '168.192.in-addr.arpa'),326 # /22 ==> 4 /24 reverse zones
306 (IPNetwork('192.168.0.1/24'), '0.168.192.in-addr.arpa'),327 (
328 IPNetwork('192.168.0.1/22'),
329 [DNSZoneInfo(
330 IPNetwork('192.168.%d.0/24' % i),
331 '%d.168.192.in-addr.arpa' % i) for i in range(4)]
332 ),
333 # /24 ==> 1 reverse zone
334 (
335 IPNetwork('192.168.0.1/24'),
336 [DNSZoneInfo(
337 IPNetwork('192.168.0.0/24'),
338 '0.168.192.in-addr.arpa')]
339 ),
340 # /29 ==> 1 reverse zones, per RFC2317
341 (
342 IPNetwork('192.168.0.241/29'),
343 [DNSZoneInfo(
344 IPNetwork('192.168.0.240/29'),
345 '240-29.0.168.192.in-addr.arpa')]
346 ),
307 # IPv6 networks.347 # IPv6 networks.
308 (IPNetwork('3ffe:801::/32'), '1.0.8.0.e.f.f.3.ip6.arpa'),348 # /32, 48, 56, 64 ==> 1 reverse zones
309 (IPNetwork('2001:db8:0::/48'), '0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'),349 (
350 IPNetwork('3ffe:801::/32'),
351 [DNSZoneInfo(
352 IPNetwork('3ffe:801::32'),
353 '1.0.8.0.e.f.f.3.ip6.arpa')]),
354 (
355 IPNetwork('2001:db8:0::/48'),
356 [DNSZoneInfo(
357 IPNetwork('2001:db8:0::/48'),
358 '0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa')]
359 ),
310 (360 (
311 IPNetwork('2001:ba8:1f1:400::/56'),361 IPNetwork('2001:ba8:1f1:400::/56'),
312 '4.0.1.f.1.0.8.a.b.0.1.0.0.2.ip6.arpa'362 [DNSZoneInfo(
363 IPNetwork('2001:ba8:1f1:400::/56'),
364 '4.0.1.f.1.0.8.a.b.0.1.0.0.2.ip6.arpa')],
313 ),365 ),
314 (366 (
315 IPNetwork('2610:8:6800:1::/64'),367 IPNetwork('2610:8:6800:1::/64'),
316 '1.0.0.0.0.0.8.6.8.0.0.0.0.1.6.2.ip6.arpa',368 [DNSZoneInfo(
317 ),369 IPNetwork('2610:8:6800:1::/64'),
370 '1.0.0.0.0.0.8.6.8.0.0.0.0.1.6.2.ip6.arpa')],
371 ),
372 # /2 with hex digits ==> 4 /4 reverse zones
373 (
374 IPNetwork('8000::/2'),
375 [
376 DNSZoneInfo(IPNetwork('8000::/4'), '8.ip6.arpa'),
377 DNSZoneInfo(IPNetwork('9000::/4'), '9.ip6.arpa'),
378 DNSZoneInfo(IPNetwork('a000::/4'), 'a.ip6.arpa'),
379 DNSZoneInfo(IPNetwork('b000::/4'), 'b.ip6.arpa'),
380 ],
381 ),
382 # /103 ==> 2 /104 reverse zones
318 (383 (
319 IPNetwork('2001:ba8:1f1:400::/103'),384 IPNetwork('2001:ba8:1f1:400::/103'),
320 '0.0.0.0.0.0.0.0.0.0.0.4.0.1.f.1.0.8.a.b.0.1.0.0.2.ip6.arpa',385 [DNSZoneInfo(
386 IPNetwork('2001:ba8:1f1:400:0:0:%d00:0000/104' % i),
387 zn % i) for i in range(2)],
388 ),
389 # /125 ==> 1 reverse zone, based on RFC2317
390 (
391 IPNetwork('2001:ba8:1f1:400::/125'),
392 [DNSZoneInfo(
393 IPNetwork('2001:ba8:1f1:400::/125'),
394 "0-125.%s" % (
395 IPAddress('2001:ba8:1f1:400::').reverse_dns[2:-1]))],
396
321 ),397 ),
322398
323 ]399 ]
@@ -325,8 +401,15 @@
325 for network, _ in expected:401 for network, _ in expected:
326 domain = factory.make_name('zone')402 domain = factory.make_name('zone')
327 dns_zone_config = DNSReverseZoneConfig(domain, network=network)403 dns_zone_config = DNSReverseZoneConfig(domain, network=network)
328 results.append((network, dns_zone_config.zone_name))404 results.append((network, dns_zone_config.zone_info))
329 self.assertEqual(expected, results)405 # Make sure we have the right number of elements.
406 self.assertEqual(len(expected), len(results))
407 # And that the zone names chosen for each element are correct.
408 for net in range(len(expected)):
409 for zi in range(len(expected[net][1])):
410 self.assertItemsEqual(
411 expected[net][1][zi].zone_name,
412 results[net][1][zi].zone_name)
330413
331 def test_get_ptr_mapping(self):414 def test_get_ptr_mapping(self):
332 name = factory.make_string()415 name = factory.make_string()
@@ -378,11 +461,12 @@
378 factory.make_string(), serial=random.randint(1, 100),461 factory.make_string(), serial=random.randint(1, 100),
379 network=network)462 network=network)
380 dns_zone_config.write_config()463 dns_zone_config.write_config()
381 self.assertThat(464 for zone_name in [zi.zone_name for zi in dns_zone_config.zone_info]:
382 os.path.join(465 self.assertThat(
383 target_dir, 'zone.%s' % dns_zone_config.zone_name),466 os.path.join(
384 FileContains(467 target_dir, 'zone.%s' % zone_name),
385 matcher=Contains('IN NS %s.' % dns_zone_config.domain)))468 FileContains(
469 matcher=Contains('IN NS %s.' % dns_zone_config.domain)))
386470
387 def test_writes_reverse_dns_zone_config(self):471 def test_writes_reverse_dns_zone_config(self):
388 target_dir = patch_dns_config_path(self)472 target_dir = patch_dns_config_path(self)
@@ -394,9 +478,41 @@
394 dynamic_ranges=[478 dynamic_ranges=[
395 IPRange(dynamic_network.first, dynamic_network.last)])479 IPRange(dynamic_network.first, dynamic_network.last)])
396 dns_zone_config.write_config()480 dns_zone_config.write_config()
397 reverse_file_name = 'zone.168.192.in-addr.arpa'481 for sub in range(4):
398 expected_generate_directives = dns_zone_config.get_GENERATE_directives(482 reverse_file_name = 'zone.%d.168.192.in-addr.arpa' % sub
399 dynamic_network, domain)483 expected_GEN_direct = dns_zone_config.get_GENERATE_directives(
484 dynamic_network, domain,
485 DNSZoneInfo(
486 IPNetwork('192.168.%d.0/24' % sub),
487 "%d.168.192.in-addr.arpa" % sub))
488 expected = ContainsAll(
489 [
490 'IN NS %s' % domain
491 ] +
492 [
493 '$GENERATE %s %s IN PTR %s' % (
494 iterator_values, reverse_dns, hostname)
495 for iterator_values, reverse_dns, hostname in
496 expected_GEN_direct
497 ])
498 self.assertThat(
499 os.path.join(target_dir, reverse_file_name),
500 FileContains(matcher=expected))
501
502 def test_writes_reverse_dns_zone_config_for_small_network(self):
503 target_dir = patch_dns_config_path(self)
504 domain = factory.make_string()
505 network = IPNetwork('192.168.0.1/26')
506 dynamic_network = IPNetwork('192.168.0.1/28')
507 dns_zone_config = DNSReverseZoneConfig(
508 domain, serial=random.randint(1, 100), network=network,
509 dynamic_ranges=[
510 IPRange(dynamic_network.first, dynamic_network.last)])
511 dns_zone_config.write_config()
512 reverse_zone_name = '0-26.0.168.192.in-addr.arpa'
513 reverse_file_name = 'zone.0-26.0.168.192.in-addr.arpa'
514 expected_GEN_direct = dns_zone_config.get_GENERATE_directives(
515 dynamic_network, domain, DNSZoneInfo(network, reverse_zone_name))
400 expected = ContainsAll(516 expected = ContainsAll(
401 [517 [
402 'IN NS %s' % domain518 'IN NS %s' % domain
@@ -405,7 +521,7 @@
405 '$GENERATE %s %s IN PTR %s' % (521 '$GENERATE %s %s IN PTR %s' % (
406 iterator_values, reverse_dns, hostname)522 iterator_values, reverse_dns, hostname)
407 for iterator_values, reverse_dns, hostname in523 for iterator_values, reverse_dns, hostname in
408 expected_generate_directives524 expected_GEN_direct
409 ])525 ])
410 self.assertThat(526 self.assertThat(
411 os.path.join(target_dir, reverse_file_name),527 os.path.join(target_dir, reverse_file_name),
@@ -431,8 +547,9 @@
431 factory.make_string(), serial=random.randint(1, 100),547 factory.make_string(), serial=random.randint(1, 100),
432 network=factory.make_ipv4_network())548 network=factory.make_ipv4_network())
433 dns_zone_config.write_config()549 dns_zone_config.write_config()
434 filepath = FilePath(dns_zone_config.target_path)550 for tgt in [zi.target_path for zi in dns_zone_config.zone_info]:
435 self.assertTrue(filepath.getPermissions().other.read)551 filepath = FilePath(tgt)
552 self.assertTrue(filepath.getPermissions().other.read)
436553
437554
438class TestDNSReverseZoneConfig_GetGenerateDirectives(MAASTestCase):555class TestDNSReverseZoneConfig_GetGenerateDirectives(MAASTestCase):
@@ -451,7 +568,11 @@
451 self.assertItemsEqual(568 self.assertItemsEqual(
452 expected_directives,569 expected_directives,
453 DNSReverseZoneConfig.get_GENERATE_directives(570 DNSReverseZoneConfig.get_GENERATE_directives(
454 ip_range, domain="domain"))571 ip_range,
572 domain="domain",
573 zone_info=DNSZoneInfo(
574 IPNetwork('192.168.0.0/16'),
575 "168.192.in-addr.arpa")))
455576
456 def get_expected_generate_directives(self, network, domain):577 def get_expected_generate_directives(self, network, domain):
457 ip_parts = network.network.format().split('.')578 ip_parts = network.network.format().split('.')
@@ -481,8 +602,11 @@
481 relevant_ip_parts.reverse()602 relevant_ip_parts.reverse()
482 expected_rdns_base = (603 expected_rdns_base = (
483 "%s.%s.in-addr.arpa." % tuple(relevant_ip_parts))604 "%s.%s.in-addr.arpa." % tuple(relevant_ip_parts))
484 expected_rdns_template = "$.%s.%s" % (605 if network.size >= 256:
485 num + second_octet_offset, expected_rdns_base)606 expected_rdns_template = "$.%s.%s" % (
607 num + second_octet_offset, expected_rdns_base)
608 else:
609 expected_rdns_template = "$"
486 expected_generate_directives.append(610 expected_generate_directives.append(
487 (611 (
488 "%s-%s" % (iterator_low, iterator_high),612 "%s-%s" % (iterator_low, iterator_high),
@@ -494,22 +618,25 @@
494618
495 def test_returns_single_entry_for_slash_24_network(self):619 def test_returns_single_entry_for_slash_24_network(self):
496 network = IPNetwork("%s/24" % factory.make_ipv4_address())620 network = IPNetwork("%s/24" % factory.make_ipv4_address())
621 reverse = ".".join(IPAddress(network).reverse_dns.split('.')[1:-1])
497 domain = factory.make_string()622 domain = factory.make_string()
498 expected_generate_directives = self.get_expected_generate_directives(623 expected_generate_directives = self.get_expected_generate_directives(
499 network, domain)624 network, domain)
500 directives = DNSReverseZoneConfig.get_GENERATE_directives(625 directives = DNSReverseZoneConfig.get_GENERATE_directives(
501 network, domain)626 network, domain, DNSZoneInfo(network, reverse))
502 self.expectThat(directives, HasLength(1))627 self.expectThat(directives, HasLength(1))
503 self.assertItemsEqual(expected_generate_directives, directives)628 self.assertItemsEqual(expected_generate_directives, directives)
504629
505 def test_returns_single_entry_for_tiny_network(self):630 def test_returns_single_entry_for_tiny_network(self):
506 network = IPNetwork("%s/28" % factory.make_ipv4_address())631 network = IPNetwork("%s/28" % factory.make_ipv4_address())
632 reverse = IPAddress(network).reverse_dns.split('.')
633 reverse = ".".join(["%s-28" % reverse[0]] + reverse[1:-1])
507 domain = factory.make_string()634 domain = factory.make_string()
508635
509 expected_generate_directives = self.get_expected_generate_directives(636 expected_generate_directives = self.get_expected_generate_directives(
510 network, domain)637 network, domain)
511 directives = DNSReverseZoneConfig.get_GENERATE_directives(638 directives = DNSReverseZoneConfig.get_GENERATE_directives(
512 network, domain)639 network, domain, DNSZoneInfo(network, reverse))
513 self.expectThat(directives, HasLength(1))640 self.expectThat(directives, HasLength(1))
514 self.assertItemsEqual(expected_generate_directives, directives)641 self.assertItemsEqual(expected_generate_directives, directives)
515642
@@ -517,31 +644,22 @@
517 ip_range = IPRange('10.0.0.1', '10.0.0.255')644 ip_range = IPRange('10.0.0.1', '10.0.0.255')
518 domain = factory.make_string()645 domain = factory.make_string()
519 directives = DNSReverseZoneConfig.get_GENERATE_directives(646 directives = DNSReverseZoneConfig.get_GENERATE_directives(
520 ip_range, domain)647 ip_range, domain,
648 DNSZoneInfo(IPNetwork('10.0.0.0/24'), '0.0.10.in-addr.arpa'))
521 self.expectThat(directives, HasLength(1))649 self.expectThat(directives, HasLength(1))
522650
523 def test_dtrt_for_larger_networks(self):651 # generate 2 zones, rather than 1 zone with 2 GENERATEs.
524 # For every other network size that we're not explicitly652 def test_returns_256_entries_for_slash_16_network(self):
525 # testing here,653 network = IPNetwork(factory.make_ipv4_network(slash=16))
526 # DNSReverseZoneConfig.get_GENERATE_directives() will return654 reverse = IPAddress(network.first).reverse_dns.split('.')[2:-1]
527 # one GENERATE directive for every 255 addresses in the network.655 reverse = ".".join(reverse)
528 for prefixlen in range(23, 17):
529 network = IPNetwork(
530 "%s/%s" % (factory.make_ipv4_address(), prefixlen))
531 domain = factory.make_string()
532 directives = DNSReverseZoneConfig.get_GENERATE_directives(
533 network, domain)
534 self.expectThat(directives, HasLength(network.size / 256))
535
536 def test_returns_two_entries_for_slash_23_network(self):
537 network = IPNetwork(factory.make_ipv4_network(slash=23))
538 domain = factory.make_string()656 domain = factory.make_string()
539657
540 expected_generate_directives = self.get_expected_generate_directives(658 expected_generate_directives = self.get_expected_generate_directives(
541 network, domain)659 network, domain)
542 directives = DNSReverseZoneConfig.get_GENERATE_directives(660 directives = DNSReverseZoneConfig.get_GENERATE_directives(
543 network, domain)661 network, domain, DNSZoneInfo(network, reverse))
544 self.expectThat(directives, HasLength(2))662 self.expectThat(directives, HasLength(256))
545 self.assertItemsEqual(expected_generate_directives, directives)663 self.assertItemsEqual(expected_generate_directives, directives)
546664
547 def test_ignores_network_larger_than_slash_16(self):665 def test_ignores_network_larger_than_slash_16(self):
@@ -549,7 +667,8 @@
549 self.assertEqual(667 self.assertEqual(
550 [],668 [],
551 DNSReverseZoneConfig.get_GENERATE_directives(669 DNSReverseZoneConfig.get_GENERATE_directives(
552 network, factory.make_string()))670 network, factory.make_string(),
671 DNSZoneInfo(network, "do not care")))
553672
554 def test_ignores_networks_that_span_slash_16s(self):673 def test_ignores_networks_that_span_slash_16s(self):
555 # If the upper and lower bounds of a range span two /16 networks674 # If the upper and lower bounds of a range span two /16 networks
@@ -557,7 +676,8 @@
557 # get_GENERATE_directives() will return early676 # get_GENERATE_directives() will return early
558 ip_range = IPRange('10.0.0.55', '10.1.0.54')677 ip_range = IPRange('10.0.0.55', '10.1.0.54')
559 directives = DNSReverseZoneConfig.get_GENERATE_directives(678 directives = DNSReverseZoneConfig.get_GENERATE_directives(
560 ip_range, factory.make_string())679 ip_range, factory.make_string(),
680 DNSZoneInfo(IPNetwork('10.0.0.0/15'), "do not care"))
561 self.assertEqual([], directives)681 self.assertEqual([], directives)
562682
563 def test_sorts_output_by_hostname(self):683 def test_sorts_output_by_hostname(self):
@@ -568,12 +688,19 @@
568 expected_rdns = "$.%s.0.10.in-addr.arpa."688 expected_rdns = "$.%s.0.10.in-addr.arpa."
569689
570 directives = list(DNSReverseZoneConfig.get_GENERATE_directives(690 directives = list(DNSReverseZoneConfig.get_GENERATE_directives(
571 network, domain))691 network, domain,
692 DNSZoneInfo(IPNetwork('10.0.0.0/24'), '0.0.10.in-addr.arpa')))
572 self.expectThat(693 self.expectThat(
573 directives[0], Equals(694 directives[0], Equals(
574 ("0-255", expected_rdns % "0", expected_hostname % "0")))695 ("0-255", expected_rdns % "0", expected_hostname % "0")))
696
697 expected_hostname = "10-0-%s-$." + domain + "."
698 expected_rdns = "$.%s.0.10.in-addr.arpa."
699 directives = list(DNSReverseZoneConfig.get_GENERATE_directives(
700 network, domain,
701 DNSZoneInfo(IPNetwork('10.0.1.0/24'), '1.0.10.in-addr.arpa')))
575 self.expectThat(702 self.expectThat(
576 directives[1], Equals(703 directives[0], Equals(
577 ("0-255", expected_rdns % "1", expected_hostname % "1")))704 ("0-255", expected_rdns % "1", expected_hostname % "1")))
578705
579706
580707
=== modified file 'src/provisioningserver/dns/zoneconfig.py'
--- src/provisioningserver/dns/zoneconfig.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/dns/zoneconfig.py 2016-02-17 21:43:45 +0000
@@ -15,13 +15,13 @@
15__all__ = [15__all__ = [
16 'DNSForwardZoneConfig',16 'DNSForwardZoneConfig',
17 'DNSReverseZoneConfig',17 'DNSReverseZoneConfig',
18 'DNSZoneInfo',
18 ]19 ]
1920
2021
21from abc import ABCMeta22from abc import ABCMeta
22from datetime import datetime23from datetime import datetime
23from itertools import chain24from itertools import chain
24import math
2525
26from netaddr import (26from netaddr import (
27 IPAddress,27 IPAddress,
@@ -106,6 +106,24 @@
106 return intersecting_subnets, prefix, rdns_suffix106 return intersecting_subnets, prefix, rdns_suffix
107107
108108
109class DNSZoneInfo:
110 """Information about a DNS zone"""
111
112 def __init__(self, subnetwork, zone_name, target_path=None):
113 """
114 :param subnetwork: IPNetwork that this zone (chunk) is for. None
115 for forward zones.
116 :param zone_name: Fully-qualified zone name
117 :param target_path: Optional, can be used to override the target path.
118 """
119 self.subnetwork = subnetwork
120 self.zone_name = zone_name
121 if target_path is None:
122 self.target_path = compose_config_path('zone.%s' % zone_name)
123 else:
124 self.target_path = target_path
125
126
109class DNSZoneConfigBase:127class DNSZoneConfigBase:
110 """Base class for zone writers."""128 """Base class for zone writers."""
111129
@@ -113,17 +131,17 @@
113131
114 template_file_name = 'zone.template'132 template_file_name = 'zone.template'
115133
116 def __init__(self, domain, zone_name, serial=None):134 def __init__(self, domain, zone_info, serial=None):
117 """135 """
118 :param domain: The domain name of the forward zone.136 :param domain: The domain name of the forward zone.
119 :param zone_name: Fully-qualified zone name.137 :param zone_info: list of DNSZoneInfo entries.
120 :param serial: The serial to use in the zone file. This must increment138 :param serial: The serial to use in the zone file. This must increment
121 on each change.139 on each change.
122 """140 """
123 self.domain = domain141 self.domain = domain
124 self.zone_name = zone_name
125 self.serial = serial142 self.serial = serial
126 self.target_path = compose_config_path('zone.%s' % self.zone_name)143 self.zone_info = zone_info
144 self.target_base = compose_config_path('zone')
127145
128 def make_parameters(self):146 def make_parameters(self):
129 """Return a dict of the common template parameters."""147 """Return a dict of the common template parameters."""
@@ -142,9 +160,12 @@
142 support a resolution of one second, and so this method may set an160 support a resolution of one second, and so this method may set an
143 unexpected modification time in order to maintain that property.161 unexpected modification time in order to maintain that property.
144 """162 """
145 content = render_dns_template(cls.template_file_name, *parameters)163 if not isinstance(output_file, list):
146 with report_missing_config_dir():164 output_file = [output_file]
147 incremental_write(content, output_file, mode=0644)165 for outfile in output_file:
166 content = render_dns_template(cls.template_file_name, *parameters)
167 with report_missing_config_dir():
168 incremental_write(content, outfile, mode=0644)
148169
149170
150class DNSForwardZoneConfig(DNSZoneConfigBase):171class DNSForwardZoneConfig(DNSZoneConfigBase):
@@ -175,7 +196,7 @@
175 self._dynamic_ranges = kwargs.pop('dynamic_ranges', [])196 self._dynamic_ranges = kwargs.pop('dynamic_ranges', [])
176 self._srv_mapping = kwargs.pop('srv_mapping', [])197 self._srv_mapping = kwargs.pop('srv_mapping', [])
177 super(DNSForwardZoneConfig, self).__init__(198 super(DNSForwardZoneConfig, self).__init__(
178 domain, zone_name=domain, **kwargs)199 domain, zone_info=[DNSZoneInfo(None, domain)], **kwargs)
179200
180 @classmethod201 @classmethod
181 def get_mapping(cls, mapping, domain, dns_ip):202 def get_mapping(cls, mapping, domain, dns_ip):
@@ -255,7 +276,7 @@
255 return []276 return []
256277
257 generate_directives = set()278 generate_directives = set()
258 subnets, prefix, _ = get_details_for_ip_range(dynamic_range)279 subnets, prefix, rdns_suffix = get_details_for_ip_range(dynamic_range)
259 for subnet in subnets:280 for subnet in subnets:
260 iterator = "%d-%d" % (281 iterator = "%d-%d" % (
261 (subnet.first & 0x000000ff),282 (subnet.first & 0x000000ff),
@@ -279,27 +300,28 @@
279 def write_config(self):300 def write_config(self):
280 """Write the zone file."""301 """Write the zone file."""
281 # Create GENERATE directives for IPv4 ranges.302 # Create GENERATE directives for IPv4 ranges.
282 generate_directives = list(303 for zi in self.zone_info:
283 chain.from_iterable(304 generate_directives = list(
284 self.get_GENERATE_directives(dynamic_range)305 chain.from_iterable(
285 for dynamic_range in self._dynamic_ranges306 self.get_GENERATE_directives(dynamic_range)
286 if dynamic_range.version == 4307 for dynamic_range in self._dynamic_ranges
287 ))308 if dynamic_range.version == 4
288 self.write_zone_file(309 ))
289 self.target_path, self.make_parameters(),310 self.write_zone_file(
290 {311 zi.target_path, self.make_parameters(),
291 'mappings': {312 {
292 'SRV': self.get_srv_mapping(313 'mappings': {
293 self._srv_mapping),314 'SRV': self.get_srv_mapping(
294 'A': self.get_A_mapping(315 self._srv_mapping),
295 self._mapping, self.domain, self._dns_ip),316 'A': self.get_A_mapping(
296 'AAAA': self.get_AAAA_mapping(317 self._mapping, self.domain, self._dns_ip),
297 self._mapping, self.domain, self._dns_ip),318 'AAAA': self.get_AAAA_mapping(
298 },319 self._mapping, self.domain, self._dns_ip),
299 'generate_directives': {320 },
300 'A': generate_directives,321 'generate_directives': {
301 }322 'A': generate_directives,
302 })323 }
324 })
303325
304326
305class DNSReverseZoneConfig(DNSZoneConfigBase):327class DNSReverseZoneConfig(DNSZoneConfigBase):
@@ -325,32 +347,88 @@
325 self._mapping = kwargs.pop('mapping', {})347 self._mapping = kwargs.pop('mapping', {})
326 self._network = kwargs.pop("network", None)348 self._network = kwargs.pop("network", None)
327 self._dynamic_ranges = kwargs.pop('dynamic_ranges', [])349 self._dynamic_ranges = kwargs.pop('dynamic_ranges', [])
328 zone_name = self.compose_zone_name(self._network)350 zone_info = self.compose_zone_info(self._network)
329 super(DNSReverseZoneConfig, self).__init__(351 super(DNSReverseZoneConfig, self).__init__(
330 domain, zone_name=zone_name, **kwargs)352 domain, zone_info=zone_info, **kwargs)
331353
332 @classmethod354 @classmethod
333 def compose_zone_name(cls, network):355 def compose_zone_info(cls, network):
334 """Return the name of the reverse zone."""356 """Return the names of the reverse zones."""
335 # Generate the name of the reverse zone file:357 # Generate the name of the reverse zone file:
336 # Use netaddr's reverse_dns() to get the reverse IP name358 # Use netaddr's reverse_dns() to get the reverse IP name
337 # of the first IP address in the network and then drop the first359 # of the first IP address in the network and then drop the first
338 # octets of that name (i.e. drop the octets that will be specified in360 # octets of that name (i.e. drop the octets that will be specified in
339 # the zone file).361 # the zone file).
362 # returns a list of (IPNetwork, zone_name, zonefile_path) tuples
363 info = []
340 first = IPAddress(network.first)364 first = IPAddress(network.first)
365 last = IPAddress(network.last)
341 if first.version == 6:366 if first.version == 6:
342 # IPv6.367 # IPv6.
343 # Use float division and ceil to cope with network sizes that368 # 2001:89ab::/19 yields 8.1.0.0.2.ip6.arpa, and the full list
344 # are not divisible by 4.369 # is 8.1.0.0.2.ip6.arpa, 9.1.0.0.2.ip6.arpa
345 rest_limit = int(math.ceil((128 - network.prefixlen) / 4.))370 # The ipv6 reverse dns form is 32 elements of 1 hex digit each.
371 # How many elements of the reverse DNS name to we throw away?
372 # Prefixlen of 0-3 gives us 1, 4-7 gives us 2, etc.
373 # While this seems wrong, we always _add_ a base label back in,
374 # so it's correct.
375 rest_limit = (132 - network.prefixlen) / 4
376 # What is the prefix for each inner subnet (It will be the next
377 # smaller multiple of 4.) If it's the smallest one, then RFC2317
378 # tells us that we're adding an extra blob to the front of the
379 # reverse zone name, and we want the entire prefixlen.
380 subnet_prefix = (network.prefixlen + 3) / 4 * 4
381 if subnet_prefix == 128:
382 subnet_prefix = network.prefixlen
383 # How big is the step between subnets? Again, special case for
384 # extra small subnets.
385 step = 1 << ((128 - network.prefixlen) / 4 * 4)
386 if step < 16:
387 step = 16
388 # Grab the base (hex) and trailing labels for our reverse zone.
389 split_zone = first.reverse_dns.split('.')
390 zone_rest = ".".join(split_zone[rest_limit:-1])
391 base = int(split_zone[rest_limit - 1], 16)
346 else:392 else:
347 # IPv4.393 # IPv4.
348 # Use float division and ceil to cope with splits not done on394 # The logic here is the same as for IPv6, but with 8 instead of 4.
349 # octets boundaries.395 rest_limit = (40 - network.prefixlen) / 8
350 rest_limit = int(math.ceil((32 - network.prefixlen) / 8.))396 subnet_prefix = (network.prefixlen + 7) / 8 * 8
351 reverse_name = first.reverse_dns.split('.', rest_limit)[-1]397 if subnet_prefix == 32:
352 # Strip off trailing '.'.398 subnet_prefix = network.prefixlen
353 return reverse_name[:-1]399 step = 1 << ((32 - network.prefixlen) / 8 * 8)
400 if step < 256:
401 step = 256
402 # Grab the base (decimal) and trailing labels for our reverse
403 # zone.
404 split_zone = first.reverse_dns.split('.')
405 zone_rest = ".".join(split_zone[rest_limit:-1])
406 base = int(split_zone[rest_limit - 1])
407 while first <= last:
408 # Rest_limit has bounds of 1..labelcount+1 (5 or 33).
409 # If we're stripping any elements, then we just want base.name.
410 if rest_limit > 1:
411 if first.version == 6:
412 new_zone = "%x.%s" % (base, zone_rest)
413 else:
414 new_zone = "%d.%s" % (base, zone_rest)
415 # We didn't actually strip any elemnts, so base goes back with
416 # the prefixlen attached.
417 elif first.version == 6:
418 new_zone = "%x-%d.%s" % (base, network.prefixlen, zone_rest)
419 else:
420 new_zone = "%d-%d.%s" % (base, network.prefixlen, zone_rest)
421 info.append(DNSZoneInfo(
422 IPNetwork("%s/%d" % (first, subnet_prefix)),
423 new_zone))
424 base += 1
425 try:
426 first += step
427 except IndexError:
428 # IndexError occurs when we go from 255.255.255.255 to
429 # 0.0.0.0. If we hit that, we're all fine and done.
430 break
431 return info
354432
355 @classmethod433 @classmethod
356 def get_PTR_mapping(cls, mapping, domain, network):434 def get_PTR_mapping(cls, mapping, domain, network):
@@ -366,7 +444,7 @@
366 :param mapping: A hostname:ip-addresses mapping for all known hosts in444 :param mapping: A hostname:ip-addresses mapping for all known hosts in
367 the reverse zone.445 the reverse zone.
368 :param domain: Zone's domain name.446 :param domain: Zone's domain name.
369 :param network: Zone's network.447 :param network: DNS Zone's network.
370 :type network: :class:`netaddr.IPNetwork`448 :type network: :class:`netaddr.IPNetwork`
371 """449 """
372 return (450 return (
@@ -380,7 +458,7 @@
380 )458 )
381459
382 @classmethod460 @classmethod
383 def get_GENERATE_directives(cls, dynamic_range, domain):461 def get_GENERATE_directives(cls, dynamic_range, domain, zone_info):
384 """Return the GENERATE directives for the reverse zone of a network."""462 """Return the GENERATE directives for the reverse zone of a network."""
385 slash_16 = IPNetwork("%s/16" % IPAddress(dynamic_range.first))463 slash_16 = IPNetwork("%s/16" % IPAddress(dynamic_range.first))
386 if (dynamic_range.size > 256 ** 2 or464 if (dynamic_range.size > 256 ** 2 or
@@ -391,19 +469,29 @@
391 return []469 return []
392470
393 generate_directives = set()471 generate_directives = set()
472 # The largest subnet returned is a /24.
394 subnets, prefix, rdns_suffix = get_details_for_ip_range(dynamic_range)473 subnets, prefix, rdns_suffix = get_details_for_ip_range(dynamic_range)
395 for subnet in subnets:474 for subnet in subnets:
396 iterator = "%d-%d" % (475 if (IPAddress(subnet.first) in zone_info.subnetwork):
397 (subnet.first & 0x000000ff),476 iterator = "%d-%d" % (
398 (subnet.last & 0x000000ff))477 (subnet.first & 0x000000ff),
399 hostname = "%s-%d-$" % (478 (subnet.last & 0x000000ff))
400 prefix.replace('.', '-'),479 hostname = "%s-%d-$" % (
401 (subnet.first & 0x0000ff00) >> 8)480 prefix.replace('.', '-'),
402 rdns = "$.%d.%s" % (481 (subnet.first & 0x0000ff00) >> 8)
403 (subnet.first & 0x0000ff00) >> 8,482 # If we're at least a /24, then fully specify the name,
404 rdns_suffix)483 # rather than trying to figure out how much of the name
405 generate_directives.add(484 # is in the zone name.
406 (iterator, rdns, "%s.%s." % (hostname, domain)))485 if zone_info.subnetwork.prefixlen <= 24:
486 rdns = "$.%d.%s" % (
487 (subnet.first & 0x0000ff00) >> 8,
488 rdns_suffix)
489 else:
490 # Let the zone declaration provide the suffix.
491 # rather than trying to calculate it.
492 rdns = "$"
493 generate_directives.add(
494 (iterator, rdns, "%s.%s." % (hostname, domain)))
407495
408 return sorted(496 return sorted(
409 generate_directives, key=lambda directive: directive[2])497 generate_directives, key=lambda directive: directive[2])
@@ -411,21 +499,25 @@
411 def write_config(self):499 def write_config(self):
412 """Write the zone file."""500 """Write the zone file."""
413 # Create GENERATE directives for IPv4 ranges.501 # Create GENERATE directives for IPv4 ranges.
414 generate_directives = list(502 for zi in self.zone_info:
415 chain.from_iterable(503 generate_directives = list(
416 self.get_GENERATE_directives(dynamic_range, self.domain)504 chain.from_iterable(
417 for dynamic_range in self._dynamic_ranges505 self.get_GENERATE_directives(
418 if dynamic_range.version == 4506 dynamic_range,
419 ))507 self.domain,
420 self.write_zone_file(508 zi)
421 self.target_path, self.make_parameters(),509 for dynamic_range in self._dynamic_ranges
422 {510 if dynamic_range.version == 4
423 'mappings': {511 ))
424 'PTR': self.get_PTR_mapping(512 self.write_zone_file(
425 self._mapping, self.domain, self._network),513 zi.target_path, self.make_parameters(),
426 },514 {
427 'generate_directives': {515 'mappings': {
428 'PTR': generate_directives,516 'PTR': self.get_PTR_mapping(
517 self._mapping, self.domain, zi.subnetwork),
518 },
519 'generate_directives': {
520 'PTR': generate_directives,
521 }
429 }522 }
430 }523 )
431 )

Subscribers

People subscribed via source and target branches