pvl.hosts.zone: stricter name/type + name/CNAME -conflict logic
authorTero Marttila <tero.marttila@aalto.fi>
Wed, 25 Feb 2015 15:37:46 +0200
changeset 472 814cc88c531b
parent 471 e4b4458d8061
child 473 68fd85d850ab
pvl.hosts.zone: stricter name/type + name/CNAME -conflict logic
pvl/hosts/tests.py
pvl/hosts/zone.py
--- a/pvl/hosts/tests.py	Wed Feb 25 15:24:49 2015 +0200
+++ b/pvl/hosts/tests.py	Wed Feb 25 15:37:46 2015 +0200
@@ -255,7 +255,73 @@
         #self.assertZoneEquals(zone.host_forward(h, 'bar.domain'), {
         #    ('test', 'CNAME'): ['host.foo'],
         #})
- 
+
+    def testHostsForward(self):
+        hosts = [
+                host.Host.build('foo', 'domain',
+                    ip      = '192.0.2.1',
+                    ip6     = '2001:db8::192.0.2.1',
+                    alias   = 'test',
+                ),
+                host.Host.build('bar', 'domain',
+                    ip      = '192.0.2.2',
+                )
+        ]
+
+        self.assertZoneEquals(zone.apply_hosts_forward(self.options, hosts, 'domain'), {
+            ('foo', 'A'): ['192.0.2.1'],
+            ('foo', 'AAAA'): ['2001:db8::c000:201'],
+            ('test', 'CNAME'): ['foo'],
+            ('bar', 'A'): ['192.0.2.2'],
+        })
+
+    def testHostsConflict(self):
+        hosts = [
+                host.Host.build('foo', 'domain',
+                    ip      = '192.0.2.1',
+                ),
+                host.Host.build('foo', 'domain',
+                    ip      = '192.0.2.2',
+                )
+        ]
+        
+        with self.assertRaises(zone.HostZoneError):
+            self.assertZoneEquals(zone.apply_hosts_forward(self.options, hosts, 'domain'), { })
+
+    def testHostsAliasConflict(self):
+        hosts = [
+                host.Host.build('foo', 'domain',
+                    ip          = '192.0.2.1',
+                ),
+                host.Host.build('bar', 'domain',
+                    ip          = '192.0.2.2',
+                    alias       = 'foo',
+                )
+        ]
+        
+        # with A first
+        with self.assertRaises(zone.HostZoneError):
+            self.assertZoneEquals(zone.apply_hosts_forward(self.options, hosts, 'domain'), { })
+    
+        # also with CNAME first
+        with self.assertRaises(zone.HostZoneError):
+            self.assertZoneEquals(zone.apply_hosts_forward(self.options, reversed(hosts), 'domain'), { })
+
+    def testHostsAlias4Conflict(self):
+        hosts = [
+                host.Host.build('foo', 'domain',
+                    ip          = '192.0.2.1',
+                ),
+                host.Host.build('bar', 'domain',
+                    ip          = '192.0.2.2',
+                    alias4      = 'foo',
+                )
+        ]
+        
+        with self.assertRaises(zone.HostZoneError):
+            self.assertZoneEquals(zone.apply_hosts_forward(self.options, hosts, 'domain'), { })
+    
+
 class TestReverseZone(TestZoneMixin, unittest.TestCase):
     def setUp(self):
         self.options = Options()
--- a/pvl/hosts/zone.py	Wed Feb 25 15:24:49 2015 +0200
+++ b/pvl/hosts/zone.py	Wed Feb 25 15:37:46 2015 +0200
@@ -144,6 +144,8 @@
     """
         Generate DNS ZoneRecords for for hosts within the given zone origin.
 
+        Verifies that there are no overlapping name/type records, or CNAME records from aliases.
+
         Yields ZoneRecords in Host-order
     """
 
@@ -151,22 +153,22 @@
         yield pvl.dns.ZoneDirective.build(None, 'ORIGIN', origin)
 
     by_name = dict()
-    by_cname = dict()
+    by_name_type = dict()
     
     for host in hosts:
         if not host.domain:
             log.debug("%s: skip without domain", host)
 
         for rr in host_forward(host, origin) :
-            if rr.name in by_cname:
-                raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_cname[rr.name].name, rr=rr))
+            if (rr.name, 'CNAME') in by_name_type:
+                raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_name_type[rr.name, 'CNAME'].name, rr=rr))
             elif rr.type == 'CNAME' and rr.name in by_name:
                 raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=rr.name, rr=by_name[rr.name]))
+            elif (rr.name, rr.type) in by_name_type:
+                raise HostZoneError(host, "{host}: {type} {name} conflict: {rr}".format(host=host, type=rr.type, name=rr.name, rr=by_name_type[rr.name, rr.type]))
             
             by_name[rr.name] = rr
-
-            if rr.type == 'CNAME':
-                by_cname[rr.name] = rr
+            by_name_type[rr.name, rr.type] = rr
             
             # preserve ordering
             yield rr