← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/maas-dns3 into lp:maas

 

Raphaël Badin has proposed merging lp:~rvb/maas/maas-dns3 into lp:maas with lp:~rvb/maas/maas-dns-utils as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/maas-dns3/+merge/114832

WIP
-- 
https://code.launchpad.net/~rvb/maas/maas-dns3/+merge/114832
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/maas-dns3 into lp:maas.
=== modified file 'etc/celeryconfig.py'
--- etc/celeryconfig.py	2012-07-11 09:06:57 +0000
+++ etc/celeryconfig.py	2012-07-13 10:59:19 +0000
@@ -32,6 +32,10 @@
 # Location of MAAS' bind configuration files.
 DNS_CONFIG_DIR = '/var/cache/bind/maas'
 
+# RNDC port to be configured by MAAS to communicate with the BIND
+# server.
+DNS_RNDC_PORT = 954
+
 # DHCP leases file, as maintained by ISC dhcpd.
 DHCP_LEASES_FILE = '/var/lib/dhcp/dhcpd.leases'
 

=== modified file 'etc/democeleryconfig.py'
--- etc/democeleryconfig.py	2012-07-12 15:18:10 +0000
+++ etc/democeleryconfig.py	2012-07-13 10:59:19 +0000
@@ -26,3 +26,7 @@
 
 DNS_CONFIG_DIR = os.path.join(
     DEV_ROOT_DIRECTORY, 'run/named/')
+
+
+DNS_RNDC_PORT = 9154
+

=== modified file 'src/maasserver/dns.py'
--- src/maasserver/dns.py	2012-06-29 16:00:51 +0000
+++ src/maasserver/dns.py	2012-07-13 10:59:19 +0000
@@ -11,14 +11,22 @@
 
 __metaclass__ = type
 __all__ = [
+    'add_zone',
+    'change_dns_zone',
     'next_zone_serial',
+    'write_full_dns_config',
     ]
 
 
+from maasserver.models import (
+    DHCPLease,
+    NodeGroup,
+    )
 from maasserver.sequence import (
     INT_MAX,
     Sequence,
     )
+from provisioningserver import tasks
 
 # A DNS zone's serial is a 32-bit integer.  Also, we start with the
 # value 1 because 0 has special meaning for some DNS servers.  Even if
@@ -29,3 +37,45 @@
 
 def next_zone_serial():
     return '%0.10d' % zone_serial.nextval()
+
+
+def change_dns_zone(nodegroup):
+    mapping = DHCPLease.objects.get_hostname_ip_mapping(nodegroup)
+    zone_name = nodegroup.name
+    tasks.write_dns_zone_config.delay(
+        zone_name=zone_name, domain=zone_name,
+        serial=next_zone_serial(), hostname_ip_mapping=mapping,
+        callback=tasks.rndc_command.subtask(args=['reload', zone_name]))
+
+
+def add_zone(nodegroup):
+    zone_names = [
+        result[0]
+        for result in NodeGroup.objects.all().values_list('name')]
+    tasks.write_dns_config(zone_names=zone_names)
+    mapping = DHCPLease.objects.get_hostname_ip_mapping(nodegroup)
+    zone_name = nodegroup.name
+    tasks.write_dns_zone_config.delay(
+        zone_name=zone_name, domain=zone_name,
+        serial=next_zone_serial(), hostname_ip_mapping=mapping,
+        callback=tasks.write_dns_config.subtask(
+            zone_names=zone_names,
+            callback=tasks.rndc_command.subtask(args=['reconfig'])))
+
+
+def write_full_dns_config():
+    groups = NodeGroup.objects.all()
+    serial = next_zone_serial()
+    zones = {
+        group.name: {
+            'serial': serial,
+            'zone_name': group.name,
+            'domain': group.name,
+            'hostname_ip_mapping': (
+                DHCPLease.objects.get_hostname_ip_mapping(
+                    group))
+            }
+        for group in groups
+        }
+    tasks.write_full_dns_config(
+        zones,  callback=tasks.rndc_command.subtask(args=['reload']))

=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py	2012-07-10 15:17:21 +0000
+++ src/maasserver/testing/factory.py	2012-07-13 10:59:19 +0000
@@ -90,7 +90,7 @@
                   created=None, **kwargs):
         # hostname=None is a valid value, hence the set_hostname trick.
         if hostname is '' and set_hostname:
-            hostname = self.getRandomString(255)
+            hostname = self.getRandomString(20)
         if status is None:
             status = NODE_STATUS.DEFAULT_STATUS
         node = Node(

=== modified file 'src/maasserver/tests/test_dns.py'
--- src/maasserver/tests/test_dns.py	2012-06-29 16:00:51 +0000
+++ src/maasserver/tests/test_dns.py	2012-07-13 10:59:19 +0000
@@ -12,11 +12,26 @@
 __metaclass__ = type
 __all__ = []
 
+
+from django.core.management import call_command
 from maasserver.dns import (
+    add_zone,
+    change_dns_zone,
     next_zone_serial,
+    write_full_dns_config,
     zone_serial,
     )
+from maasserver.testing.factory import factory
 from maasserver.testing.testcase import TestCase
+from maastesting.bindfixture import BINDServer
+from maastesting.celery import CeleryFixture
+from maastesting.tests.test_bindfixture import dig_call
+from provisioningserver.dns.config import conf
+from provisioningserver.tasks import (
+    setup_rndc_configuration,
+    write_dns_config,
+    )
+from testresources import FixtureResource
 from testtools.matchers import MatchesStructure
 
 
@@ -33,6 +48,66 @@
             )
 
     def test_next_zone_serial_returns_sequence(self):
+        initial = int(next_zone_serial())
         self.assertSequenceEqual(
-            ['%0.10d' % i for i in range(1, 11)],
-            [next_zone_serial() for i in range(10)])
+            ['%0.10d' % i for i in range(initial + 1, initial + 11)],
+            [next_zone_serial() for i in range(initial, initial + 10)])
+
+
+class TestDNSConfigModifications(TestCase):
+
+    resources = (
+        ("celery", FixtureResource(CeleryFixture())),
+        )
+
+    def setUp(self):
+        super(TestDNSConfigModifications, self).setUp()
+        self.bind = self.useFixture(BINDServer())
+        self.patch(conf, 'DNS_CONFIG_DIR', self.bind.config.homedir)
+
+        call_command(
+            'get_named_conf', edit=True,
+            config_path=self.bind.config.conf_file)
+        setup_rndc_configuration()
+        write_dns_config(inactive=True)
+        self.bind.runner.rndc('reload')
+
+    def create_nodegroup_with_lease(self, nodegroup=None):
+        if nodegroup is None:
+            nodegroup = factory.make_node_group()
+        node = factory.make_node(
+            nodegroup=nodegroup, set_hostname=True)
+        mac = factory.make_mac_address(node=node)
+        lease = factory.make_dhcp_lease(
+            nodegroup=nodegroup, mac=mac.mac_address)
+        return nodegroup, node, lease
+
+    def dig_resolve(self, fqdn):
+        return dig_call(
+            port=self.bind.config.port,
+            commands=[fqdn, '+short'])
+
+    def test_add_zone_loads_dns_zone(self):
+        nodegroup, node, lease = self.create_nodegroup_with_lease()
+        add_zone(nodegroup)
+
+        fqdn = "%s.%s" % (node.hostname, nodegroup.name)
+        self.assertEqual(lease.ip, self.dig_resolve(fqdn))
+
+    def test_change_zone_changes_dns_zone(self):
+        nodegroup, _, _ = self.create_nodegroup_with_lease()
+        write_full_dns_config()
+        nodegroup, new_node, new_lease = (
+            self.create_nodegroup_with_lease(nodegroup=nodegroup))
+        change_dns_zone(nodegroup)
+
+        fqdn = "%s.%s" % (new_node.hostname, nodegroup.name)
+        self.assertEqual(new_lease.ip, self.dig_resolve(fqdn))
+
+    def test_write_full_dns_loads_full_dns_config(self):
+        nodegroup, node, lease = self.create_nodegroup_with_lease()
+
+        write_full_dns_config()
+
+        fqdn = "%s.%s" % (node.hostname, nodegroup.name)
+        self.assertEqual(lease.ip, self.dig_resolve(fqdn))

=== modified file 'src/maastesting/tests/test_bindfixture.py'
--- src/maastesting/tests/test_bindfixture.py	2012-07-09 14:01:13 +0000
+++ src/maastesting/tests/test_bindfixture.py	2012-07-13 10:59:19 +0000
@@ -32,7 +32,7 @@
 from testtools.testcase import gather_details
 
 
-def dig_call(port=53, server='127.0.0.1', command=''):
+def dig_call(port=53, server='127.0.0.1', commands=None):
     """Call `dig` with the given command.
 
     Note that calling dig without a command will perform an NS
@@ -42,16 +42,19 @@
     :param port: Port of the queried DNS server (defaults to 53).
     :param server: IP address of the queried DNS server (defaults
         to '127.0.0.1').
-    :param command: Dig command to run (defaults to '').
+    :param commands: List of dig commands to run (defaults to None
+        which will perform an NS query for "." (the root)).
     :return: The output as a string.
     :rtype: basestring
     """
     cmd = [
         'dig', '+time=1', '+tries=1', '@%s' % server, '-p',
         '%d' % port]
-    if command != '':
-        cmd.append(command)
-    return check_output(cmd)
+    if commands != None:
+        if not isinstance(commands, list):
+            commands = (commands, )
+        cmd.extend(commands)
+    return check_output(cmd).strip()
 
 
 class TestBINDFixture(TestCase):

=== modified file 'src/provisioningserver/dns/config.py'
--- src/provisioningserver/dns/config.py	2012-07-11 15:14:48 +0000
+++ src/provisioningserver/dns/config.py	2012-07-13 10:59:19 +0000
@@ -13,7 +13,6 @@
 __all__ = [
     'DNSConfig',
     'DNSZoneConfig',
-    'InactiveDNSConfig',
     'setup_rndc',
     ]
 
@@ -83,7 +82,8 @@
     MAAS_RNDC_CONF_NAME and MAAS_NAMED_RNDC_CONF_NAME, both stored in
     conf.DNS_CONFIG_DIR.
     """
-    rndc_content, named_content = generate_rndc()
+    rndc_content, named_content = generate_rndc(
+        conf.DNS_RNDC_PORT)
 
     target_file = get_rndc_conf_path()
     with open(target_file, "wb") as f:
@@ -98,7 +98,10 @@
     """Execute a rndc command."""
     rndc_conf = os.path.join(
         conf.DNS_CONFIG_DIR, MAAS_RNDC_CONF_NAME)
-    check_call(['rndc', '-c', rndc_conf] + map(str, arguments))
+    with open(os.devnull, "ab") as devnull:
+        check_call(
+        ['rndc', '-c', rndc_conf] + map(str, arguments),
+        stdout=devnull)
 
 
 # Directory where the DNS configuration template files can be found.
@@ -184,16 +187,6 @@
         return '\ninclude "%s";\n' % self.target_path
 
 
-class InactiveDNSConfig(DNSConfig):
-    """A specialized version of DNSConfig that simply writes a blank/empty
-    configuration file.
-    """
-
-    def get_template(self):
-        """Return an empty template."""
-        return tempita.Template('', 'empty template')
-
-
 class DNSZoneConfig(DNSConfig):
     """A specialized version of DNSConfig that writes zone files."""
 

=== modified file 'src/provisioningserver/dns/templates/zone.template'
--- src/provisioningserver/dns/templates/zone.template	2012-07-11 11:13:24 +0000
+++ src/provisioningserver/dns/templates/zone.template	2012-07-13 10:59:19 +0000
@@ -8,6 +8,7 @@
               )
 
     IN  NS  {{domain}}.
-{{for host in hosts}}
-{{host['hostname']}} IN A {{host['ip']}}
+{{domain}}.  IN A 127.0.0.1; # TODO: use master worker IP.
+{{for hostname, ip in hostname_ip_mapping.items()}}
+{{hostname}} IN A {{ip}}
 {{endfor}}

=== modified file 'src/provisioningserver/dns/tests/test_config.py'
--- src/provisioningserver/dns/tests/test_config.py	2012-07-11 15:14:48 +0000
+++ src/provisioningserver/dns/tests/test_config.py	2012-07-13 10:59:19 +0000
@@ -30,7 +30,6 @@
     DNSZoneConfig,
     execute_rndc_command,
     generate_rndc,
-    InactiveDNSConfig,
     MAAS_NAMED_CONF_NAME,
     MAAS_NAMED_RNDC_CONF_NAME,
     MAAS_RNDC_CONF_NAME,
@@ -147,19 +146,6 @@
                 Contains('include "%s"' % dnsconfig.target_path)))
 
 
-class TestInactiveDNSConfig(TestCase):
-    """Tests for InactiveDNSConfig."""
-
-    def test_write_config_writes_empty_config(self):
-        target_dir = self.make_dir()
-        self.patch(InactiveDNSConfig, 'target_dir', target_dir)
-        dnsconfig = InactiveDNSConfig()
-        dnsconfig.write_config()
-        self.assertThat(
-            os.path.join(target_dir, MAAS_NAMED_CONF_NAME),
-            FileContains(''))
-
-
 class TestDNSZoneConfig(TestCase):
     """Tests for DNSZoneConfig."""
 
@@ -185,17 +171,16 @@
         dnszoneconfig = DNSZoneConfig(zone_name)
         domain = factory.getRandomString()
         serial = random.randint(1, 100)
-        hosts = [{
-            'ip': factory.getRandomIPAddress(),
-            'hostname': factory.getRandomString()}]
+        hostname = factory.getRandomString()
+        ip = factory.getRandomIPAddress()
         dnszoneconfig.write_config(
-            domain=domain, serial=serial, hosts=hosts)
+            domain=domain, serial=serial,
+            hostname_ip_mapping={hostname: ip})
         self.assertThat(
             os.path.join(target_dir, 'zone.%s' % zone_name),
             FileContains(
                 matcher=ContainsAll(
                     [
                         'IN  NS  %s.' % domain,
-                        '%s IN A %s' % (
-                            hosts[0]['hostname'], hosts[0]['ip']),
+                        '%s IN A %s' % (hostname, ip),
                     ])))

=== modified file 'src/provisioningserver/tasks.py'
--- src/provisioningserver/tasks.py	2012-07-11 11:25:25 +0000
+++ src/provisioningserver/tasks.py	2012-07-13 10:59:19 +0000
@@ -13,22 +13,19 @@
 __all__ = [
     'power_off',
     'power_on',
-    'reload_dns_config',
-    'reload_zone_config',
+    'rndc_command',
+    'setup_rndc_configuration',
     'write_dns_config',
     'write_dns_zone_config',
     'write_full_dns_config',
-    'setup_rndc_configuration',
     ]
 
 
 from celery.task import task
-from celery.task.sets import subtask
 from provisioningserver.dns.config import (
     DNSConfig,
     DNSZoneConfig,
     execute_rndc_command,
-    InactiveDNSConfig,
     setup_rndc,
     )
 from provisioningserver.power.poweraction import (
@@ -97,20 +94,14 @@
 
 
 @task
-def reload_dns_config():
-    """Use rndc to reload the DNS configuration."""
-    execute_rndc_command('reload')
-
-
-@task
-def reload_zone_config(zone_name):
-    """Use rndc to reload the DNS configuration for a zone."""
-    execute_rndc_command('reload', zone_name)
+def rndc_command(*arguments):
+    """Use rndc to execute a command."""
+    execute_rndc_command(*arguments)
 
 
 @task
 def write_full_dns_config(zones=None, reverse_zones=None,
-                          **kwargs):
+                          callback=None, **kwargs):
     """Write out the DNS configuration files: the main configuration
     file and the zone files.
     :param zones: Mapping between zone names and the zone data used
@@ -120,6 +111,8 @@
         reverse zone data used to write the reverse zone config
         files.
     :type reverse_zones: dict
+    :param callback: Callback subtask.
+    :type callback: callable
     :param **kwargs: Keyword args passed to DNSConfig.write_config()
     """
     if zones is None:
@@ -137,52 +130,57 @@
         zone_names=list(zones),
         reverse_zone_names=list(reverse_zones))
     config.write_config(**kwargs)
-    subtask(reload_dns_config.subtask()).delay()
+    if callback is not None:
+        callback.delay()
 
 
 @task
-def write_dns_config(inactive=False, zone_names=(),
-                     reverse_zone_names=(), **kwargs):
+def write_dns_config(zone_names=(), reverse_zone_names=(),
+                     callback=None, **kwargs):
     """Write out the DNS configuration file.
 
-    :param inactive: Whether or not an inactive (i.e. blank)
-        configuration should be written. False by default.
-    :type inactive: boolean
     :param zone_names: List of zone names to include as part of the
         main config.
     :type zone_names: list
     :param reverse_zone_names: List of reverse zone names to include as part of
         the main config.
     :type reverse_zone_names: list
+    :param callback: Callback subtask.
+    :type callback: callable
     :param **kwargs: Keyword args passed to DNSConfig.write_config()
     """
-    if inactive:
-        InactiveDNSConfig().write_config()
-    else:
-        config = DNSConfig(
-            zone_names=zone_names,
-            reverse_zone_names=reverse_zone_names)
-        config.write_config(**kwargs)
-    subtask(reload_dns_config.subtask()).delay()
+    config = DNSConfig(
+        zone_names=zone_names,
+        reverse_zone_names=reverse_zone_names)
+    config.write_config(**kwargs)
+    if callback is not None:
+        callback.delay()
 
 
 @task
-def write_dns_zone_config(zone_name, **kwargs):
+def write_dns_zone_config(zone_name, callback=None, **kwargs):
     """Write out a DNS zone configuration file.
 
     :param zone_name: The identifier of the zone to write the configuration
         for.
     :type zone_name: basestring
+    :param callback: Callback subtask.
+    :type callback: callable
     :param **kwargs: Keyword args passed to DNSZoneConfig.write_config()
     """
     DNSZoneConfig(zone_name).write_config(**kwargs)
-    subtask(reload_zone_config.subtask(args=[zone_name])).delay()
+    if callback is not None:
+        callback.delay()
 
 
 @task
-def setup_rndc_configuration():
+def setup_rndc_configuration(callback=None):
     """Write out the two rndc configuration files (rndc.conf and
     named.conf.rndc).
+
+    :param callback: Callback subtask.
+    :type callback: callable
     """
     setup_rndc()
-    subtask(reload_dns_config.subtask()).delay()
+    if callback is not None:
+        callback.delay()

=== modified file 'src/provisioningserver/tests/test_tasks.py'
--- src/provisioningserver/tests/test_tasks.py	2012-07-11 15:25:05 +0000
+++ src/provisioningserver/tests/test_tasks.py	2012-07-13 10:59:19 +0000
@@ -33,8 +33,7 @@
 from provisioningserver.tasks import (
     power_off,
     power_on,
-    reload_dns_config,
-    reload_zone_config,
+    rndc_command,
     setup_rndc_configuration,
     write_dns_config,
     write_dns_zone_config,
@@ -130,7 +129,10 @@
 
     def test_write_dns_config_writes_file(self):
         zone_names = [random.randint(1, 100), random.randint(1, 100)]
-        result = write_dns_config.delay(inactive=False, zone_names=zone_names)
+        command = factory.getRandomString()
+        result = write_dns_config.delay(
+            zone_names=zone_names,
+            callback=rndc_command.subtask(args=[command]))
 
         self.assertThat(
             (
@@ -142,32 +144,17 @@
                 (
                     Equals(True),
                     FileExists(),
-                    Equals([(('reload',), {})]),
-                )),
-            result)
-
-    def test_write_dns_config_with_inactive_True(self):
-        result = write_dns_config.delay(inactive=True)
-
-        self.assertThat(
-            (
-                result.successful(),
-                os.path.join(self.dns_conf_dir, MAAS_NAMED_CONF_NAME),
-                self.rndc_recorder.calls,
-            ),
-            MatchesListwise(
-                (
-                    Equals(True),
-                    FileContains(''),
-                    Equals([(('reload',), {})]),
+                    Equals([((command,), {})]),
                 )),
             result)
 
     def test_write_dns_zone_config_writes_file(self):
+        command = factory.getRandomString()
         zone_name = factory.getRandomString()
         result = write_dns_zone_config.delay(
             zone_name=zone_name, domain=factory.getRandomString(),
-            serial=random.randint(1, 100), hosts=[])
+            serial=random.randint(1, 100), hostname_ip_mapping={},
+            callback=rndc_command.subtask(args=[command]))
 
         self.assertThat(
             (
@@ -179,12 +166,14 @@
                 (
                     Equals(True),
                     FileExists(),
-                    Equals([(('reload', zone_name), {})]),
+                    Equals([((command, ), {})]),
                 )),
             result)
 
     def test_setup_rndc_configuration_writes_files(self):
-        result = setup_rndc_configuration.delay()
+        command = factory.getRandomString()
+        result = setup_rndc_configuration.delay(
+            callback=rndc_command.subtask(args=[command]))
 
         self.assertThat(
             (
@@ -199,31 +188,20 @@
                     Equals(True),
                     FileExists(),
                     FileExists(),
-                    Equals([(('reload',), {})]),
+                    Equals([((command,), {})]),
                 )),
             result)
 
-    def test_reload_dns_config_issues_reload_command(self):
-        result = reload_dns_config.delay()
-
-        self.assertThat(
-            (result.successful(), self.rndc_recorder.calls),
-            MatchesListwise(
-                (
-                    Equals(True),
-                    Equals([(('reload',), {})]),
-                )))
-
-    def test_reload_zone_config_issues_zone_reload_command(self):
-        zone_name = factory.getRandomString()
-        result = reload_zone_config.delay(zone_name)
-
-        self.assertThat(
-            (result.successful(), self.rndc_recorder.calls),
-            MatchesListwise(
-                (
-                    Equals(True),
-                    Equals([(('reload', zone_name), {})]),
+    def test_rndc_command_execute_command(self):
+        command = factory.getRandomString()
+        result = rndc_command.delay(command)
+
+        self.assertThat(
+            (result.successful(), self.rndc_recorder.calls),
+            MatchesListwise(
+                (
+                    Equals(True),
+                    Equals([((command,), {})]),
                 )))
 
     def test_write_full_dns_config_sets_up_config(self):
@@ -236,11 +214,14 @@
         zones = {
             zone_name: {
                 'serial': random.randint(1, 100),
-                'hosts': [{'hostname': hostname, 'ip': ip}],
+                'hostname_ip_mapping': {hostname: ip},
                 'domain': domain,
             }
         }
-        result = write_full_dns_config.delay(zones=zones)
+        command = factory.getRandomString()
+        result = write_full_dns_config.delay(
+            zones=zones,
+            callback=rndc_command.subtask(args=[command]))
 
         self.assertThat(
             (
@@ -254,7 +235,7 @@
             MatchesListwise(
                 (
                     Equals(True),
-                    Equals([(('reload',), {})]),
+                    Equals([((command,), {})]),
                     FileExists(),
                     FileExists(),
                 )))


Follow ups