Hello,

here is WIP version of generator for IPA DNS records and locations, that is responsible for creating and updating all IPA records for all masters.


Please note that this is not finished yet and some methods may not work.


Patch attached

From 9de5de867a029a02a690718b3fe272702471ca0c Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 8 Jun 2016 17:53:58 +0200
Subject: [PATCH] DNS Location: generator for records

WIP
---
 ipalib/dns.py | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 296 insertions(+)

diff --git a/ipalib/dns.py b/ipalib/dns.py
index 54a4c24a08c974d8e905e4630d91d2381c39778d..d9f3e9e36ff56b65361c9f5a76ce94bef52b4e98 100644
--- a/ipalib/dns.py
+++ b/ipalib/dns.py
@@ -18,9 +18,19 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import
+
 import re
+from collections import defaultdict
+from dns import (
+    rrset,
+    rdataclass,
+    rdatatype,
+)
+from dns.rdtypes.IN.SRV import SRV
 
 from ipalib import errors
+from ipapython.dnsutil import DNSName, resolve_rrsets
 
 # dnsrecord param name formats
 record_name_format = '%srecord'
@@ -98,3 +108,289 @@ def iterate_rrparams_by_parts(cmd, kw, skip_extra=False):
         if rrparam.name not in processed:
             processed.append(rrparam.name)
             yield rrparam
+
+
+class DNSRecordsGenerator(object):
+
+    IPA_DEFAULT_MASTER_SRV_REC = (
+        # srv record name, port
+        (DNSName(u'_ldap._tcp'), 389), (DNSName(u'_kerberos._tcp'), 88),
+        (DNSName(u'_kerberos._udp'), 88),
+        (DNSName(u'_kerberos-master._tcp'), 88),
+        (DNSName(u'_kerberos-master._udp'), 88),
+        (DNSName(u'_kpasswd._tcp'), 464), (DNSName(u'_kpasswd._udp'), 464),
+    )
+
+    IPA_DEFAULT_ADTRUST_SRV_REC = (
+        # srv record name, port
+        (DNSName(u'_ldap._tcp.Default-First-Site-Name._sites.dc._msdcs'), 389),
+        (DNSName(u'_ldap._tcp.dc._msdcs'), 389),
+        (DNSName(u'_kerberos._tcp.Default-First-Site-Name._sites.dc._msdcs'),
+            88),
+        (DNSName(u'_kerberos._udp.Default-First-Site-Name._sites.dc._msdcs'),
+            88),
+        (DNSName(u'_kerberos._tcp.dc._msdcs'), 88),
+        (DNSName(u'_kerberos._udp.dc._msdcs'), 88),
+    )
+
+    # fixme do it configurable
+    PRIORITY_HIGH = 0
+    PRIORITY_LOW = 50
+
+    def __init__(self, api_instance):
+        self.api_instance = api_instance
+        self.servers_data = {}
+        self.__init_data()
+
+    def reload_data(self):
+        self.__init_data()
+
+    def __get_server_attrs(self, hostname, allowed_roles=None):
+        """
+        Returns weight, location, and intersection of its roles and allowed
+        roles
+        :param hostname: server hostname
+        :param allowed_roles: *set* of server roles that are allowed, if None
+        all roles will be used
+        :return: (weight, location, intersection of roles and allowed roles)
+        """
+        server_result = self.api_instance.Command.server_show(hostname)['result']
+        weight = int(server_result.get('ipalocation_weight', [u'100'])[0])
+        location = server_result.get('ipalocation', [None])[0]
+        roles = set(server_result.get('enabled_role_servrole', ()))
+
+        return weight, location, roles
+
+    def __init_data(self):
+        self.servers_data = {}
+
+        servers_result = self.api_instance.Command.server_find(
+            pkey_only=True)['result']
+        servers = [s['cn'][0] for s in servers_result]
+        for s in servers:
+            weight, location, roles = self.__get_server_attrs(s)
+            self.servers_data[s] = {
+                'weight': weight,
+                'location': location,
+                'roles': roles,
+            }
+
+    def __get_srv_records(
+        self, hostname, rname_port_map,
+        weight=100, priority=0, location=None
+    ):
+        assert isinstance(hostname, DNSName)
+        assert isinstance(priority, int)
+        assert isinstance(weight, int)
+
+        rrsets_list = []
+
+        if location:
+            suffix = (
+                location + DNSName('_locations') +
+                DNSName(self.api_instance.env.domain).make_absolute()
+            )
+        else:
+            suffix = DNSName(self.api_instance.env.domain).make_absolute()
+
+        for name, port in rname_port_map:
+            rd = SRV(
+                rdataclass.IN, rdatatype.SRV,
+                priority,
+                weight,
+                port,
+                hostname.make_absolute()
+            )
+
+            r_name = name.derelativize(suffix)
+
+            # FIXME: use TTL from config
+            rrsets_list.append(rrset.from_rdata(r_name, 86400, rd))
+        return rrsets_list
+
+    def __get_ca_records_from_hostname(self, hostname):
+        assert isinstance(hostname, DNSName) and hostname.is_absolute()
+        rrsets = resolve_rrsets(hostname, ['A', 'AAAA'])
+        for rrset in rrsets:
+            rrset.name = (
+                DNSName('ipa-ca') +
+                DNSName(self.api_instance.env.domain).make_absolute()
+            )
+        return rrsets
+
+    def _sort_rrsets(self, rrsets, result_dict):
+        for rr in rrsets:
+            records = result_dict.setdefault(rr.name, [])
+            records.append(rr)
+
+    def get_base_dns_records(self, servers=None, roles=None,
+            include_master_role=True):
+        """
+        Generate IPA service records for specific servers and roles
+        :param servers: list of server which will be used in records,
+        if None all IPA servers will be used
+        :param roles: roles for which DNS records will be generated,
+        if None all roles will be used
+        :return: dictionary containing record name as key and list of RRSets
+        as value
+        """
+        records = {}
+        if servers is None:
+            servers = self.servers_data.keys()
+
+        for server in servers:
+            self._sort_rrsets(
+                self._get_base_dns_records_for_server(
+                    server, roles=roles,
+                    include_master_role=include_master_role
+                ),
+                records
+            )
+        return records
+
+    def get_location_dns_records(self, servers=None, roles=None,
+            include_master_role=True):
+        """
+        Generate IPA service records for specific servers and roles
+        :param servers: list of server which will be used in records,
+        if None all IPA servers will be used
+        :param roles: roles for which DNS records will be generated,
+        if None all roles will be used
+        :return: dictionary containing record name as key and list of RRSets
+        as value
+        """
+        records = {}
+        if servers is None:
+            servers_result = self.api_instance.Command.server_find(
+                pkey_only=True)['result']
+            servers = [s['cn'][0] for s in servers_result]
+
+        locations_result = self.api_instance.Command.location_find()['result']
+        locations = [l['idnsname'][0] for l in locations_result]
+
+        for server in servers:
+            self._sort_rrsets(
+                self._get_location_dns_records_for_server(
+                    server, locations, roles=roles,
+                    include_master_role=include_master_role
+                ),
+                records
+            )
+        return records
+
+    def _get_base_dns_records_for_server(self, hostname, roles=None,
+            include_master_role=True):
+        base_records_list = []
+        server = self.servers_data[hostname]
+        if roles:
+            eff_roles = server['roles'] & roles
+        else:
+            eff_roles = server['roles']
+        hostname_abs = DNSName(hostname).make_absolute()
+
+        # get master records
+        if include_master_role:
+            base_records_list.extend(
+                self.__get_srv_records(
+                    hostname_abs,
+                    self.IPA_DEFAULT_MASTER_SRV_REC,
+                    weight=server['weight']
+                )
+            )
+
+        if 'CA server' in eff_roles:
+            base_records_list.extend(
+                self.__get_ca_records_from_hostname(hostname_abs)
+            )
+
+        if 'AD trust controller' in eff_roles:
+            base_records_list.extend(
+                self.__get_srv_records(
+                    hostname_abs,
+                    self.IPA_DEFAULT_ADTRUST_SRV_REC,
+                    weight=server['weight']
+                )
+            )
+
+        return base_records_list
+
+    def _get_location_dns_records_for_server(
+            self, hostname, locations, roles=None, include_master_role=True):
+        """
+        Function returns list of DNS records for server with proper locations
+        mappings
+        :param api_instance: instance of API
+        :param hostname: server hostname
+        :return: dict with keys as record name, and list of rrsets as values
+        """
+        location_records_list = []
+        server = self.servers_data[hostname]
+        if roles:
+            eff_roles = server['roles'] & roles
+        else:
+            eff_roles = server['roles']
+        hostname_abs = DNSName(hostname).make_absolute()
+
+        # generate locations specific records
+        for location in locations:
+            if location == self.servers_data[hostname]['location']:
+                priority = self.PRIORITY_HIGH
+            else:
+                priority = self.PRIORITY_LOW
+
+            if include_master_role:
+                location_records_list.extend(
+                    self.__get_srv_records(
+                        hostname_abs,
+                        self.IPA_DEFAULT_MASTER_SRV_REC,
+                        weight=server['weight'],
+                        priority=priority,
+                        location=location
+                    )
+                )
+
+            if 'AD trust controller' in eff_roles:
+                location_records_list.extend(
+                    self.__get_srv_records(
+                        hostname_abs,
+                        self.IPA_DEFAULT_ADTRUST_SRV_REC,
+                        weight=server['weight'],
+                        priority=priority,
+                        location=location
+                    )
+                )
+
+        return location_records_list
+
+    def update_dns_records(self):
+        for record_name, rrsets in self.get_base_dns_records():
+            update_dict = defaultdict(list)
+            for rrset in rrsets:
+                for rdata in rrset.items:
+                    option_name = (
+                        record_name_format % rdatatype.to_text(
+                            rdata.rdtype).lower()
+                    )
+                    update_dict[option_name].append(rdata.to_text())
+            try:
+                removal_dict = {k: None for k in update_dict.keys()}
+                self.api_instance.Command.dnsrecord_mod(
+                    self.api_instance.env.domain, record_name,
+                    **removal_dict
+                )
+            except (errors.NotFound, errors.EmptyModlist):
+                pass
+            try:
+                self.api_instance.Command.dnsrecord_add(
+                    self.api_instance.env.domain, record_name,
+                    add_attr=[
+                        u'objectclass:idnsTemplateObject',
+                        u'idnsTemplateAttribute;cnamerecord:'
+                        u'%s\{substitutionvariable_ipalocation\}._locations' %
+                        record_name.relativize(
+                            DNSName(self.api_instance.env.domain))
+                    ],
+                    **update_dict
+                )
+            except (errors.NotFound, errors.EmptyModlist):
+                pass
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to