Alex Monk has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/304146

Change subject: [WIP] dnsrecursor: Rewrite code setting up lua hooks
......................................................................

[WIP] dnsrecursor: Rewrite code setting up lua hooks

TODO: Basic idea has been tested, but this puppetisation needs testig

To:
* Not use separate files for our labs hooks, as PowerDNS only reads one file
  - preventing a rather nasty gotcha where one lua hook file would define a
  function and then get it overridden by the next.
* Handle NXDOMAINs/SOAs for metal names properly.
* Have all these files in the role module for labs dnsrecursors, rather than
  the dnsrecursor module itself.

Bug: T139438
Change-Id: I1e4fae6ab33da9b229bc27868136783b8aedc010
---
R hieradata/common/role/labs/dnsrecursor/lua_hooks.yaml
D modules/dnsrecursor/files/labs-ip-alias-dump.py
M modules/dnsrecursor/manifests/init.pp
D modules/dnsrecursor/manifests/labsaliaser.pp
D modules/dnsrecursor/manifests/metalresolver.pp
D modules/dnsrecursor/templates/metaldns.lua.erb
D modules/dnsrecursor/templates/recursorhooks.lua.erb
A modules/role/files/labs/dnsrecursor-hooks-builder.py
M modules/role/manifests/labs/dnsrecursor.pp
9 files changed, 200 insertions(+), 278 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/puppet 
refs/changes/46/304146/1

diff --git a/hieradata/common/dnsrecursor/labsaliaser.yaml 
b/hieradata/common/role/labs/dnsrecursor/lua_hooks.yaml
similarity index 100%
rename from hieradata/common/dnsrecursor/labsaliaser.yaml
rename to hieradata/common/role/labs/dnsrecursor/lua_hooks.yaml
diff --git a/modules/dnsrecursor/files/labs-ip-alias-dump.py 
b/modules/dnsrecursor/files/labs-ip-alias-dump.py
deleted file mode 100644
index 3f5a8f2..0000000
--- a/modules/dnsrecursor/files/labs-ip-alias-dump.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/python
-import os
-import sys
-import yaml
-import argparse
-import itertools
-
-from keystoneclient.session import Session as KeystoneSession
-from keystoneclient.auth.identity.v2 import Password as KeystonePassword
-from keystoneclient.client import Client as KeystoneClient
-
-from novaclient import client as novaclient
-
-argparser = argparse.ArgumentParser()
-argparser.add_argument(
-    '--config-file',
-    help='Path to config file',
-    default='/etc/labs-dns-alias.yaml',
-    type=argparse.FileType('r')
-)
-argparser.add_argument(
-    '--check-changes-only',
-    help='Exit with 0 if there are no changes and 1 if there are changes. Do 
not write to file',
-    action='store_true'
-)
-
-LUA_LINE_TEMPLATE = '{table}["{key}"] = "{value}" -- {comment}\n'
-
-args = argparser.parse_args()
-config = yaml.safe_load(args.config_file)
-
-auth = KeystonePassword(
-    auth_url=config['nova_api_url'],
-    username=config['username'],
-    password=config['password'],
-    tenant_name=config['admin_project_name']
-)
-keystoneClient = KeystoneClient(
-    session=KeystoneSession(auth=auth), endpoint=config['nova_api_url'])
-
-projects = []
-for tenant in keystoneClient.tenants.list():
-    projects.append(tenant.name)
-
-aliases = {}
-for project in projects:
-    client = novaclient.Client(
-        "1.1",
-        config['username'],
-        config['password'],
-        project,
-        config['nova_api_url']
-    )
-
-    for server in client.servers.list():
-        serverAddresses = {}
-        try:
-            private = [
-                str(ip['addr']) for ip in server.addresses['public']
-                if ip['OS-EXT-IPS:type'] == 'fixed'
-            ]
-            public = [
-                str(ip['addr']) for ip in server.addresses['public']
-                if ip['OS-EXT-IPS:type'] == 'floating'
-            ]
-            if public:
-                # Match all possible public IPs to all possible private ones
-                # Technically there can be more than one floating IP and more 
than one private IP
-                # Although this is never practically the case...
-                aliases[server.name] = list(itertools.product(public, private))
-        except KeyError:
-            # This can happen if a server doesn't (yet) have any addresses, 
while it's being
-            # constructed.  In which case we simply harmlessly ignore it.
-            pass
-
-output = 'aliasmapping = {}\n'
-# Sort to prevent flapping around due to random ordering
-for name in sorted(aliases.keys()):
-    ips = aliases[name]
-    for public, private in ips:
-        output += LUA_LINE_TEMPLATE.format(
-            table='aliasmapping',
-            key=public,
-            value=private,
-            comment=name
-        )
-
-output += """
-function postresolve (remoteip, domain, qtype, records, origrcode)
-    for key,val in ipairs(records)
-    do
-        if (aliasmapping[val.content] and val.qtype == pdns.A) then
-            val.content = aliasmapping[val.content]
-            setvariable()
-        end
-    end
-    return origrcode, records
-end
-
-"""
-
-if 'extra_records' in config:
-    output += 'extra_records = {}\n'
-    extra_records = config['extra_records']
-
-    for q in sorted(extra_records.keys()):
-        output += LUA_LINE_TEMPLATE.format(
-            table='extra_records',
-            key=q,
-            value=extra_records[q],
-            comment=q
-        )
-
-    output += """
-function preresolve(remoteip, domain, qtype)
-    if extra_records[domain]
-    then
-        return 0, {
-            {qtype=pdns.A, content=extra_records[domain], ttl=300, place="1"},
-        }
-    end
-    return -1, {}
-end
-"""
-
-if os.path.exists(config['output_path']):
-    with open(config['output_path']) as f:
-        current_contents = f.read()
-else:
-    current_contents = ""
-
-if output == current_contents:
-    # Do nothing!
-    if args.check_changes_only:
-        sys.exit(0)
-else:
-    if args.check_changes_only:
-        sys.exit(1)
-    with open(config['output_path'], 'w') as f:
-        f.write(output)
diff --git a/modules/dnsrecursor/manifests/init.pp 
b/modules/dnsrecursor/manifests/init.pp
index 43ef452..23ee7ae 100644
--- a/modules/dnsrecursor/manifests/init.pp
+++ b/modules/dnsrecursor/manifests/init.pp
@@ -5,6 +5,9 @@
 #
 # [*allow_from]
 #  Prefixes from which to allow recursive DNS queries
+#
+# [*lua_hooks]
+#  Boolean. If true, will load lua_hooks found at 
/etc/powerdns/recursorhooks.lua
 
 class dnsrecursor(
     $listen_addresses         = [$::ipaddress],
@@ -41,18 +44,6 @@
         mode    => '0444',
         notify  => Service['pdns-recursor'],
         content => template('dnsrecursor/recursor.conf.erb'),
-    }
-
-    if $lua_hooks {
-        file { '/etc/powerdns/recursorhooks.lua':
-            ensure  => 'present',
-            require => Package['pdns-recursor'],
-            owner   => 'root',
-            group   => 'root',
-            mode    => '0444',
-            notify  => Service['pdns-recursor'],
-            content => template('dnsrecursor/recursorhooks.lua.erb'),
-        }
     }
 
     service { 'pdns-recursor':
diff --git a/modules/dnsrecursor/manifests/labsaliaser.pp 
b/modules/dnsrecursor/manifests/labsaliaser.pp
deleted file mode 100644
index 24a2d9d..0000000
--- a/modules/dnsrecursor/manifests/labsaliaser.pp
+++ /dev/null
@@ -1,47 +0,0 @@
-class dnsrecursor::labsaliaser(
-    $username,
-    $password,
-    $nova_api_url,
-    $extra_records,
-    $alias_file,
-    $admin_project_name,
-) {
-
-    require_package(['python-novaclient', 'python-keystoneclient'])
-
-    $config = {
-        'username'           => $username,
-        'password'           => $password,
-        'output_path'        => $alias_file,
-        'nova_api_url'       => $nova_api_url,
-        'extra_records'      => $extra_records,
-        'admin_project_name' => $admin_project_name,
-    }
-
-    file { '/etc/labs-dns-alias.yaml':
-        ensure  => present,
-        owner   => 'root',
-        group   => 'root',
-        mode    => '0440',
-        content => ordered_yaml($config),
-    }
-
-    file { '/usr/local/bin/labs-ip-alias-dump.py':
-        ensure => present,
-        owner  => 'root',
-        group  => 'root',
-        mode   => '0550',
-        source => 'puppet:///modules/dnsrecursor/labs-ip-alias-dump.py',
-    }
-
-    exec { '/usr/local/bin/labs-ip-alias-dump.py':
-        user    => 'root',
-        group   => 'root',
-        notify  => Service['pdns-recursor'],
-        require => File[
-            '/usr/local/bin/labs-ip-alias-dump.py',
-            '/etc/labs-dns-alias.yaml'
-        ],
-        unless  => '/usr/local/bin/labs-ip-alias-dump.py --check-changes-only',
-    }
-}
diff --git a/modules/dnsrecursor/manifests/metalresolver.pp 
b/modules/dnsrecursor/manifests/metalresolver.pp
deleted file mode 100644
index 0db5eba..0000000
--- a/modules/dnsrecursor/manifests/metalresolver.pp
+++ /dev/null
@@ -1,16 +0,0 @@
-class dnsrecursor::metalresolver(
-    $metal_resolver,
-    $tld,
-) {
-    $labs_metal = hiera('labs_metal',[])
-
-    file { $metal_resolver:
-        ensure  => present,
-        require => Package['pdns-recursor'],
-        owner   => 'root',
-        group   => 'root',
-        mode    => '0444',
-        notify  => Service['pdns-recursor'],
-        content => template('dnsrecursor/metaldns.lua.erb'),
-    }
-}
diff --git a/modules/dnsrecursor/templates/metaldns.lua.erb 
b/modules/dnsrecursor/templates/metaldns.lua.erb
deleted file mode 100644
index 459c703..0000000
--- a/modules/dnsrecursor/templates/metaldns.lua.erb
+++ /dev/null
@@ -1,34 +0,0 @@
--- This script comes from puppet: 
modules/dnsrecursor/templates/metaldns.lua.erb.
---
--- It inserts a few select entries for labs metal DNS resolution.
---
--- This is handled here rather than in designate because it's easier to 
puppetize
---  this file than to insert things into designate from puppet, and currently
---  puppet/hiera contains the canonical representation of bare metal hosts and 
names.
-
-ARecords = {}
-PTRRecords = {}
-
-<% @labs_metal.sort.map do |k,v| -%>
-ARecords["<%= k %>.<%= @site %>.<%= @tld %>."] = "<%= v['IPv4'] %>"
-ARecords["<%= k %>.<%= v['project'] %>.<%= @site %>.<%= @tld %>."] = "<%= 
v['IPv4'] %>"
-PTRRecords["<%= v['IPv4'].split('.').reverse().join('.') %>.in-addr.arpa."] = 
"<%= k %>.<%= v['project'] %>.<%= @site %>.<%= @tld %>."
-<% end -%>
-
-function nxdomain (remoteip, domain, qtype)
-    if ((qtype == pdns.PTR or qtype == pdns.ANY) and PTRRecords[domain]) then
-        return 0, {{qtype=pdns.PTR, content=(PTRRecords[domain]), ttl=300}}
-    end
-
-    if ((qtype == pdns.A or qtype == pdns.ANY) and ARecords[domain]) then
-        return 0, {{qtype=pdns.A, content=(ARecords[domain]), ttl=300}}
-    end
-
-    -- Prevent NXDOMAIN if the domain exists, we just don't have a record of 
the matching type.
-    if (ARecords[domain] or PTRRecords[domain]) then
-        -- There isn't really a way to provide a proper serial here, so I 
chose '1'.
-        return 0, {{qtype=pdns.SOA, content="labs-ns0.wikimedia.org. 
root.wmflabs.org. 1 3600 600 86400 3600", ttl=60}}
-    end
-
-    return -1, {}
-end
diff --git a/modules/dnsrecursor/templates/recursorhooks.lua.erb 
b/modules/dnsrecursor/templates/recursorhooks.lua.erb
deleted file mode 100644
index fb158f2..0000000
--- a/modules/dnsrecursor/templates/recursorhooks.lua.erb
+++ /dev/null
@@ -1,7 +0,0 @@
--- This file is managed by puppet.
---
--- The Powerdns recursor only supports a single .lua file.  This is that file; 
it includes
---  other files via 'dofile'.
-<% @lua_hooks.sort.map do |hook| -%>
-dofile("<%= hook %>")
-<% end -%>
diff --git a/modules/role/files/labs/dnsrecursor-hooks-builder.py 
b/modules/role/files/labs/dnsrecursor-hooks-builder.py
new file mode 100644
index 0000000..53d11df
--- /dev/null
+++ b/modules/role/files/labs/dnsrecursor-hooks-builder.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python
+import os
+import sys
+import yaml
+import argparse
+import itertools
+
+from keystoneclient.session import Session as KeystoneSession
+from keystoneclient.auth.identity.v2 import Password as KeystonePassword
+from keystoneclient.client import Client as KeystoneClient
+
+from novaclient import client as novaclient
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument(
+    '--config-file',
+    help='Path to config file',
+    default='/etc/labs-dnsrecursor-hooks-builder-config.yaml',
+    type=argparse.FileType('r')
+)
+argparser.add_argument(
+    '--check-changes-only',
+    help='Exit with 0 if there are no changes and 1 if there are changes. Do 
not write to file',
+    action='store_true'
+)
+
+LUA_LINE_TEMPLATE = '{table}["{key}"] = "{value}"\n'
+LUA_LINE_TEMPLATE_COMMENT = '{table}["{key}"] = "{value}" -- {comment}\n'
+INSTANCE_DNS_SUFFIX = '.' + config['site'] + '.' + config['tld'] + '.'
+
+args = argparser.parse_args()
+config = yaml.safe_load(args.config_file)
+
+auth = KeystonePassword(
+    auth_url=config['nova_api_url'],
+    username=config['username'],
+    password=config['password'],
+    tenant_name=config['admin_project_name']
+)
+keystoneClient = KeystoneClient(
+    session=KeystoneSession(auth=auth), endpoint=config['nova_api_url'])
+
+projects = []
+for tenant in keystoneClient.tenants.list():
+    projects.append(tenant.name)
+
+aliases = {}
+for project in projects:
+    client = novaclient.Client(
+        "1.1",
+        config['username'],
+        config['password'],
+        project,
+        config['nova_api_url']
+    )
+
+    for server in client.servers.list():
+        serverAddresses = {}
+        try:
+            private = [
+                str(ip['addr']) for ip in server.addresses['public']
+                if ip['OS-EXT-IPS:type'] == 'fixed'
+            ]
+            public = [
+                str(ip['addr']) for ip in server.addresses['public']
+                if ip['OS-EXT-IPS:type'] == 'floating'
+            ]
+            if public:
+                # Match all possible public IPs to all possible private ones
+                # Technically there can be more than one floating IP and more 
than one private IP
+                # Although this is never practically the case...
+                aliases[server.name] = list(itertools.product(public, private))
+        except KeyError:
+            # This can happen if a server doesn't (yet) have any addresses, 
while it's being
+            # constructed.  In which case we simply harmlessly ignore it.
+            pass
+
+output = """-- THIS FILE IS GENERATED BY dnsrecursor-hooks-builder.py, RUN BY 
PUPPET
+-- Queries hitting this code (i.e., the recursors) are from internal labs 
machines.
+--
+-- The metal entries are handled here rather than in designate because it's 
easier to puppetize
+-- this file than to insert things into designate from puppet, and currently 
puppet/hiera contains
+-- the canonical representation of bare metal hosts and names.
+--
+-- The IP aliasing is handled here because it is dynamic, we need to return 
different records
+-- to the outside world than to internal machines. The problem this solves is 
that labs instances
+-- without public IPs cannot connect to labs public IPs, only private IPs.
+
+IPAliases = {}
+MetalARecords = {}
+MetalPTRRecords = {}
+"""
+# Sort to prevent flapping around due to random ordering
+for name in sorted(aliases.keys()):
+    ips = aliases[name]
+    for public, private in ips:
+        output += LUA_LINE_TEMPLATE_COMMENT.format(
+            table='IPAliases',
+            key=public,
+            value=private,
+            comment=name
+        )
+
+for instance_name in sorted(config['metal']):
+    instance_details = config['metal'][instance_name]
+    IPv4 = instance_details['IPv4']
+    project = instance_details['project']
+    output += LUA_LINE_TEMPLATE.format(
+        table='MetalARecords',
+        key=instance_name + '.' + INSTANCE_DNS_SUFFIX,
+        value=IPv4
+    ) + LUA_LINE_TEMPLATE.format(
+        table='MetalARecords',
+        key=instance_name + '.' + project + INSTANCE_DNS_SUFFIX,
+        value=IPv4
+    ) + LUA_LINE_TEMPLATE.format(
+        table='MetalPTRRecords',
+        key='.'.join(list(reversed(IPv4.split('.')))) + '.in-addr.arpa.',
+        value=instance_name + '.' + project + INSTANCE_DNS_SUFFIX
+    ) + '\n'
+
+output += """
+function postresolve (remoteip, domain, qtype, records, origrcode)
+    if ((qtype == pdns.PTR or qtype == pdns.ANY) and MetalPTRRecords[domain]) 
then
+        return 0, {{qtype=pdns.PTR, content=(MetalPTRRecords[domain]), 
ttl=300}}
+    end
+
+    if ((qtype == pdns.A or qtype == pdns.ANY) and MetalARecords[domain]) then
+        return 0, {{qtype=pdns.A, content=(MetalARecords[domain]), ttl=300}}
+    end
+
+    -- Prevent NXDOMAIN if the domain exists, we just don't have a record of 
the matching type.
+    if (MetalARecords[domain] or MetalPTRRecords[domain]) then
+        return 0, records
+    end
+
+    for key, val in ipairs(records)
+    do
+        if (IPAliases[val.content] and val.qtype == pdns.A) then
+            val.content = IPAliases[val.content]
+            setvariable()
+        end
+    end
+
+    return origrcode, records
+end
+
+"""
+
+if 'extra_records' in config:
+    output += 'extra_records = {}\n'
+    extra_records = config['extra_records']
+
+    for q in sorted(extra_records.keys()):
+        output += LUA_LINE_TEMPLATE.format(
+            table='extra_records',
+            key=q,
+            value=extra_records[q],
+            comment=q
+        )
+
+    output += """
+function preresolve(remoteip, domain, qtype)
+    if extra_records[domain]
+    then
+        return 0, {
+            {qtype=pdns.A, content=extra_records[domain], ttl=300, place="1"},
+        }
+    end
+    return -1, {}
+end
+"""
+
+if os.path.exists(config['output_path']):
+    with open(config['output_path']) as f:
+        current_contents = f.read()
+else:
+    current_contents = ""
+
+if output == current_contents:
+    # Do nothing!
+    if args.check_changes_only:
+        sys.exit(0)
+else:
+    if args.check_changes_only:
+        sys.exit(1)
+    with open(config['output_path'], 'w') as f:
+        f.write(output)
diff --git a/modules/role/manifests/labs/dnsrecursor.pp 
b/modules/role/manifests/labs/dnsrecursor.pp
index af84f4b..f258ca5 100644
--- a/modules/role/manifests/labs/dnsrecursor.pp
+++ b/modules/role/manifests/labs/dnsrecursor.pp
@@ -37,9 +37,6 @@
         address   => $recursor_ip
     }
 
-    #  We need to alias some public IPs to their corresponding private IPs.
-    $wikitech_nova_ldap_user_pass = $keystoneconfig['ldap_user_pass']
-    $wikitech_nova_admin_project_name = $keystoneconfig['admin_project_name']
     $nova_controller_hostname = hiera('labs_nova_controller')
 
     $listen_addresses = $::realm ? {
@@ -48,36 +45,26 @@
     }
 
     $labs_auth_dns = ipresolve($dnsconfig['host'],4)
-
-    $alias_file = '/etc/powerdns/labs-ip-alias.lua'
-    $metal_resolver = '/etc/powerdns/metaldns.lua'
-    $lua_hooks = [$alias_file, $metal_resolver]
-
-    $tld = hiera('labs_tld')
     $private_reverse = hiera('labs_private_ips_reverse_dns')
+
+    # Purge old files
+    # TODO: Remove
+    file { ['/etc/labs-dns-alias.yaml', 
'/usr/local/bin/labs-ip-alias-dump.py']:
+        ensure  => absent
+    }
+
+    include dnsrecursor::lua_hooks
 
     class { '::dnsrecursor':
             listen_addresses         => $listen_addresses,
             allow_from               => $all_networks,
             additional_forward_zones => "${tld}=${labs_auth_dns}, 
${private_reverse}=${labs_auth_dns}",
             auth_zones               => 'labsdb=/var/zones/labsdb',
-            lua_hooks                => $lua_hooks,
+            lua_hooks                => true,
             max_negative_ttl         => 900,
             max_tcp_per_client       => 10,
             max_cache_entries        => 3000000,
             client_tcp_timeout       => 1,
-    }
-
-    class { '::dnsrecursor::labsaliaser':
-        username           => 'novaadmin',
-        password           => $wikitech_nova_ldap_user_pass,
-        nova_api_url       => "http://${nova_controller_hostname}:35357/v2.0";,
-        alias_file         => $alias_file,
-        admin_project_name => $wikitech_nova_admin_project_name
-    }
-    class { '::dnsrecursor::metalresolver':
-        metal_resolver => $metal_resolver,
-        tld            => $tld
     }
 
     # There are three replica servers (c1, c2, c3).  The mapping of

-- 
To view, visit https://gerrit.wikimedia.org/r/304146
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1e4fae6ab33da9b229bc27868136783b8aedc010
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Alex Monk <a...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to