← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/split-forward-and-reverse-zones into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/split-forward-and-reverse-zones into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~allenap/maas/split-forward-and-reverse-zones/+merge/127124

This splits DNSZoneConfig into two: DNSForwardZoneConfig and DNSReverseZoneConfig. This is step one in preparation for sharing forward zones across networks. This branch will *not* be landed on its own.
-- 
https://code.launchpad.net/~allenap/maas/split-forward-and-reverse-zones/+merge/127124
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/maas/split-forward-and-reverse-zones into lp:maas.
=== modified file 'src/provisioningserver/dns/config.py'
--- src/provisioningserver/dns/config.py	2012-09-03 14:21:02 +0000
+++ src/provisioningserver/dns/config.py	2012-09-29 23:25:24 +0000
@@ -12,7 +12,8 @@
 __metaclass__ = type
 __all__ = [
     'DNSConfig',
-    'DNSZoneConfig',
+    'DNSForwardZoneConfig',
+    'DNSReverseZoneConfig',
     'setup_rndc',
     ]
 
@@ -22,6 +23,10 @@
     abstractproperty,
     )
 from datetime import datetime
+from itertools import (
+    imap,
+    islice,
+    )
 import os.path
 from subprocess import (
     check_call,
@@ -29,7 +34,6 @@
     )
 
 from celery.conf import conf
-from netaddr import IPRange
 from provisioningserver.dns.utils import generated_hostname
 from provisioningserver.utils import (
     atomic_write,
@@ -170,8 +174,9 @@
     template_file_name = 'named.conf.template'
     target_file_name = MAAS_NAMED_CONF_NAME
 
-    def __init__(self, zones=()):
-        self.zones = zones
+    def __init__(self, forward_zones=(), reverse_zones=()):
+        self.forward_zones = forward_zones
+        self.reverse_zones = reverse_zones
         return super(DNSConfig, self).__init__()
 
     @property
@@ -184,7 +189,8 @@
 
     def get_context(self):
         return {
-            'zones': self.zones,
+            'forward_zones': self.forward_zones,
+            'reverse_zones': self.reverse_zones,
             'DNS_CONFIG_DIR': conf.DNS_CONFIG_DIR,
             'named_rndc_conf_path':  get_named_rndc_conf_path(),
             'modified': unicode(datetime.today()),
@@ -201,46 +207,68 @@
 
     >>> shortened_reversed_ip('192.156.0.3', 2)
     '3.0'
+
+    :type ip: :class:`netaddr.IPAddress`
     """
     assert 0 <= byte_num <= 4, ("byte_num should be >=0 and <= 4.")
-    ip_octets = ip.split('.')
-    significant_octets = list(
-    reversed(ip_octets))[:byte_num]
-    return '.'.join(significant_octets)
-
-
-class DNSZoneConfig(DNSConfig):
-    """A specialized version of DNSConfig that writes zone files."""
+    significant_octets = islice(reversed(ip.words), byte_num)
+    return '.'.join(imap(unicode, significant_octets))
+
+
+class DNSZoneConfigBase(DNSConfigBase):
+    """Base class for zone writers."""
 
     template_file_name = 'zone.template'
 
-    def __init__(self, zone_name, serial=None, mapping=None, dns_ip=None,
-                 subnet_mask=None, broadcast_ip=None, ip_range_low=None,
-                 ip_range_high=None):
-        self.zone_name = zone_name
+    def __init__(self, domain, serial=None, mapping=None, dns_ip=None,
+                 network=None):
+        """
+        :param domain: The domain name of the forward zone.
+        :param serial: The serial to use in the zone file. This must increment
+            on each change.
+        :param mapping: A hostname:ip-address mapping for all known hosts in
+            the zone.
+        :param dns_ip: The IP address of the DNS server authoritative for this
+            zone.
+        :param network: The network that the mapping exists within.
+        :type network: :class:`netaddr.IPNetwork`
+        """
+        self.domain = domain
         self.serial = serial
-        if mapping is None:
-            self.mapping = {}
-        else:
-            self.mapping = mapping
+        self.mapping = {} if mapping is None else mapping
         self.dns_ip = dns_ip
-        self.subnet_mask = subnet_mask
-        self.broadcast_ip = broadcast_ip
-        self.ip_range_low = ip_range_low
-        self.ip_range_high = ip_range_high
-
-    @property
-    def byte_num(self):
-        """Number of significant octets for the IPs of this zone."""
-        return 4 - len(
-            [byte for byte in self.subnet_mask.split('.')
-             if byte == '255'])
-
-    @property
-    def reverse_zone_name(self):
-        """Return the name of the reverse zone."""
-        significant_bits = self.broadcast_ip.split('.')[:4 - self.byte_num]
-        return '%s.in-addr.arpa' % '.'.join(reversed(significant_bits))
+        self.network = network
+
+    @abstractproperty
+    def zone_name(self):
+        """Return the zone's fully-qualified name."""
+
+    @property
+    def template_path(self):
+        return os.path.join(self.template_dir, self.template_file_name)
+
+    @property
+    def target_path(self):
+        """Return the full path of the DNS zone file."""
+        return os.path.join(
+            self.target_dir, 'zone.%s' % self.zone_name)
+
+    def write_config(self, **kwargs):
+        """Write out the DNS config file for this zone."""
+        template = self.get_template()
+        kwargs.update(self.get_context())
+        rendered = self.render_template(template, **kwargs)
+        incremental_write(
+            rendered, self.target_path, mode=self.access_permissions)
+
+
+class DNSForwardZoneConfig(DNSZoneConfigBase):
+    """Writes forward zone files."""
+
+    @property
+    def zone_name(self):
+        """Return the name of the forward zone."""
+        return self.domain
 
     def get_mapping(self):
         """Return the mapping: hostname->generated hostname."""
@@ -256,45 +284,8 @@
         and the IP addresses for all the possible IP addresses in zone.
         """
         return {
-            generated_hostname(str(ip)): str(ip)
-            for ip in IPRange(self.ip_range_low, self.ip_range_high)
-        }
-
-    def get_generated_reverse_mapping(self):
-        """Return the reverse generated mapping: (shortened) ip->fqdn.
-
-        The reverse generated mapping is the mapping between the IP addresses
-        and the generated hostnames for all the possible IP addresses in zone.
-        """
-        return dict(
-            (
-                shortened_reversed_ip(ip, self.byte_num),
-                '%s.%s.' % (hostname, self.zone_name)
-            )
-            for hostname, ip in self.get_generated_mapping().items())
-
-    @property
-    def template_path(self):
-        return os.path.join(self.template_dir, self.template_file_name)
-
-    @property
-    def target_path(self):
-        """Return the full path of the DNS zone config file."""
-        return os.path.join(
-            self.target_dir, 'zone.%s' % self.zone_name)
-
-    @property
-    def target_reverse_path(self):
-        """Return the full path of the DNS reverse zone config file."""
-        return os.path.join(
-            self.target_dir, 'zone.rev.%s' % self.reverse_zone_name)
-
-    def get_base_context(self):
-        """Return the dict used to render both zone files."""
-        return {
-            'domain': self.zone_name,
-            'serial': self.serial,
-            'modified': unicode(datetime.today()),
+            generated_hostname(ip): ip
+            for ip in imap(str, self.network)
         }
 
     def get_context(self):
@@ -302,39 +293,53 @@
 
         That context dict is used to render the DNS zone file.
         """
-        context = self.get_base_context()
         mapping = self.get_generated_mapping()
         # Add A record for the name server's IP.
-        mapping['%s.' % self.zone_name] = self.dns_ip
-        mappings = {
-            'CNAME': self.get_mapping(),
-            'A': mapping,
-        }
-        context.update(mappings=mappings)
-        return context
-
-    def get_reverse_context(self):
+        mapping['%s.' % self.domain] = self.dns_ip
+        return {
+            'domain': self.domain,
+            'serial': self.serial,
+            'modified': unicode(datetime.today()),
+            'mappings': {
+                'CNAME': self.get_mapping(),
+                'A': mapping,
+                }
+            }
+
+
+class DNSReverseZoneConfig(DNSZoneConfigBase):
+    """Writes reverse zone files."""
+
+    @property
+    def zone_name(self):
+        """Return the name of the reverse zone."""
+        broadcast, netmask = self.network.broadcast, self.network.netmask
+        octets = broadcast.words[:netmask.words.count(255)]
+        return '%s.in-addr.arpa' % '.'.join(imap(unicode, reversed(octets)))
+
+    def get_generated_mapping(self):
+        """Return the reverse generated mapping: (shortened) ip->fqdn.
+
+        The reverse generated mapping is the mapping between the IP addresses
+        and the generated hostnames for all the possible IP addresses in zone.
+        """
+        byte_num = 4 - self.network.netmask.words.count(255)
+        return {
+            shortened_reversed_ip(ip, byte_num):
+                '%s.%s.' % (generated_hostname(ip), self.domain)
+            for ip in self.network
+            }
+
+    def get_context(self):
         """Return the dict used to render the DNS reverse zone file.
 
         That context dict is used to render the DNS reverse zone file.
         """
-        context = self.get_base_context()
-        mappings = {'PTR': self.get_generated_reverse_mapping()}
-        context.update(mappings=mappings)
-        return context
-
-    def write_config(self, **kwargs):
-        """Write out the DNS config file for this zone."""
-        template = self.get_template()
-        kwargs.update(self.get_context())
-        rendered = self.render_template(template, **kwargs)
-        incremental_write(
-            rendered, self.target_path, mode=self.access_permissions)
-
-    def write_reverse_config(self, **kwargs):
-        """Write out the DNS reverse config file for this zone."""
-        template = self.get_template()
-        kwargs.update(self.get_reverse_context())
-        rendered = self.render_template(template, **kwargs)
-        incremental_write(
-            rendered, self.target_reverse_path, mode=self.access_permissions)
+        return {
+            'domain': self.domain,
+            'serial': self.serial,
+            'modified': unicode(datetime.today()),
+            'mappings': {
+                'PTR': self.get_generated_mapping(),
+                }
+            }

=== modified file 'src/provisioningserver/dns/templates/named.conf.template'
--- src/provisioningserver/dns/templates/named.conf.template	2012-07-25 09:36:03 +0000
+++ src/provisioningserver/dns/templates/named.conf.template	2012-09-29 23:25:24 +0000
@@ -1,7 +1,7 @@
 include "{{named_rndc_conf_path}}";
 
 # Zone declarations.
-{{for zone in zones}}
+{{for zone in forward_zones}}
 zone "{{zone.zone_name}}" {
     type master;
     file "{{zone.target_path}}";
@@ -9,9 +9,9 @@
 {{endfor}}
 
 # Reverse zone declarations.
-{{for zone in zones}}
-zone "{{zone.reverse_zone_name}}" {
+{{for zone in reverse_zones}}
+zone "{{zone.zone_name}}" {
     type master;
-    file "{{zone.target_reverse_path}}";
+    file "{{zone.target_path}}";
 };
 {{endfor}}

=== added file 'src/provisioningserver/dns/tests/__init__.py'
=== modified file 'src/provisioningserver/dns/tests/test_config.py'
--- src/provisioningserver/dns/tests/test_config.py	2012-09-03 15:32:04 +0000
+++ src/provisioningserver/dns/tests/test_config.py	2012-09-29 23:25:24 +0000
@@ -23,12 +23,16 @@
     MatchesAll,
     )
 from maastesting.testcase import TestCase
-from netaddr import IPNetwork
+from netaddr import (
+    IPAddress,
+    IPNetwork,
+    )
 from provisioningserver.dns import config
 from provisioningserver.dns.config import (
     DNSConfig,
     DNSConfigFail,
-    DNSZoneConfig,
+    DNSForwardZoneConfig,
+    DNSReverseZoneConfig,
     execute_rndc_command,
     generate_rndc,
     MAAS_NAMED_CONF_NAME,
@@ -39,7 +43,6 @@
     TEMPLATES_PATH,
     )
 from provisioningserver.dns.utils import generated_hostname
-from provisioningserver.testing import network_infos
 import tempita
 from testtools.matchers import (
     Contains,
@@ -148,21 +151,26 @@
     def test_write_config_writes_config(self):
         target_dir = self.make_dir()
         self.patch(DNSConfig, 'target_dir', target_dir)
-        zone_name = factory.getRandomString()
+        domain = factory.getRandomString()
         network = IPNetwork('192.168.0.3/24')
         ip = factory.getRandomIPInNetwork(network)
-        zone = DNSZoneConfig(
-            zone_name, mapping={factory.getRandomString(): ip},
-            **network_infos(network))
-        dnsconfig = DNSConfig(zones=[zone])
+        forward_zone = DNSForwardZoneConfig(
+            domain, mapping={factory.getRandomString(): ip},
+            network=network)
+        reverse_zone = DNSReverseZoneConfig(
+            domain, mapping={factory.getRandomString(): ip},
+            network=network)
+        dnsconfig = DNSConfig(
+            forward_zones=[forward_zone],
+            reverse_zones=[reverse_zone])
         dnsconfig.write_config()
         self.assertThat(
             os.path.join(target_dir, MAAS_NAMED_CONF_NAME),
             FileContains(
                 matcher=ContainsAll(
                     [
-                        'zone.%s' % zone_name,
-                        'zone.rev.0.168.192.in-addr.arpa',
+                        'zone.%s' % domain,
+                        'zone.0.168.192.in-addr.arpa',
                         MAAS_NAMED_RNDC_CONF_NAME,
                     ])))
 
@@ -192,125 +200,184 @@
     def test_shortened_reversed_ip_2(self):
         self.assertEqual(
             '3.0',
-            shortened_reversed_ip('192.156.0.3', 2))
+            shortened_reversed_ip(IPAddress('192.156.0.3'), 2))
 
     def test_shortened_reversed_ip_0(self):
         self.assertEqual(
             '',
-            shortened_reversed_ip('192.156.0.3', 0))
+            shortened_reversed_ip(IPAddress('192.156.0.3'), 0))
 
     def test_shortened_reversed_ip_4(self):
         self.assertEqual(
             '3.0.156.192',
-            shortened_reversed_ip('192.156.0.3', 4))
-
-
-class TestDNSZoneConfig(TestCase):
-    """Tests for DNSZoneConfig."""
-
-    def test_DNSZoneConfig_fields(self):
-        zone_name = factory.getRandomString()
-        serial = random.randint(1, 200)
-        hostname = factory.getRandomString()
-        network = factory.getRandomNetwork()
-        ip = factory.getRandomIPInNetwork(network)
-        mapping = {hostname: ip}
-        dns_zone_config = DNSZoneConfig(
-            zone_name, serial, mapping, **network_infos(network))
-        self.assertThat(
-            dns_zone_config,
-            MatchesStructure.byEquality(
-                zone_name=zone_name,
-                serial=serial,
-                mapping=mapping,
-                subnet_mask=str(network.netmask),
-                broadcast_ip=str(network.broadcast),
-                ip_range_low=str(network.first),
-                ip_range_high=str(network.last),
-                )
-            )
-
-    def test_DNSZoneConfig_computes_dns_config_file_paths(self):
-        zone_name = factory.make_name('zone')
-        reverse_file_name = 'zone.rev.168.192.in-addr.arpa'
-        dns_zone_config = DNSZoneConfig(
-            zone_name, broadcast_ip='192.168.0.255',
-            subnet_mask='255.255.252.0',
-            )
-        self.assertEqual(
-            (
-                os.path.join(TEMPLATES_PATH, 'zone.template'),
-                os.path.join(conf.DNS_CONFIG_DIR, 'zone.%s' % zone_name),
+            shortened_reversed_ip(IPAddress('192.156.0.3'), 4))
+
+
+class TestDNSForwardZoneConfig(TestCase):
+    """Tests for DNSForwardZoneConfig."""
+
+    def test_DNSForwardZoneConfig_fields(self):
+        domain = factory.getRandomString()
+        serial = random.randint(1, 200)
+        hostname = factory.getRandomString()
+        network = factory.getRandomNetwork()
+        ip = factory.getRandomIPInNetwork(network)
+        mapping = {hostname: ip}
+        dns_zone_config = DNSForwardZoneConfig(
+            domain, serial, mapping, network=network)
+        self.assertThat(
+            dns_zone_config,
+            MatchesStructure.byEquality(
+                domain=domain,
+                serial=serial,
+                mapping=mapping,
+                network=network,
+                )
+            )
+
+    def test_DNSForwardZoneConfig_computes_dns_config_file_paths(self):
+        domain = factory.make_name('zone')
+        dns_zone_config = DNSForwardZoneConfig(domain)
+        self.assertEqual(
+            (
+                os.path.join(TEMPLATES_PATH, 'zone.template'),
+                os.path.join(conf.DNS_CONFIG_DIR, 'zone.%s' % domain),
+            ),
+            (
+                dns_zone_config.template_path,
+                dns_zone_config.target_path,
+            ))
+
+    def test_DNSForwardZoneConfig_get_generated_mapping(self):
+        name = factory.getRandomString()
+        network = IPNetwork('192.12.0.1/30')
+        dns_zone_config = DNSForwardZoneConfig(name, network=network)
+        self.assertEqual(
+            {
+                generated_hostname('192.12.0.0'): '192.12.0.0',
+                generated_hostname('192.12.0.1'): '192.12.0.1',
+                generated_hostname('192.12.0.2'): '192.12.0.2',
+                generated_hostname('192.12.0.3'): '192.12.0.3',
+             },
+            dns_zone_config.get_generated_mapping(),
+            )
+
+    def test_DNSForwardZoneConfig_writes_dns_zone_config(self):
+        target_dir = self.make_dir()
+        self.patch(DNSForwardZoneConfig, 'target_dir', target_dir)
+        domain = factory.getRandomString()
+        hostname = factory.getRandomString()
+        network = factory.getRandomNetwork()
+        ip = factory.getRandomIPInNetwork(network)
+        dns_zone_config = DNSForwardZoneConfig(
+            domain, serial=random.randint(1, 100),
+            mapping={hostname: ip}, network=network)
+        dns_zone_config.write_config()
+        self.assertThat(
+            os.path.join(target_dir, 'zone.%s' % domain),
+            FileContains(
+                matcher=ContainsAll(
+                    [
+                        '%s IN CNAME %s' % (hostname, generated_hostname(ip)),
+                        '%s IN A %s' % (generated_hostname(ip), ip),
+                    ])))
+
+    def test_DNSForwardZoneConfig_writes_dns_zone_config_with_NS_record(self):
+        target_dir = self.make_dir()
+        self.patch(DNSForwardZoneConfig, 'target_dir', target_dir)
+        network = factory.getRandomNetwork()
+        dns_ip = factory.getRandomIPAddress()
+        dns_zone_config = DNSForwardZoneConfig(
+            factory.getRandomString(), serial=random.randint(1, 100),
+            dns_ip=dns_ip, network=network)
+        dns_zone_config.write_config()
+        self.assertThat(
+            os.path.join(target_dir, 'zone.%s' % dns_zone_config.domain),
+            FileContains(
+                matcher=ContainsAll(
+                    [
+                        'IN  NS  %s.' % dns_zone_config.domain,
+                        '%s. IN A %s' % (dns_zone_config.domain, dns_ip),
+                    ])))
+
+    def test_DNSForwardZoneConfig_config_file_is_world_readable(self):
+        self.patch(DNSForwardZoneConfig, 'target_dir', self.make_dir())
+        network = factory.getRandomNetwork()
+        dns_zone_config = DNSForwardZoneConfig(
+            factory.getRandomString(), serial=random.randint(1, 100),
+            dns_ip=factory.getRandomIPAddress(), network=network)
+        dns_zone_config.write_config()
+        filepath = FilePath(dns_zone_config.target_path)
+        self.assertTrue(filepath.getPermissions().other.read)
+
+
+class TestDNSReverseZoneConfig(TestCase):
+    """Tests for DNSReverseZoneConfig."""
+
+    def test_DNSReverseZoneConfig_fields(self):
+        domain = factory.getRandomString()
+        serial = random.randint(1, 200)
+        hostname = factory.getRandomString()
+        network = factory.getRandomNetwork()
+        ip = factory.getRandomIPInNetwork(network)
+        mapping = {hostname: ip}
+        dns_zone_config = DNSReverseZoneConfig(
+            domain, serial, mapping, network=network)
+        self.assertThat(
+            dns_zone_config,
+            MatchesStructure.byEquality(
+                domain=domain,
+                serial=serial,
+                mapping=mapping,
+                network=network,
+                )
+            )
+
+    def test_DNSReverseZoneConfig_computes_dns_config_file_paths(self):
+        domain = factory.make_name('zone')
+        reverse_file_name = 'zone.168.192.in-addr.arpa'
+        dns_zone_config = DNSReverseZoneConfig(
+            domain, network=IPNetwork("192.168.0.0/22"))
+        self.assertEqual(
+            (
+                os.path.join(TEMPLATES_PATH, 'zone.template'),
                 os.path.join(conf.DNS_CONFIG_DIR, reverse_file_name)
             ),
             (
                 dns_zone_config.template_path,
                 dns_zone_config.target_path,
-                dns_zone_config.target_reverse_path,
             ))
 
-    def test_DNSZoneConfig_reverse_data_slash_24(self):
-        # DNSZoneConfig calculates the reverse data correctly for
+    def test_DNSReverseZoneConfig_reverse_data_slash_24(self):
+        # DNSReverseZoneConfig calculates the reverse data correctly for
         # a /24 network.
-        zone_name = factory.make_name('zone')
+        domain = factory.make_name('zone')
         hostname = factory.getRandomString()
         ip = '192.168.0.5'
         network = IPNetwork('192.168.0.1/24')
-        dns_zone_config = DNSZoneConfig(
-            zone_name, mapping={hostname: ip}, **network_infos(network))
+        dns_zone_config = DNSReverseZoneConfig(
+            domain, mapping={hostname: ip}, network=network)
         self.assertEqual(
-            (
-                1,
-                {hostname: generated_hostname(ip)},
-                '0.168.192.in-addr.arpa',
-            ),
-            (
-                dns_zone_config.byte_num,
-                dns_zone_config.get_mapping(),
-                dns_zone_config.reverse_zone_name,
-            ))
+            '0.168.192.in-addr.arpa',
+            dns_zone_config.zone_name)
 
-    def test_DNSZoneConfig_reverse_data_slash_22(self):
-        # DNSZoneConfig calculates the reverse data correctly for
+    def test_DNSReverseZoneConfig_reverse_data_slash_22(self):
+        # DNSReverseZoneConfig calculates the reverse data correctly for
         # a /22 network.
-        zone_name = factory.getRandomString()
+        domain = factory.getRandomString()
         hostname = factory.getRandomString()
         ip = '192.168.0.10'
         network = IPNetwork('192.168.0.1/22')
-        dns_zone_config = DNSZoneConfig(
-            zone_name, mapping={hostname: ip}, **network_infos(network))
-        self.assertEqual(
-            (
-                2,
-                {hostname: generated_hostname(ip)},
-                '168.192.in-addr.arpa',
-            ),
-            (
-                dns_zone_config.byte_num,
-                dns_zone_config.get_mapping(),
-                dns_zone_config.reverse_zone_name,
-            ))
-
-    def test_DNSZoneConfig_get_generated_mapping(self):
-        name = factory.getRandomString()
-        network = IPNetwork('192.12.0.1/30')
-        dns_zone_config = DNSZoneConfig(
-            name, **network_infos(network))
-        self.assertEqual(
-            {
-                generated_hostname('192.12.0.0'): '192.12.0.0',
-                generated_hostname('192.12.0.1'): '192.12.0.1',
-                generated_hostname('192.12.0.2'): '192.12.0.2',
-                generated_hostname('192.12.0.3'): '192.12.0.3',
-             },
-            dns_zone_config.get_generated_mapping(),
-            )
-
-    def test_DNSZoneConfig_get_generated_reverse_mapping(self):
-        name = factory.getRandomString()
-        network = IPNetwork('192.12.0.1/30')
-        dns_zone_config = DNSZoneConfig(
-            name, **network_infos(network))
+        dns_zone_config = DNSReverseZoneConfig(
+            domain, mapping={hostname: ip}, network=network)
+        self.assertEqual(
+            '168.192.in-addr.arpa',
+            dns_zone_config.zone_name)
+
+    def test_DNSReverseZoneConfig_get_generated_mapping(self):
+        name = factory.getRandomString()
+        network = IPNetwork('192.12.0.1/30')
+        dns_zone_config = DNSReverseZoneConfig(name, network=network)
         self.assertEqual(
             {
                 '0': '%s.' % generated_hostname('192.12.0.0', name),
@@ -318,56 +385,33 @@
                 '2': '%s.' % generated_hostname('192.12.0.2', name),
                 '3': '%s.' % generated_hostname('192.12.0.3', name),
              },
-            dns_zone_config.get_generated_reverse_mapping(),
+            dns_zone_config.get_generated_mapping(),
             )
 
-    def test_DNSZoneConfig_writes_dns_zone_config(self):
-        target_dir = self.make_dir()
-        self.patch(DNSConfig, 'target_dir', target_dir)
-        zone_name = factory.getRandomString()
-        hostname = factory.getRandomString()
-        network = factory.getRandomNetwork()
-        ip = factory.getRandomIPInNetwork(network)
-        dns_zone_config = DNSZoneConfig(
-            zone_name, serial=random.randint(1, 100),
-            mapping={hostname: ip}, **network_infos(network))
-        dns_zone_config.write_config()
-        self.assertThat(
-            os.path.join(target_dir, 'zone.%s' % zone_name),
-            FileContains(
-                matcher=ContainsAll(
-                    [
-                        '%s IN CNAME %s' % (hostname, generated_hostname(ip)),
-                        '%s IN A %s' % (generated_hostname(ip), ip),
-                    ])))
-
-    def test_DNSZoneConfig_writes_dns_zone_config_with_NS_record(self):
-        target_dir = self.make_dir()
-        self.patch(DNSConfig, 'target_dir', target_dir)
+    def test_DNSReverseZoneConfig_writes_dns_zone_config_with_NS_record(self):
+        target_dir = self.make_dir()
+        self.patch(DNSReverseZoneConfig, 'target_dir', target_dir)
         network = factory.getRandomNetwork()
         dns_ip = factory.getRandomIPAddress()
-        dns_zone_config = DNSZoneConfig(
+        dns_zone_config = DNSReverseZoneConfig(
             factory.getRandomString(), serial=random.randint(1, 100),
-            dns_ip=dns_ip, **network_infos(network))
+            dns_ip=dns_ip, network=network)
         dns_zone_config.write_config()
         self.assertThat(
-            os.path.join(target_dir, 'zone.%s' % dns_zone_config.zone_name),
+            os.path.join(
+                target_dir, 'zone.%s' % dns_zone_config.zone_name),
             FileContains(
-                matcher=ContainsAll(
-                    [
-                        'IN  NS  %s.' % dns_zone_config.zone_name,
-                        '%s. IN A %s' % (dns_zone_config.zone_name, dns_ip),
-                    ])))
+                matcher=Contains('IN  NS  %s.' % dns_zone_config.domain)))
 
-    def test_DNSZoneConfig_writes_reverse_dns_zone_config(self):
+    def test_DNSReverseZoneConfig_writes_reverse_dns_zone_config(self):
         target_dir = self.make_dir()
-        self.patch(DNSConfig, 'target_dir', target_dir)
-        zone_name = factory.getRandomString()
+        self.patch(DNSReverseZoneConfig, 'target_dir', target_dir)
+        domain = factory.getRandomString()
         network = IPNetwork('192.168.0.1/22')
-        dns_zone_config = DNSZoneConfig(
-            zone_name, serial=random.randint(1, 100), **network_infos(network))
-        dns_zone_config.write_reverse_config()
-        reverse_file_name = 'zone.rev.168.192.in-addr.arpa'
+        dns_zone_config = DNSReverseZoneConfig(
+            domain, serial=random.randint(1, 100), network=network)
+        dns_zone_config.write_config()
+        reverse_file_name = 'zone.168.192.in-addr.arpa'
         self.assertThat(
             os.path.join(target_dir, reverse_file_name),
             FileContains(
@@ -381,22 +425,12 @@
             )
         )
 
-    def test_DNSZoneConfig_config_file_is_world_readable(self):
-        self.patch(DNSConfig, 'target_dir', self.make_dir())
-        dns_zone_config = DNSZoneConfig(
+    def test_DNSReverseZoneConfig_reverse_config_file_is_world_readable(self):
+        self.patch(DNSReverseZoneConfig, 'target_dir', self.make_dir())
+        dns_zone_config = DNSReverseZoneConfig(
             factory.getRandomString(), serial=random.randint(1, 100),
             dns_ip=factory.getRandomIPAddress(),
-            **network_infos(factory.getRandomNetwork()))
+            network=factory.getRandomNetwork())
         dns_zone_config.write_config()
         filepath = FilePath(dns_zone_config.target_path)
         self.assertTrue(filepath.getPermissions().other.read)
-
-    def test_DNSZoneConfig_reverse_config_file_is_world_readable(self):
-        self.patch(DNSConfig, 'target_dir', self.make_dir())
-        dns_zone_config = DNSZoneConfig(
-            factory.getRandomString(), serial=random.randint(1, 100),
-            dns_ip=factory.getRandomIPAddress(),
-            **network_infos(factory.getRandomNetwork()))
-        dns_zone_config.write_reverse_config()
-        filepath = FilePath(dns_zone_config.target_reverse_path)
-        self.assertTrue(filepath.getPermissions().other.read)

=== modified file 'src/provisioningserver/dns/tests/test_utils.py'
--- src/provisioningserver/dns/tests/test_utils.py	2012-07-24 17:47:48 +0000
+++ src/provisioningserver/dns/tests/test_utils.py	2012-09-29 23:25:24 +0000
@@ -15,6 +15,7 @@
 
 from maastesting.factory import factory
 from maastesting.testcase import TestCase
+from netaddr import IPAddress
 from provisioningserver.dns.utils import generated_hostname
 
 
@@ -29,3 +30,7 @@
         self.assertEqual(
             '192-168-0-1.%s' % domain,
             generated_hostname('192.168.0.1', domain))
+
+    def test_generated_hostname_accepts_IPAddress(self):
+        address = IPAddress("12.34.56.78")
+        self.assertEqual("12-34-56-78", generated_hostname(address))

=== modified file 'src/provisioningserver/dns/utils.py'
--- src/provisioningserver/dns/utils.py	2012-07-24 17:47:48 +0000
+++ src/provisioningserver/dns/utils.py	2012-09-29 23:25:24 +0000
@@ -23,7 +23,7 @@
     >>> generated_hostname('192.168.0.1', 'mydomain.com')
     '192-168-0-1.mydomain.com'
     """
-    hostname = ip.replace('.', '-')
+    hostname = unicode(ip).replace('.', '-')
     if domain is not None:
         return '%s.%s' % (hostname, domain)
     else: