[Freeipa-devel] [PATCH] 037 Remove the original DNS plugin

Jakub Hrozek jhrozek at redhat.com
Wed Jan 12 21:40:40 UTC 2011


On Wed, Jan 12, 2011 at 08:58:15PM +0100, Jakub Hrozek wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> On 01/12/2011 08:56 PM, Jakub Hrozek wrote:
> > I didn't find a related ticket, but I think this needs to be done. At
> > the very least it caused confusion for QA.
> > 
> > This patch
> > - removes the obsolete DNS plugin
> > - renames the new plugin to dns
> > - moves ipa dns-resolve to the new plugin
> > - ports the installer and the host plugin to the new interface
> > 
> > I didn't touch the UI at all. Adam, Endi, do I need to tweak it somehow
> > (esp. because the plugin is renamed).
> > 
> > 	Jakub
> 
> Attached is another version of the same patch, just formatted with -M
> - -C, so it should hopefully look better.
> 

OK, that was still not very readable so I splitted the patches into two
to ease the review:

1) jhrozek-freeipa-037-03-dont-use-legacy-dns.patch:
Port installer and host plugin to the new DNS plugin

* moves ipa dns-resolve to the new plugin
* ports the installer and the host plugin to the new interface

2) jhrozek-freeipa-038-rename-dns2-to-dns.patch
No functionality change, just renames the old plugin to the new one.

I used "git format-patch -M -C --patience --full-index" to format the
patch but git still didn't detect the replace, it seems. Is there
anything else I can do in order to get a prettier patch? I created the
patch with "git rm ipalib/plugins/dns.py" and then "git mv
ipalib/plugins/dns2.py ipalib/plugins/dns.py" -- without performing rm
first, git would complain about renaming file to another which is
tracked.

-------------- next part --------------
>From a244add5fcfd74415c537074f456bce2adb1160e Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 12 Jan 2011 21:02:05 +0100
Subject: [PATCH 1/2] Port installer and host plugin to the new DNS plugin

* move ipa dns-resolve to the new plugin
* port the installer and the host plugin to the new interface
---
 ipalib/plugins/dns2.py            |   52 ++++++++++++++++++++++++++++++++-----
 ipalib/plugins/host.py            |   35 ++++++++++++++----------
 ipaserver/install/bindinstance.py |   30 ++++++++++----------
 3 files changed, 80 insertions(+), 37 deletions(-)

diff --git a/ipalib/plugins/dns2.py b/ipalib/plugins/dns2.py
index 9254f1df9184a04fd5b6940eb3c2198b092b0c1d..d8e0ad657ef45085258cfec018647d83455eb94c 100644
--- a/ipalib/plugins/dns2.py
+++ b/ipalib/plugins/dns2.py
@@ -121,6 +121,13 @@ _record_validators = {
 }
 
 
+def dns_container_exists(ldap):
+    try:
+        ldap.get_entry(api.env.container_dns, [])
+    except errors.NotFound:
+        return False
+    return True
+
 class dnszone(LDAPObject):
     """
     DNS Zone, container for resource records.
@@ -227,12 +234,6 @@ class dnszone(LDAPObject):
         ),
     )
 
-    def check_container_exists(self):
-        try:
-            self.backend.get_entry(self.container_dn, [])
-        except errors.NotFound:
-            raise errors.NotFound(reason=_('DNS is not configured'))
-
 api.register(dnszone)
 
 
@@ -241,7 +242,9 @@ class dnszone_add(LDAPCreate):
     Create new DNS zone (SOA record).
     """
     def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        self.obj.check_container_exists()
+        if not dns_container_exists(self.api.Backend.ldap2):
+            raise errors.NotFound(reason=_('DNS is not configured'))
+
         entry_attrs['idnszoneactive'] = 'TRUE'
         entry_attrs['idnsallowdynupdate'] = str(
             entry_attrs.get('idnsallowdynupdate', False)
@@ -583,3 +586,38 @@ class dnsrecord_find(LDAPSearch, dnsrecord_cmd_w_record_options):
 
 api.register(dnsrecord_find)
 
+class dns_resolve(Command):
+    """
+    Resolve a host name in DNS
+    """
+    has_output = output.standard_value
+    msg_summary = _('Found \'%(value)s\'')
+
+    takes_args = (
+        Str('hostname',
+            label=_('Hostname'),
+        ),
+    )
+
+    def execute(self, *args, **options):
+        query=args[0]
+        if query.find(api.env.domain) == -1 and query.find('.') == -1:
+            query = '%s.%s.' % (query, api.env.domain)
+        if query[-1] != '.':
+            query = query + '.'
+        reca = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
+        rec6 = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
+        records = reca + rec6
+        found = False
+        for rec in records:
+            if rec.dns_type == dnsclient.DNS_T_A or \
+              rec.dns_type == dnsclient.DNS_T_AAAA:
+                found = True
+                break
+
+        if not found:
+            raise errors.NotFound(reason=_('Host \'%(host)s\' not found' % {'host':query}))
+
+        return dict(result=True, value=query)
+
+api.register(dns_resolve)
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index 88ac0bcb780d44d1619d3eb2d91c74a3b2ed3a25..d60f63776714689e754c3f1c18e972e062304edc 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -84,7 +84,7 @@ from ipalib.plugins.service import normalize_certificate
 from ipalib.plugins.service import set_certificate_attrs
 from ipalib.plugins.service import make_pem, check_writable_file
 from ipalib.plugins.service import write_certificate
-from ipalib.plugins.dns import dns_container_exists, _attribute_types
+from ipalib.plugins.dns2 import dns_container_exists, _record_types
 from ipalib import _, ngettext
 from ipalib import x509
 from ipapython.ipautil import ipa_generate_password
@@ -282,7 +282,7 @@ class host_add(LDAPCreate):
         if 'ip_address' in options and dns_container_exists(ldap):
             parts = keys[-1].split('.')
             domain = unicode('.'.join(parts[1:]))
-            result = api.Command['dns_find']()['result']
+            result = api.Command['dnszone_find']()['result']
             match = False
             for zone in result:
                 if domain == zone['idnsname'][0]:
@@ -290,7 +290,7 @@ class host_add(LDAPCreate):
                     break
             if not match:
                 raise errors.NotFound(reason=_('DNS zone %(zone)s not found' % dict(zone=domain)))
-            if not options.get('no_reverse',False):
+            if not options.get('no_reverse', False):
                 # we prefer lookup of the IP through the reverse zone
                 revzone, revname = get_reverse_zone(options['ip_address'])
                 # Verify that our reverse zone exists
@@ -302,7 +302,7 @@ class host_add(LDAPCreate):
                 if not match:
                     raise errors.NotFound(reason=_('Reverse DNS zone %(zone)s not found' % dict(zone=revzone)))
                 try:
-                    reverse = api.Command['dns_find_rr'](revzone, revname)
+                    reverse = api.Command['dnsrecord_find'](revzone, idnsname=revname)
                     if reverse['count'] > 0:
                         raise errors.DuplicateEntry(message=u'This IP address is already assigned.')
                 except errors.NotFound:
@@ -344,17 +344,18 @@ class host_add(LDAPCreate):
                 parts = keys[-1].split('.')
                 domain = unicode('.'.join(parts[1:]))
                 if ':' in options['ip_address']:
-                    type = u'AAAA'
+                    addkw = { u'aaaarecord' : options['ip_address'] }
                 else:
-                    type = u'A'
+                    addkw = { u'arecord' : options['ip_address'] }
                 try:
-                    api.Command['dns_add_rr'](domain, parts[0], type, options['ip_address'])
+                    api.Command['dnsrecord_add'](domain, parts[0], **addkw)
                 except errors.EmptyModlist:
                     # the entry already exists and matches
                     pass
                 revzone, revname = get_reverse_zone(options['ip_address'])
                 try:
-                    api.Command['dns_add_rr'](revzone, revname, u'PTR', keys[-1]+'.')
+                    addkw = { u'ptrrecord' : keys[-1]+'.' }
+                    api.Command['dnsrecord_add'](revzone, revname, **addkw)
                 except errors.EmptyModlist:
                     # the entry already exists and matches
                     pass
@@ -424,7 +425,7 @@ class host_del(LDAPDelete):
             # Remove DNS entries
             parts = fqdn.split('.')
             domain = unicode('.'.join(parts[1:]))
-            result = api.Command['dns_find']()['result']
+            result = api.Command['dnszone_find']()['result']
             match = False
             for zone in result:
                 if domain == zone['idnsname'][0]:
@@ -434,30 +435,34 @@ class host_del(LDAPDelete):
                 raise errors.NotFound(reason=_('DNS zone %(zone)s not found' % dict(zone=domain)))
                 raise e
             # Get all forward resources for this host
-            records = api.Command['dns_find_rr'](domain, parts[0])['result']
+            records = api.Command['dnsrecord_find'](domain, idnsname=parts[0])['result']
             for record in records:
                 if 'arecord' in record:
                     ipaddr = record['arecord'][0]
                     self.debug('deleting ipaddr %s' % ipaddr)
                     revzone, revname = get_reverse_zone(ipaddr)
                     try:
-                        api.Command['dns_del_rr'](revzone, revname, u'PTR', fqdn+'.')
+                        delkw = { u'ptrrecord' : fqdn+'.' }
+                        api.Command['dnsrecord_del'](revzone, revname, **delkw)
                     except errors.NotFound:
                         pass
                     try:
-                        api.Command['dns_del_rr'](domain, parts[0], u'A', ipaddr)
+                        delkw = { u'arecord' : ipaddr }
+                        api.Command['dnsrecord_del'](domain, parts[0], **delkw)
                     except errors.NotFound:
                         pass
                 else:
                     # Try to delete all other record types too
+                    _attribute_types = [str('%srecord' % t.lower()) for t in _record_types]
                     for attr in _attribute_types:
                         if attr != 'arecord' and attr in record:
                             for i in xrange(len(record[attr])):
                                 if (record[attr][i].endswith(parts[0]) or
                                     record[attr][i].endswith(fqdn+'.')):
-                                    api.Command['dns_del_rr'](domain,
-                                        record['idnsname'][0],
-                                        _attribute_types[attr], record[attr][i])
+                                    delkw = { unicode(attr) : record[attr][i] }
+                                    api.Command['dnsrecord_del'](domain,
+                                            record['idnsname'][0],
+                                            **delkw)
                             break
 
         try:
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index e1a5810f44d342fd1031efaf40da55684e833825..976b69541588d4dc157ae42fc69a9c2f09f3b71c 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -107,8 +107,8 @@ def get_reverse_zone(ip_address):
 
 def dns_zone_exists(name):
     try:
-        zone = api.Command.dns_show(unicode(name))
-    except Exception:
+        zone = api.Command.dnszone_show(unicode(name))
+    except ipalib.errors.NotFound:
         return False
 
     if len(zone) == 0:
@@ -121,11 +121,11 @@ def add_zone(name, update_policy=None, zonemgr=None, dns_backup=None):
         update_policy = "grant %s krb5-self * A;" % api.env.realm
 
     try:
-        api.Command.dns_add(unicode(name),
-                            idnssoamname=unicode(api.env.host+"."),
-                            idnssoarname=unicode(zonemgr),
-                            idnsallowdynupdate=True,
-                            idnsupdatepolicy=unicode(update_policy))
+        api.Command.dnszone_add(unicode(name),
+                                idnssoamname=unicode(api.env.host+"."),
+                                idnssoarname=unicode(zonemgr),
+                                idnsallowdynupdate=True,
+                                idnsupdatepolicy=unicode(update_policy))
     except (errors.DuplicateEntry, errors.EmptyModlist):
         pass
 
@@ -138,10 +138,10 @@ def add_reverze_zone(ip_address, update_policy=None, dns_backup=None):
     if not update_policy:
         update_policy = "grant %s krb5-subdomain %s. PTR;" % (api.env.realm, zone)
     try:
-        api.Command.dns_add(unicode(zone),
-                            idnssoamname=unicode(api.env.host+"."),
-                            idnsallowdynupdate=True,
-                            idnsupdatepolicy=unicode(update_policy))
+        api.Command.dnszone_add(unicode(zone),
+                                idnssoamname=unicode(api.env.host+"."),
+                                idnsallowdynupdate=True,
+                                idnsupdatepolicy=unicode(update_policy))
     except (errors.DuplicateEntry, errors.EmptyModlist):
         pass
 
@@ -150,9 +150,9 @@ def add_reverze_zone(ip_address, update_policy=None, dns_backup=None):
     return zone
 
 def add_rr(zone, name, type, rdata, dns_backup=None):
+    addkw = { '%srecord' % unicode(type.lower()) : unicode(rdata) }
     try:
-        api.Command.dns_add_rr(unicode(zone), unicode(name),
-                               unicode(type), unicode(rdata))
+        api.Command.dnsrecord_add(unicode(zone), unicode(name), **addkw)
     except (errors.DuplicateEntry, errors.EmptyModlist):
         pass
     if dns_backup:
@@ -201,8 +201,8 @@ class DnsBackup(object):
                 if have_ldap:
                     type, host, rdata = dns_record.split(" ", 2)
                     try:
-                        api.Command.dns_del_rr(unicode(zone), unicode(host),
-                                               unicode(type), unicode(rdata))
+                        delkw = { '%srecord' % unicode(type) : unicode(rdata) }
+                        api.Command.dnsrecord_del(unicode(zone), unicode(host), **delkw)
                     except:
                         pass
                 j += 1
-- 
1.7.3.4

-------------- next part --------------
>From 4257cc21718380674bd1f0c20b44f16984afe316 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 12 Jan 2011 22:09:02 +0100
Subject: [PATCH 2/2] Rename dns2 to dns

---
 ipalib/plugins/dns.py  | 1032 +++++++++++++++++-------------------------------
 ipalib/plugins/dns2.py |  623 -----------------------------
 ipalib/plugins/host.py |    2 +-
 3 files changed, 358 insertions(+), 1299 deletions(-)
 delete mode 100644 ipalib/plugins/dns2.py

diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index ced13efc92b2480bbe15675233903edd8387fa16..cf58098036f7056d20337b4d3b5f02b158b41360 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -1,7 +1,7 @@
 # Authors:
 #   Pavel Zuna <pzuna at redhat.com>
 #
-# Copyright (C) 2009  Red Hat
+# Copyright (C) 2010  Red Hat
 # see file 'COPYING' for use and warranty information
 #
 # This program is free software; you can redistribute it and/or modify
@@ -17,45 +17,45 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
-Domain Name System (DNS) plug-in
+Domain Name System (DNS)
 
-Implements a set of commands useful for manipulating DNS records used by
-the BIND LDAP plug-in.
+Manage DNS zone and resource records.
 
 EXAMPLES:
 
  Add new zone:
-   ipa dns-add example.com nameserver.example.com admin at example.com
+   ipa dnszone-add example.com --name-server nameserver.example.com
+                               --admin-email admin at example.com
 
  Add second nameserver for example.com:
-   ipa dns-add-rr example.com @ NS nameserver2.example.com
+   ipa dnsrecord-add example.com @ --ns-rec nameserver2.example.com
 
  Delete previously added nameserver from example.com:
-   ipa dns-del-rr example.com @ NS nameserver2.example.com
+   ipa dnsrecord-del example.com @ --ns-rec nameserver2.example.com
 
  Add new A record for www.example.com: (random IP)
-   ipa dns-add-rr example.com www A 80.142.15.2
+   ipa dnsrecord-add example.com www --a-rec 80.142.15.2
 
  Add new PTR record for www.example.com
-   ipa dns-add-rr 15.142.80.in-addr.arpa 2 PTR www.example.com.
+   ipa dnsrecord-add 15.142.80.in-addr.arpa 2 --ptr-rec www.example.com.
 
  Show zone example.com:
-   ipa dns-show example.com
+   ipa dnszone-show example.com
 
  Find zone with "example" in it's domain name:
-   ipa dns-find example
+   ipa dnszone-find example
 
  Find records for resources with "www" in their name in zone example.com:
-   ipa dns-find-rr example.com www
+   ipa dnsrecord-find example.com www
 
- Find A records for resource www in zone example.com
-   ipa dns-find-rr example.com --resource www --type A
+ Find A records with value 10.10.0.1 in zone example.com
+   ipa dnsrecord-find example.com --a-rec 10.10.0.1
 
  Show records for resource www in zone example.com
-   ipa dns-show-rr example.com www
+   ipa dnsrecord-show example.com www
 
  Delete zone example.com with all resource records:
-   ipa dns-delete example.com
+   ipa dnszone-del example.com
 
  Resolve a host name to see if it exists (will add default IPA domain
  if one is not included):
@@ -64,59 +64,31 @@ EXAMPLES:
 
 """
 
-# A few notes about the LDAP schema to make this plugin more understandable:
-#  - idnsRecord object is a HOSTNAME with one or more resource records
-#  - idnsZone object is a idnsRecord object with mandatory SOA record
-#    it basically makes the assumption that ZONE == DOMAINNAME + SOA record
-#    resource records can be stored in both idnsZone and idnsRecord objects
-
+import netaddr
 import time
 
-from ipalib import api, crud, errors, output
-from ipalib import Object, Command
-from ipalib import Flag, Int, Str, StrEnum
+from ipalib import api, errors, output
+from ipalib import Command
+from ipalib import Flag, Int, List, Str, StrEnum
+from ipalib.plugins.baseldap import *
 from ipalib import _, ngettext
-from ipalib.output import Output, standard_entry, standard_list_of_entries
 from ipapython import dnsclient
 
-# parent DN
-_zone_container_dn = api.env.container_dns
-
 # supported resource record types
 _record_types = (
-    u'A', u'AAAA', u'A6', u'AFSDB', u'CERT', u'CNAME', u'DNAME',
-    u'DS', u'HINFO', u'KEY', u'KX', u'LOC', u'MD', u'MINFO', u'MX',
-    u'NAPTR', u'NS', u'NSEC', u'NXT', u'PTR', u'RRSIG', u'SSHFP',
-    u'SRV', u'TXT',
+    u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV',
+    u'DNAME', u'DNSKEY', u'DS', u'HINFO', u'HIP', u'IPSECKEY', u'KEY', u'KX',
+    u'LOC', u'MD', u'MINFO', u'MX', u'NAPTR', u'NS', u'NSEC', u'NSEC3',
+    u'NSEC3PARAM', u'NXT', u'PTR', u'RRSIG', u'RP', u'SIG', u'SPF', u'SRV',
+    u'SSHFP', u'TA', u'TKEY', u'TSIG', u'TXT',
 )
 
-# mapping from attribute to resource record type
-_attribute_types = dict(
-    arecord=u'A', aaaarecord=u'AAAA', a6record=u'A6',
-    afsdbrecord=u'AFSDB', certrecord=u'CERT', cnamerecord=u'CNAME',
-    dnamerecord=u'DNAME', dsrecord=u'DS', hinforecord=u'HINFO',
-    keyrecord=u'KEY', kxrecord=u'KX', locrecord='LOC',
-    mdrecord=u'MD', minforecord=u'MINFO', mxrecord=u'MX',
-    naptrrecord=u'NAPTR', nsrecord=u'NS', nsecrecord=u'NSEC',
-    ntxtrecord=u'NTXT', ptrrecord=u'PTR', rrsigrecord=u'RRSIG',
-    sshfprecord=u'SSHFP', srvrecord=u'SRV', txtrecord=u'TXT',
-)
+# attributes derived from record types
+_record_attributes = [str('%srecord' % t.lower()) for t in _record_types]
 
 # supported DNS classes, IN = internet, rest is almost never used
 _record_classes = (u'IN', u'CS', u'CH', u'HS')
 
-# attributes displayed by default for resource records
-_record_default_attributes = ['%srecord' % r for r in _record_types]
-_record_default_attributes.append('idnsname')
-
-# attributes displayed by default for zones
-_zone_default_attributes = [
-    'idnsname', 'idnszoneactive', 'idnssoamname', 'idnssoarname',
-    'idnssoaserial', 'idnssoarefresh', 'idnssoaretry', 'idnssoaexpire',
-    'idnssoaminimum'
-]
-
-
 # normalizer for admin email
 def _rname_normalizer(value):
     value = value.replace('@', '.')
@@ -124,41 +96,57 @@ def _rname_normalizer(value):
         value += '.'
     return value
 
-# build zone dn
-def _get_zone_dn(ldap, idnsname):
-    rdn = ldap.make_rdn_from_attr('idnsname', idnsname)
-    return ldap.make_dn_from_rdn(rdn, _zone_container_dn)
+def _create_zone_serial(**kwargs):
+    """Generate serial number for zones."""
+    return int('%s01' % time.strftime('%Y%d%m'))
 
-# build dn for entry with record
-def _get_record_dn(ldap, zone, idnsname):
-    parent_dn = _get_zone_dn(ldap, zone)
-    if idnsname == '@' or idnsname == zone:
-        return parent_dn
-    rdn = ldap.make_rdn_from_attr('idnsname', idnsname)
-    return ldap.make_dn_from_rdn(rdn, parent_dn)
+def _validate_ipaddr(ugettext, ipaddr):
+    try:
+        ip = netaddr.IPAddress(ipaddr)
+    except netaddr.AddrFormatError:
+        return u'invalid address format'
+    return None
+
+def _validate_ipnet(ugettext, ipnet):
+    try:
+        net = netaddr.IPNetwork(ipnet)
+    except (UnboundLocalError, ValueError):
+        return u'invalid format'
+    return None
+
+_record_validators = {
+    u'A': _validate_ipaddr,
+    u'AAAA': _validate_ipaddr,
+    u'APL': _validate_ipnet,
+}
 
 
 def dns_container_exists(ldap):
-    """
-    See if the dns container exists. If not raise an exception.
-    """
-    basedn = 'cn=dns,%s' % api.env.basedn
     try:
-        ret = ldap.find_entries('(objectclass=*)', None, basedn,
-            ldap.SCOPE_BASE)
+        ldap.get_entry(api.env.container_dns, [])
     except errors.NotFound:
-        raise errors.NotFound(reason=_('DNS is not configured'))
-
+        return False
     return True
 
-class dns(Object):
-    """DNS zone/SOA record object."""
+class dnszone(LDAPObject):
+    """
+    DNS Zone, container for resource records.
+    """
+    container_dn = api.env.container_dns
+    object_name = 'DNS zone'
+    object_name_plural = 'DNS zones'
+    object_class = ['top', 'idnsrecord', 'idnszone']
+    default_attributes = [
+        'idnsname', 'idnszoneactive', 'idnssoamname', 'idnssoarname',
+        'idnssoaserial', 'idnssoarefresh', 'idnssoaretry', 'idnssoaexpire',
+        'idnssoaminimum'
+    ] + _record_attributes
     label = _('DNS')
 
     takes_params = (
         Str('idnsname',
             cli_name='name',
-            label=_('Zone'),
+            label=_('Zone name'),
             doc=_('Zone name (FQDN)'),
             normalizer=lambda value: value.lower(),
             primary_key=True,
@@ -166,743 +154,437 @@ class dns(Object):
         Str('idnssoamname',
             cli_name='name_server',
             label=_('Authoritative name server'),
+            doc=_('Authoritative name server'),
         ),
         Str('idnssoarname',
             cli_name='admin_email',
-            label=_('administrator e-mail address'),
+            label=_('Administrator e-mail address'),
+            doc=_('Administrator e-mail address'),
             default_from=lambda idnsname: 'root.%s' % idnsname,
             normalizer=_rname_normalizer,
         ),
         Int('idnssoaserial?',
             cli_name='serial',
             label=_('SOA serial'),
+            doc=_('SOA record serial number'),
+            create_default=_create_zone_serial,
+            autofill=True,
         ),
         Int('idnssoarefresh?',
             cli_name='refresh',
             label=_('SOA refresh'),
+            doc=_('SOA record refresh time'),
+            default=3600,
+            autofill=True,
         ),
         Int('idnssoaretry?',
             cli_name='retry',
             label=_('SOA retry'),
+            doc=_('SOA record retry time'),
+            default=900,
+            autofill=True,
         ),
         Int('idnssoaexpire?',
             cli_name='expire',
             label=_('SOA expire'),
+            doc=_('SOA record expire time'),
+            default=1209600,
+            autofill=True,
         ),
         Int('idnssoaminimum?',
             cli_name='minimum',
             label=_('SOA minimum'),
+            doc=_('SOA record minimum value'),
+            default=3600,
+            autofill=True,
+        ),
+        Int('idnssoamaximum?',
+            cli_name='maximum',
+            label=_('SOA maximum'),
+            doc=_('SOA record maximum value'),
         ),
         Int('dnsttl?',
             cli_name='ttl',
             label=_('SOA time to live'),
+            doc=_('SOA record time to live'),
         ),
         StrEnum('dnsclass?',
             cli_name='class',
             label=_('SOA class'),
+            doc=_('SOA record class'),
             values=_record_classes,
         ),
-        Flag('idnsallowdynupdate',
-            cli_name='allow_dynupdate',
-            label=_('allow dynamic update?'),
-        ),
         Str('idnsupdatepolicy?',
             cli_name='update_policy',
             label=_('BIND update policy'),
+            doc=_('BIND update policy'),
+        ),
+        Flag('idnszoneactive?',
+            cli_name='zone_active',
+            label=_('Active zone'),
+            doc=_('Is zone active?'),
+            flags=['no_create', 'no_update'],
+            attribute=True,
+        ),
+        Flag('idnsallowdynupdate',
+            cli_name='allow_dynupdate',
+            label=_('Dynamic update'),
+            doc=_('Allow dynamic update?'),
+            attribute=True,
         ),
     )
 
-    default_attributes = _zone_default_attributes
+api.register(dnszone)
 
-    json_friendly_attributes = (
-        'default_attributes', 'label', 'name', 'takes_params' )
 
-    def __json__(self):
-        json_dict = dict(
-            (a, getattr(self, a)) for a in self.json_friendly_attributes
-        )
-        if self.primary_key:
-            json_dict['primary_key'] = self.primary_key.name
-        json_dict['methods'] = [m for m in self.methods]
-        return json_dict
-
-
-api.register(dns)
-
-
-class dns_add(crud.Create):
+class dnszone_add(LDAPCreate):
     """
-    Create new DNS zone/SOA record.
+    Create new DNS zone (SOA record).
     """
-    def execute(self, *args, **options):
-        ldap = self.Backend.ldap2
-        idnsname = args[0]
-
-        dns_container_exists(ldap)
-
-        # build entry attributes
-        entry_attrs = self.args_options_2_entry(*args, **options)
-
-        # build entry DN
-        dn = _get_zone_dn(ldap, idnsname)
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        if not dns_container_exists(self.api.Backend.ldap2):
+            raise errors.NotFound(reason=_('DNS is not configured'))
 
-        # fill in required attributes
-        entry_attrs['objectclass'] = ['top', 'idnsrecord', 'idnszone']
         entry_attrs['idnszoneactive'] = 'TRUE'
         entry_attrs['idnsallowdynupdate'] = str(
-            entry_attrs['idnsallowdynupdate']
+            entry_attrs.get('idnsallowdynupdate', False)
         ).upper()
 
-        # fill default values, build SOA serial from current date
-        soa_serial = int('%s01' % time.strftime('%Y%d%m'))
-        entry_attrs.setdefault('idnssoaserial', soa_serial)
-        entry_attrs.setdefault('idnssoarefresh', 3600)
-        entry_attrs.setdefault('idnssoaretry', 900)
-        entry_attrs.setdefault('idnssoaexpire', 1209600)
-        entry_attrs.setdefault('idnssoaminimum', 3600)
+        nameserver = entry_attrs['idnssoamname']
+        if nameserver[-1] != '.':
+            nameserver += '.'
+        entry_attrs['nsrecord'] = nameserver
+        entry_attrs['idnssoamname'] = nameserver
+        return dn
 
-        # create zone entry
-        ldap.add_entry(dn, entry_attrs)
+api.register(dnszone_add)
 
-        # get zone entry with created attributes for output
-        (dn, entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
-        entry_attrs['dn'] = dn
 
-        return dict(result=entry_attrs, value=idnsname)
-
-    def output_for_cli(self, textui, result, *args, **options):
-        entry_attrs = result['result']
-        idnsname = result['value']
-
-        textui.print_name(self.name)
-        textui.print_attribute('dn', entry_attrs['dn'])
-        del entry_attrs['dn']
-        textui.print_entry(entry_attrs)
-        textui.print_dashed('Created DNS zone "%s".' % idnsname)
-
-api.register(dns_add)
-
-
-class dns_del(crud.Delete):
+class dnszone_del(LDAPDelete):
     """
-    Delete existing DNS zone/SOA record.
+    Delete DNS zone (SOA record).
     """
-    def execute(self, *args, **options):
-        ldap = self.api.Backend.ldap2
-        idnsname = args[0]
-
-        dns_container_exists(ldap)
-
-        # build zone entry DN
-        dn = _get_zone_dn(ldap, idnsname)
-        # just check if zone exists for now
-        ldap.get_entry(dn, [''])
 
-        # retrieve all subentries of zone - records
-        try:
-            (entries, truncated) = ldap.find_entries(
-                None, [''], dn, ldap.SCOPE_ONELEVEL
-            )
-        except errors.NotFound:
-            (entries, truncated) = (tuple(), False)
+api.register(dnszone_del)
 
-        # kill'em all, records first
-        for e in entries:
-            ldap.delete_entry(e[0])
-        ldap.delete_entry(dn)
 
-        return dict(result=True, value=u'')
-
-    def output_for_cli(self, textui, result, *args, **options):
-        textui.print_name(self.name)
-        textui.print_dashed('Deleted DNS zone "%s".' % args[0])
-
-api.register(dns_del)
-
-
-class dns_mod(crud.Update):
+class dnszone_mod(LDAPUpdate):
     """
-    Modify DNS zone/SOA record.
+    Modify DNS zone (SOA record).
     """
-    def execute(self, *args, **options):
-        ldap = self.api.Backend.ldap2
-        idnsname = args[0]
-
-        dns_container_exists(ldap)
-
-        # build entry attributes, don't include idnsname!
-        entry_attrs = self.args_options_2_entry(*tuple(), **options)
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
         entry_attrs['idnsallowdynupdate'] = str(
-            entry_attrs['idnsallowdynupdate']
+            entry_attrs.get('idnsallowdynupdate', False)
         ).upper()
+        return dn
 
-        # build entry DN
-        dn = _get_zone_dn(ldap, idnsname)
+api.register(dnszone_mod)
 
-        # update zone entry
-        ldap.update_entry(dn, entry_attrs)
 
-        # get zone entry with modified + default attributes for output
-        (dn, entry_attrs) = ldap.get_entry(
-            dn, (entry_attrs.keys() + _zone_default_attributes)
-        )
-        entry_attrs['dn'] = dn
-
-        return dict(result=entry_attrs, value=idnsname)
-
-    def output_for_cli(self, textui, result, *args, **options):
-        entry_attrs = result['result']
-        idnsname = result['value']
-
-        textui.print_name(self.name)
-        textui.print_attribute('dn', entry_attrs['dn'])
-        del entry_attrs['dn']
-        textui.print_entry(entry_attrs)
-        textui.print_dashed('Modified DNS zone "%s".' % idnsname)
-
-api.register(dns_mod)
-
-
-class dns_find(crud.Search):
-    """
-    Search for DNS zones/SOA records.
-    """
-    def execute(self, term, **options):
-        ldap = self.api.Backend.ldap2
-
-        dns_container_exists(ldap)
-
-        # build search filter
-        filter = ldap.make_filter_from_attr('idnsname', term, exact=False)
-
-        # select attributes we want to retrieve
-        if options.get('all', False):
-            attrs_list = ['*']
-        else:
-            attrs_list = _zone_default_attributes
-
-        # get matching entries
-        try:
-            (entries, truncated) = ldap.find_entries(
-                filter, attrs_list, _zone_container_dn, ldap.SCOPE_ONELEVEL
-            )
-        except errors.NotFound:
-            (entries, truncated) = (tuple(), False)
-
-        for e in entries:
-            e[1]['dn'] = e[0]
-        entries = tuple(e for (dn, e) in entries)
-
-        return dict(result=entries, count=len(entries), truncated=truncated)
-
-    def output_for_cli(self, textui, result, term, **options):
-        entries = result['result']
-        truncated = result['truncated']
-
-        textui.print_name(self.name)
-        for entry_attrs in entries:
-            textui.print_attribute('dn', entry_attrs['dn'])
-            del entry_attrs['dn']
-            textui.print_entry(entry_attrs)
-            textui.print_plain('')
-        textui.print_count(
-            len(entries), '%i DNS zone matched.', '%i DNS zones matched.'
-        )
-        if truncated:
-            textui.print_dashed('These results are truncated.', below=False)
-            textui.print_dashed(
-                'Please refine your search and try again.', above=False
-            )
-
-api.register(dns_find)
-
-
-class dns_show(crud.Retrieve):
+class dnszone_find(LDAPSearch):
     """
-    Display DNS zone/SOA record.
+    Search for DNS zones (SOA records).
     """
-    def execute(self, idnsname, **options):
-        ldap = self.api.Backend.ldap2
-
-        dns_container_exists(ldap)
-
-        # build entry DN
-        dn = _get_zone_dn(ldap, idnsname)
-
-        # select attributes we want to retrieve
-        if options.get('all', False):
-            attrs_list = ['*']
-        else:
-            attrs_list = _zone_default_attributes
-
-        (dn, entry_attrs) = ldap.get_entry(dn, attrs_list)
-        entry_attrs['dn'] = dn
 
-        return dict(result=entry_attrs, value=idnsname)
+api.register(dnszone_find)
 
-    def output_for_cli(self, textui, result, *args, **options):
-        entry_attrs = result['result']
 
-        textui.print_name(self.name)
-        textui.print_attribute('dn', entry_attrs['dn'])
-        del entry_attrs['dn']
-        textui.print_entry(entry_attrs)
-
-api.register(dns_show)
-
-
-class dns_enable(Command):
+class dnszone_show(LDAPRetrieve):
     """
-    Activate DNS zone.
+    Display information about a DNS zone (SOA record).
     """
-    takes_args = (
-        Str('zone',
-            cli_name='zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
-    )
-
-    has_output = output.standard_value
-
-    def execute(self, zone):
-        ldap = self.api.Backend.ldap2
-
-        dns_container_exists(ldap)
-
-        # build entry DN
-        dn = _get_zone_dn(ldap, zone)
-
-        # activate!
-        try:
-            ldap.update_entry(dn, {'idnszoneactive': 'TRUE'})
-        except errors.EmptyModlist:
-            pass
 
-        return dict(result=True, value=zone)
+api.register(dnszone_show)
 
-    def output_for_cli(self, textui, result, zone):
-        textui.print_name(self.name)
-        textui.print_dashed('Activated DNS zone "%s".' % zone)
 
-api.register(dns_enable)
-
-
-class dns_disable(Command):
+class dnszone_disable(LDAPQuery):
     """
-    Deactivate DNS zone.
+    Disable DNS Zone.
     """
-    takes_args = (
-        Str('zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
-    )
-
     has_output = output.standard_value
+    msg_summary = _('Disabled DNS zone "%(value)s"')
 
-    def execute(self, zone):
-        ldap = self.api.Backend.ldap2
-
-        dns_container_exists(ldap)
+    def execute(self, *keys, **options):
+        ldap = self.obj.backend
 
-        # build entry DN
-        dn = _get_zone_dn(ldap, zone)
+        dn = self.obj.get_dn(*keys, **options)
 
-        # deactivate!
         try:
             ldap.update_entry(dn, {'idnszoneactive': 'FALSE'})
         except errors.EmptyModlist:
             pass
 
-        return dict(result=True, value=zone)
+        return dict(result=True, value=keys[-1])
 
-    def output_for_cli(self, textui, result, zone):
-        textui.print_name(self.name)
-        textui.print_dashed('Deactivated DNS zone "%s".' % zone)
+api.register(dnszone_disable)
 
-api.register(dns_disable)
 
+class dnszone_enable(LDAPQuery):
+    """
+    Enable DNS Zone.
+    """
+    has_output = output.standard_value
+    msg_summary = _('Enabled DNS zone "%(value)s"')
+
+    def execute(self, *keys, **options):
+        ldap = self.obj.backend
+
+        dn = self.obj.get_dn(*keys, **options)
+
+        try:
+            ldap.update_entry(dn, {'idnszoneactive': 'TRUE'})
+        except errors.EmptyModlist:
+            pass
 
-class dns_add_rr(Command):
+        return dict(result=True, value=keys[-1])
+
+api.register(dnszone_enable)
+
+
+class dnsrecord(LDAPObject):
     """
-    Add new DNS resource record.
+    DNS record.
     """
+    parent_object = 'dnszone'
+    container_dn = api.env.container_dns
+    object_name = 'DNS resource record'
+    object_name_plural = 'DNS resource records'
+    object_class = ['top', 'idnsrecord']
+    default_attributes = _record_attributes + ['idnsname']
+
+    label = _('DNS resource record')
 
-    takes_args = (
-        Str('zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
+    takes_params = (
         Str('idnsname',
-            cli_name='resource',
-            label=_('resource name'),
-            default_from=lambda zone: zone.lower(),
-            attribute=True,
+            cli_name='name',
+            label=_('Record name'),
+            doc=_('Record name'),
+            primary_key=True,
         ),
-        StrEnum('type',
-            label=_('Record type'),
-            values=_record_types,
-        ),
-        Str('data',
-            label=_('Data'),
-            doc=_('Type-specific data'),
-        ),
-    )
-
-    takes_options = (
         Int('dnsttl?',
             cli_name='ttl',
             label=_('Time to live'),
-            attribute=True,
+            doc=_('Time to live'),
         ),
         StrEnum('dnsclass?',
             cli_name='class',
             label=_('Class'),
+            doc=_('DNS class'),
             values=_record_classes,
-            attribute=True,
         ),
     )
 
-    has_output = standard_entry
+    def is_pkey_zone_record(*keys):
+        idnsname = keys[-1]
+        if idnsname == '@' or idnsname == ('%s.' % keys[-2]):
+            return True
+        return False
+
+    def get_dn(self, *keys, **options):
+        if self.is_pkey_zone_record(*keys):
+            return self.api.Object[self.parent_object].get_dn(*keys[:-1], **options)
+        return super(dnsrecord, self).get_dn(*keys, **options)
+
+api.register(dnsrecord)
+
+
+class dnsrecord_cmd_w_record_options(Command):
+    """
+    Base class for DNS record commands with record options.
+    """
+    record_param_doc = 'comma-separated list of %s records'
+
+    def get_record_options(self):
+        for t in _record_types:
+            t = t.encode('utf-8')
+            doc = self.record_param_doc % t
+            validator = _record_validators.get(t)
+            if validator:
+                yield List(
+                    '%srecord?' % t.lower(), validator,
+                    cli_name='%s_rec' % t.lower(), doc=doc,
+                    label='%s record' % t, attribute=True
+                )
+            else:
+                yield List(
+                    '%srecord?' % t.lower(), cli_name='%s_rec' % t.lower(),
+                    doc=doc, label='%s record' % t, attribute=True
+                )
+
+    def record_options_2_entry(self, **options):
+        return dict((t, options.get(t, [])) for t in _record_attributes)
+
+
+class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
+    """
+    Base class for adding/removing records from DNS resource entries.
+    """
+    has_output = output.standard_entry
 
-    def execute(self, zone, idnsname, type, data, **options):
-        ldap = self.api.Backend.ldap2
-        attr = ('%srecord' % type).lower()
+    def get_options(self):
+        for option in super(dnsrecord_mod_record, self).get_options():
+            yield option
+        for option in self.get_record_options():
+            yield option
 
-        dns_container_exists(ldap)
+    def execute(self, *keys, **options):
+        ldap = self.obj.backend
 
-        # build entry DN
-        dn = _get_record_dn(ldap, zone, idnsname)
+        dn = self.obj.get_dn(*keys, **options)
+
+        entry_attrs = self.record_options_2_entry(**options)
 
-        # get resource entry where to store the new record
         try:
-            (dn, entry_attrs) = ldap.get_entry(dn, [attr])
+            (dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
         except errors.NotFound:
-            if idnsname != '@' and idnsname != zone:
-                # resource entry doesn't exist, check if zone exists
-                zone_dn = _get_zone_dn(ldap, zone)
-                ldap.get_entry(zone_dn, [''])
-                # it does, create new resource entry
-
-                # build entry attributes
-                entry_attrs = self.args_options_2_entry(
-                    (idnsname, ), **options
-                )
+            self.obj.handle_not_found(*keys)
+
+        self.update_old_entry_callback(entry_attrs, old_entry_attrs)
+
+        try:
+            ldap.update_entry(dn, old_entry_attrs)
+        except errors.EmptyModlist:
+            pass
+
+        if options.get('all', False):
+            attrs_list = ['*']
+        else:
+            attrs_list = list(
+                set(self.obj.default_attributes + entry_attrs.keys())
+            )
+
+        try:
+            (dn, entry_attrs) = ldap.get_entry(dn, attrs_list)
+        except errors.NotFound:
+            self.obj.handle_not_found(*keys)
 
-                # fill in required attributes
-                entry_attrs['objectclass'] = ['top', 'idnsrecord']
+        if self.obj.is_pkey_zone_record(*keys):
+            entry_attrs[self.obj.primary_key.name] = [u'@']
 
-                # fill in the record
-                entry_attrs[attr] = data
+        self.post_callback(keys, entry_attrs)
 
-                # create the entry
-                ldap.add_entry(dn, entry_attrs)
+        return dict(result=entry_attrs, value=keys[-1])
 
-                # get entry with created attributes for output
-                (dn, entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
-                entry_attrs['dn'] = dn
+    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
+        pass
 
-                return dict(result=entry_attrs, value=idnsname)
+    def post_callback(self, keys, entry_attrs):
+        pass
 
-            # zone doesn't exist
-            raise
-        # resource entry already exists, create a modlist for the new record
 
-        # convert entry_attrs keys to lowercase
-        #entry_attrs = dict(
-        #    (k.lower(), v) for (k, v) in entry_attrs.iteritems()
-        #)
+class dnsrecord_add_record(dnsrecord_mod_record):
+    """
+    Add records to DNS resource.
+    """
+    INTERNAL = True
 
-        # get new value for record attribute
-        attr_value = entry_attrs.get(attr, [])
-        attr_value.append(data)
+    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
+        for (a, v) in entry_attrs.iteritems():
+            if not isinstance(v, (list, tuple)):
+                v = [v]
+            old_entry_attrs.setdefault(a, [])
+            old_entry_attrs[a] += v
 
-        ldap.update_entry(dn, {attr: attr_value})
-        # get entry with updated attribute for output
-        (dn, entry_attrs) = ldap.get_entry(dn, ['idnsname', attr])
-        entry_attrs['dn'] = dn
+api.register(dnsrecord_add_record)
+
+
+class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
+    """
+    Add new DNS resource record.
+    """
+    def get_options(self):
+        for option in super(dnsrecord_add, self).get_options():
+            yield option
+        for option in self.get_record_options():
+            yield option
+
+    def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
+        if call_func.func_name == 'add_entry':
+            if isinstance(exc, errors.DuplicateEntry):
+                self.obj.methods.add_record(
+                    *keys, **self.record_options_2_entry(**options)
+                )
+                return
+        raise exc
 
-        return dict(result=entry_attrs, value=idnsname)
+api.register(dnsrecord_add)
 
-    def output_for_cli(self, textui, result, zone, idnsname, type, data,
-            **options):
-        entry_attrs = result['result']
-        output = '"%s %s %s" to zone "%s"' % (
-            idnsname, type, data, zone,
-        )
 
-        textui.print_name(self.name)
-        textui.print_attribute('dn', entry_attrs['dn'])
-        del entry_attrs['dn']
-        textui.print_entry(entry_attrs)
-        textui.print_dashed('Added DNS resource record %s.' % output)
+class dnsrecord_delentry(LDAPDelete):
+    """
+    Delete DNS record entry.
+    """
+    INTERNAL = True
 
-api.register(dns_add_rr)
+api.register(dnsrecord_delentry)
 
 
-class dns_del_rr(Command):
+class dnsrecord_del(dnsrecord_mod_record):
     """
     Delete DNS resource record.
     """
-
-    takes_args = (
-        Str('zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
-        Str('idnsname',
-            cli_name='resource',
-            label=_('Resource name'),
-            default_from=lambda zone: zone.lower(),
-            attribute=True,
-        ),
-        StrEnum('type',
-            label=_('Record type'),
-            values=_record_types,
-        ),
-        Str('data',
-            label=_('Data'),
-            doc=_('Type-specific data'),
-        ),
-    )
-
-    has_output = standard_entry
-
-    def execute(self, zone, idnsname, type, data, **options):
-        ldap = self.api.Backend.ldap2
-        attr = ('%srecord' % type).lower()
-
-        dns_container_exists(ldap)
-
-        # build entry DN
-        dn = _get_record_dn(ldap, zone, idnsname)
-
-        # get resource entry with the record we're trying to delete
-        (dn, entry_attrs) = ldap.get_entry(dn)
-
-        # convert entry_attrs keys to lowercase
-        entry_attrs = dict(
-            (k.lower(), v) for (k, v) in entry_attrs.iteritems()
-        )
-
-        # get new value for record attribute
-        attr_value = entry_attrs.get(attr.lower(), [])
-        try:
-            attr_value.remove(data)
-        except ValueError:
-            raise errors.NotFound(reason=u'resource record not found')
-
-        # check if it's worth to keep this entry in LDAP
-        if 'idnszone' not in entry_attrs['objectclass']:
-            # get a list of all meaningful record attributes
-            record_attrs = []
-            for (k, v) in entry_attrs.iteritems():
-                if k.endswith('record') and v:
-                    record_attrs.append(k)
-            # check if the list is empty
-            if not record_attrs:
-                # it's not
-                ldap.delete_entry(dn)
-                return dict(result={}, value=idnsname)
-
-        ldap.update_entry(dn, {attr: attr_value})
-        # get entry with updated attribute for output
-        (dn, entry_attrs) = ldap.get_entry(dn, ['idnsname', attr])
-        entry_attrs['dn'] = dn
-
-        return dict(result=entry_attrs, value=idnsname)
-
-    def output_for_cli(self, textui, result, zone, idnsname, type, data, **options):
-        output = '"%s %s %s" from zone "%s"' % (
-            idnsname, type, data, zone,
-        )
-        entry_attrs = result['result']
-
-        textui.print_name(self.name)
-        if entry_attrs:
-            textui.print_attribute('dn', entry_attrs['dn'])
-            del entry_attrs['dn']
-            textui.print_entry(entry_attrs)
-        textui.print_dashed('Deleted DNS resource record %s' % output)
-
-api.register(dns_del_rr)
-
-
-class dns_find_rr(Command):
+    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
+        for (a, v) in entry_attrs.iteritems():
+            if not isinstance(v, (list, tuple)):
+                v = [v]
+            for val in v:
+                try:
+                    old_entry_attrs[a].remove(val)
+                except (KeyError, ValueError):
+                    pass
+
+    def post_callback(self, keys, entry_attrs):
+        if not self.obj.is_pkey_zone_record(*keys):
+            for a in _record_attributes:
+                if a in entry_attrs and entry_attrs[a]:
+                    return
+            self.obj.methods.delentry(*keys)
+
+api.register(dnsrecord_del)
+
+
+class dnsrecord_show(LDAPRetrieve, dnsrecord_cmd_w_record_options):
     """
-    Search for DNS resource records.
+    Display DNS resource.
     """
-    takes_args = (
-        Str('zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
-        Str('criteria?',
-            cli_name='criteria',
-            label=_('Search criteria'),
-        ),
-    )
-
-    takes_options = (
-        Str('idnsname?',
-            cli_name='resource',
-            label=_('Resource name'),
-            default_from=lambda zone: zone.lower(),
-        ),
-        StrEnum('type?',
-            label=_('Record type'),
-            values=_record_types,
-        ),
-        Str('data?',
-            label=_('type-specific data'),
-        ),
-    )
-
-    has_output = standard_list_of_entries
-
-    def execute(self, zone, term, **options):
-        ldap = self.api.Backend.ldap2
-        if 'type' in options:
-            attr = ('%srecord' % options['type']).lower()
-        else:
-            attr = None
-
-        dns_container_exists(ldap)
+    def has_output_params(self):
+        for option in self.get_record_options():
+            yield option
 
-        # build base dn for search
-        base_dn = _get_zone_dn(ldap, zone)
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        if self.obj.is_pkey_zone_record(*keys):
+            entry_attrs[self.obj.primary_key.name] = [u'@']
+        return dn
 
-        # build search keywords
-        search_kw = {}
-        if 'data' in options:
-            if attr is not None:
-                # user is looking for a certain record type
-                search_kw[attr] = options['data']
-            else:
-                # search in all record types
-                for a in _record_default_attributes:
-                    search_kw[a] = term
-        if 'idnsname' in options:
-            idnsname = options['idnsname']
-            if idnsname == '@':
-                search_kw['idnsname'] = zone
-            else:
-                search_kw['idnsname'] = idnsname
+api.register(dnsrecord_show)
 
-        # build search filter
-        filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
-        if term:
-            search_kw = {}
-            for a in _record_default_attributes:
-                search_kw[a] = term
-            term_filter = ldap.make_filter(search_kw, exact=False)
-            filter = ldap.combine_filters((filter, term_filter), ldap.MATCH_ALL)
 
-        # select attributes we want to retrieve
-        if options.get('all', False):
-            attrs_list = ['*']
-        elif attr is not None:
-            attrs_list = [attr]
-        else:
-            attrs_list = _record_default_attributes
-
-        # get matching entries
-        try:
-            (entries, truncated) = ldap.find_entries(
-                filter, attrs_list, base_dn
-            )
-        except errors.NotFound:
-            (entries, truncated) = (tuple(), False)
-
-        # if the user is looking for a certain record type, don't display
-        # entries that do not contain it
-        if attr is not None:
-            related_entries = []
-            for e in entries:
-                entry_attrs = e[1]
-                if attr in entry_attrs:
-                    related_entries.append(e)
-            entries = related_entries
-
-        for e in entries:
-            e[1]['dn'] = e[0]
-        entries = tuple(e for (dn, e) in entries)
-
-        return dict(result=entries, count=len(entries), truncated=truncated)
-
-    def output_for_cli(self, textui, result, zone, term, **options):
-        entries = result['result']
-        truncated = result['truncated']
-
-        textui.print_name(self.name)
-        for entry_attrs in entries:
-            textui.print_attribute('dn', entry_attrs['dn'])
-            del entry_attrs['dn']
-            textui.print_entry(entry_attrs)
-            textui.print_plain('')
-        textui.print_count(
-            len(entries), '%i DNS resource record matched.',
-            '%i DNS resource records matched.'
-        )
-        if truncated:
-            textui.print_dashed('These results are truncated.', below=False)
-            textui.print_dashed(
-                'Please refine your search and try again.', above=False
-            )
-
-api.register(dns_find_rr)
-
-
-class dns_show_rr(Command):
+class dnsrecord_find(LDAPSearch, dnsrecord_cmd_w_record_options):
     """
-    Show existing DNS resource records.
+    Search for DNS resources.
     """
+    def get_options(self):
+        for option in super(dnsrecord_find, self).get_options():
+            yield option
+        for option in self.get_record_options():
+            yield option.clone(query=True)
+
+    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
+        record_attrs = self.record_options_2_entry(**options)
+        record_filter = ldap.make_filter(record_attrs, rules=ldap.MATCH_ALL)
+        filter = ldap.combine_filters(
+            (filter, record_filter), rules=ldap.MATCH_ALL
+        )
+        return (filter, base_dn, ldap.SCOPE_SUBTREE)
 
-    takes_args = (
-        Str('zone',
-            label=_('Zone name'),
-            normalizer=lambda value: value.lower(),
-        ),
-        Str('idnsname',
-            cli_name='resource',
-            label=_('Resource name'),
-            normalizer=lambda value: value.lower(),
-        ),
-    )
-
-    has_output = standard_entry
-
-    def execute(self, zone, idnsname, **options):
-        # shows all records associated with resource
-        ldap = self.api.Backend.ldap2
-
-        dns_container_exists(ldap)
-
-        # build entry DN
-        dn = _get_record_dn(ldap, zone, idnsname)
-
-        # select attributes we want to retrieve
-        if options.get('all', False):
-            attrs_list = ['*']
-        else:
-            attrs_list = _record_default_attributes
-
-        (dn, entry_attrs) = ldap.get_entry(dn, attrs_list)
-        entry_attrs['dn'] = dn
-
-        return dict(result=entry_attrs, value=idnsname)
-
-    def output_for_cli(self, textui, result, zone, idnsname, **options):
-        entry_attrs = result['result']
-
-        textui.print_name(self.name)
-        textui.print_attribute('dn', entry_attrs['dn'])
-        del entry_attrs['dn']
-        textui.print_entry(entry_attrs)
-
-api.register(dns_show_rr)
+    def post_callback(self, ldap, entries, truncated, *args, **options):
+        if entries:
+            zone_obj = self.api.Object[self.obj.parent_object]
+            zone_dn = zone_obj.get_dn(args[0])
+            if entries[0][0] == zone_dn:
+                entries[0][1][zone_obj.primary_key.name] = [u'@']
 
+api.register(dnsrecord_find)
 
 class dns_resolve(Command):
     """
diff --git a/ipalib/plugins/dns2.py b/ipalib/plugins/dns2.py
deleted file mode 100644
index d8e0ad657ef45085258cfec018647d83455eb94c..0000000000000000000000000000000000000000
--- a/ipalib/plugins/dns2.py
+++ /dev/null
@@ -1,623 +0,0 @@
-# Authors:
-#   Pavel Zuna <pzuna at redhat.com>
-#
-# Copyright (C) 2010  Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-Domain Name System (DNS)
-
-Manage DNS zone and resource records.
-
-EXAMPLES:
-
- Add new zone:
-   ipa dnszone-add example.com --name-server nameserver.example.com
-                               --admin-email admin at example.com
-
- Add second nameserver for example.com:
-   ipa dnsrecord-add example.com @ --ns-rec nameserver2.example.com
-
- Delete previously added nameserver from example.com:
-   ipa dnsrecord-del example.com @ --ns-rec nameserver2.example.com
-
- Add new A record for www.example.com: (random IP)
-   ipa dnsrecord-add example.com www --a-rec 80.142.15.2
-
- Add new PTR record for www.example.com
-   ipa dnsrecord 15.142.80.in-addr.arpa 2 --ptr-rec www.example.com.
-
- Show zone example.com:
-   ipa dnszone-show example.com
-
- Find zone with "example" in it's domain name:
-   ipa dnszone-find example
-
- Find records for resources with "www" in their name in zone example.com:
-   ipa dnsrecord-find example.com www
-
- Find A records with value 10.10.0.1 in zone example.com
-   ipa dnsrecord-find example.com --a-rec 10.10.0.1
-
- Show records for resource www in zone example.com
-   ipa dnsrecord-show example.com www
-
- Delete zone example.com with all resource records:
-   ipa dnszone-del example.com
-
- Resolve a host name to see if it exists (will add default IPA domain
- if one is not included):
-   ipa dns-resolve www.example.com
-   ipa dns-resolve www
-
-"""
-
-import netaddr
-import time
-
-from ipalib import api, errors, output
-from ipalib import Command
-from ipalib import Flag, Int, List, Str, StrEnum
-from ipalib.plugins.baseldap import *
-from ipalib import _, ngettext
-from ipapython import dnsclient
-
-# supported resource record types
-_record_types = (
-    u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV',
-    u'DNAME', u'DNSKEY', u'DS', u'HINFO', u'HIP', u'IPSECKEY', u'KEY', u'KX',
-    u'LOC', u'MD', u'MINFO', u'MX', u'NAPTR', u'NS', u'NSEC', u'NSEC3',
-    u'NSEC3PARAM', u'NXT', u'PTR', u'RRSIG', u'RP', u'SIG', u'SPF', u'SRV',
-    u'SSHFP', u'TA', u'TKEY', u'TSIG', u'TXT',
-)
-
-# attributes derived from record types
-_record_attributes = [str('%srecord' % t.lower()) for t in _record_types]
-
-# supported DNS classes, IN = internet, rest is almost never used
-_record_classes = (u'IN', u'CS', u'CH', u'HS')
-
-# normalizer for admin email
-def _rname_normalizer(value):
-    value = value.replace('@', '.')
-    if not value.endswith('.'):
-        value += '.'
-    return value
-
-def _create_zone_serial(**kwargs):
-    """Generate serial number for zones."""
-    return int('%s01' % time.strftime('%Y%d%m'))
-
-def _validate_ipaddr(ugettext, ipaddr):
-    try:
-        ip = netaddr.IPAddress(ipaddr)
-    except netaddr.AddrFormatError:
-        return u'invalid address format'
-    return None
-
-def _validate_ipnet(ugettext, ipnet):
-    try:
-        net = netaddr.IPNetwork(ipnet)
-    except (UnboundLocalError, ValueError):
-        return u'invalid format'
-    return None
-
-_record_validators = {
-    u'A': _validate_ipaddr,
-    u'AAAA': _validate_ipaddr,
-    u'APL': _validate_ipnet,
-}
-
-
-def dns_container_exists(ldap):
-    try:
-        ldap.get_entry(api.env.container_dns, [])
-    except errors.NotFound:
-        return False
-    return True
-
-class dnszone(LDAPObject):
-    """
-    DNS Zone, container for resource records.
-    """
-    container_dn = api.env.container_dns
-    object_name = 'DNS zone'
-    object_name_plural = 'DNS zones'
-    object_class = ['top', 'idnsrecord', 'idnszone']
-    default_attributes = [
-        'idnsname', 'idnszoneactive', 'idnssoamname', 'idnssoarname',
-        'idnssoaserial', 'idnssoarefresh', 'idnssoaretry', 'idnssoaexpire',
-        'idnssoaminimum'
-    ] + _record_attributes
-    label = _('DNS')
-
-    takes_params = (
-        Str('idnsname',
-            cli_name='name',
-            label=_('Zone name'),
-            doc=_('Zone name (FQDN)'),
-            normalizer=lambda value: value.lower(),
-            primary_key=True,
-        ),
-        Str('idnssoamname',
-            cli_name='name_server',
-            label=_('Authoritative name server'),
-            doc=_('Authoritative name server'),
-        ),
-        Str('idnssoarname',
-            cli_name='admin_email',
-            label=_('Administrator e-mail address'),
-            doc=_('Administrator e-mail address'),
-            default_from=lambda idnsname: 'root.%s' % idnsname,
-            normalizer=_rname_normalizer,
-        ),
-        Int('idnssoaserial?',
-            cli_name='serial',
-            label=_('SOA serial'),
-            doc=_('SOA record serial number'),
-            create_default=_create_zone_serial,
-            autofill=True,
-        ),
-        Int('idnssoarefresh?',
-            cli_name='refresh',
-            label=_('SOA refresh'),
-            doc=_('SOA record refresh time'),
-            default=3600,
-            autofill=True,
-        ),
-        Int('idnssoaretry?',
-            cli_name='retry',
-            label=_('SOA retry'),
-            doc=_('SOA record retry time'),
-            default=900,
-            autofill=True,
-        ),
-        Int('idnssoaexpire?',
-            cli_name='expire',
-            label=_('SOA expire'),
-            doc=_('SOA record expire time'),
-            default=1209600,
-            autofill=True,
-        ),
-        Int('idnssoaminimum?',
-            cli_name='minimum',
-            label=_('SOA minimum'),
-            doc=_('SOA record minimum value'),
-            default=3600,
-            autofill=True,
-        ),
-        Int('idnssoamaximum?',
-            cli_name='maximum',
-            label=_('SOA maximum'),
-            doc=_('SOA record maximum value'),
-        ),
-        Int('dnsttl?',
-            cli_name='ttl',
-            label=_('SOA time to live'),
-            doc=_('SOA record time to live'),
-        ),
-        StrEnum('dnsclass?',
-            cli_name='class',
-            label=_('SOA class'),
-            doc=_('SOA record class'),
-            values=_record_classes,
-        ),
-        Str('idnsupdatepolicy?',
-            cli_name='update_policy',
-            label=_('BIND update policy'),
-            doc=_('BIND update policy'),
-        ),
-        Flag('idnszoneactive?',
-            cli_name='zone_active',
-            label=_('Active zone'),
-            doc=_('Is zone active?'),
-            flags=['no_create', 'no_update'],
-            attribute=True,
-        ),
-        Flag('idnsallowdynupdate',
-            cli_name='allow_dynupdate',
-            label=_('Dynamic update'),
-            doc=_('Allow dynamic update?'),
-            attribute=True,
-        ),
-    )
-
-api.register(dnszone)
-
-
-class dnszone_add(LDAPCreate):
-    """
-    Create new DNS zone (SOA record).
-    """
-    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        if not dns_container_exists(self.api.Backend.ldap2):
-            raise errors.NotFound(reason=_('DNS is not configured'))
-
-        entry_attrs['idnszoneactive'] = 'TRUE'
-        entry_attrs['idnsallowdynupdate'] = str(
-            entry_attrs.get('idnsallowdynupdate', False)
-        ).upper()
-
-        nameserver = entry_attrs['idnssoamname']
-        if nameserver[-1] != '.':
-            nameserver += '.'
-        entry_attrs['nsrecord'] = nameserver
-        entry_attrs['idnssoamname'] = nameserver
-        return dn
-
-api.register(dnszone_add)
-
-
-class dnszone_del(LDAPDelete):
-    """
-    Delete DNS zone (SOA record).
-    """
-
-api.register(dnszone_del)
-
-
-class dnszone_mod(LDAPUpdate):
-    """
-    Modify DNS zone (SOA record).
-    """
-    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        entry_attrs['idnsallowdynupdate'] = str(
-            entry_attrs.get('idnsallowdynupdate', False)
-        ).upper()
-        return dn
-
-api.register(dnszone_mod)
-
-
-class dnszone_find(LDAPSearch):
-    """
-    Search for DNS zones (SOA records).
-    """
-
-api.register(dnszone_find)
-
-
-class dnszone_show(LDAPRetrieve):
-    """
-    Display information about a DNS zone (SOA record).
-    """
-
-api.register(dnszone_show)
-
-
-class dnszone_disable(LDAPQuery):
-    """
-    Disable DNS Zone.
-    """
-    has_output = output.standard_value
-    msg_summary = _('Disabled DNS zone "%(value)s"')
-
-    def execute(self, *keys, **options):
-        ldap = self.obj.backend
-
-        dn = self.obj.get_dn(*keys, **options)
-
-        try:
-            ldap.update_entry(dn, {'idnszoneactive': 'FALSE'})
-        except errors.EmptyModlist:
-            pass
-
-        return dict(result=True, value=keys[-1])
-
-api.register(dnszone_disable)
-
-
-class dnszone_enable(LDAPQuery):
-    """
-    Enable DNS Zone.
-    """
-    has_output = output.standard_value
-    msg_summary = _('Enabled DNS zone "%(value)s"')
-
-    def execute(self, *keys, **options):
-        ldap = self.obj.backend
-
-        dn = self.obj.get_dn(*keys, **options)
-
-        try:
-            ldap.update_entry(dn, {'idnszoneactive': 'TRUE'})
-        except errors.EmptyModlist:
-            pass
-
-        return dict(result=True, value=keys[-1])
-
-api.register(dnszone_enable)
-
-
-class dnsrecord(LDAPObject):
-    """
-    DNS record.
-    """
-    parent_object = 'dnszone'
-    container_dn = api.env.container_dns
-    object_name = 'DNS resource record'
-    object_name_plural = 'DNS resource records'
-    object_class = ['top', 'idnsrecord']
-    default_attributes = _record_attributes + ['idnsname']
-
-    label = _('DNS resource record')
-
-    takes_params = (
-        Str('idnsname',
-            cli_name='name',
-            label=_('Record name'),
-            doc=_('Record name'),
-            primary_key=True,
-        ),
-        Int('dnsttl?',
-            cli_name='ttl',
-            label=_('Time to live'),
-            doc=_('Time to live'),
-        ),
-        StrEnum('dnsclass?',
-            cli_name='class',
-            label=_('Class'),
-            doc=_('DNS class'),
-            values=_record_classes,
-        ),
-    )
-
-    def is_pkey_zone_record(*keys):
-        idnsname = keys[-1]
-        if idnsname == '@' or idnsname == ('%s.' % keys[-2]):
-            return True
-        return False
-
-    def get_dn(self, *keys, **options):
-        if self.is_pkey_zone_record(*keys):
-            return self.api.Object[self.parent_object].get_dn(*keys[:-1], **options)
-        return super(dnsrecord, self).get_dn(*keys, **options)
-
-api.register(dnsrecord)
-
-
-class dnsrecord_cmd_w_record_options(Command):
-    """
-    Base class for DNS record commands with record options.
-    """
-    record_param_doc = 'comma-separated list of %s records'
-
-    def get_record_options(self):
-        for t in _record_types:
-            t = t.encode('utf-8')
-            doc = self.record_param_doc % t
-            validator = _record_validators.get(t)
-            if validator:
-                yield List(
-                    '%srecord?' % t.lower(), validator,
-                    cli_name='%s_rec' % t.lower(), doc=doc,
-                    label='%s record' % t, attribute=True
-                )
-            else:
-                yield List(
-                    '%srecord?' % t.lower(), cli_name='%s_rec' % t.lower(),
-                    doc=doc, label='%s record' % t, attribute=True
-                )
-
-    def record_options_2_entry(self, **options):
-        return dict((t, options.get(t, [])) for t in _record_attributes)
-
-
-class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options):
-    """
-    Base class for adding/removing records from DNS resource entries.
-    """
-    has_output = output.standard_entry
-
-    def get_options(self):
-        for option in super(dnsrecord_mod_record, self).get_options():
-            yield option
-        for option in self.get_record_options():
-            yield option
-
-    def execute(self, *keys, **options):
-        ldap = self.obj.backend
-
-        dn = self.obj.get_dn(*keys, **options)
-
-        entry_attrs = self.record_options_2_entry(**options)
-
-        try:
-            (dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys())
-        except errors.NotFound:
-            self.obj.handle_not_found(*keys)
-
-        self.update_old_entry_callback(entry_attrs, old_entry_attrs)
-
-        try:
-            ldap.update_entry(dn, old_entry_attrs)
-        except errors.EmptyModlist:
-            pass
-
-        if options.get('all', False):
-            attrs_list = ['*']
-        else:
-            attrs_list = list(
-                set(self.obj.default_attributes + entry_attrs.keys())
-            )
-
-        try:
-            (dn, entry_attrs) = ldap.get_entry(dn, attrs_list)
-        except errors.NotFound:
-            self.obj.handle_not_found(*keys)
-
-        if self.obj.is_pkey_zone_record(*keys):
-            entry_attrs[self.obj.primary_key.name] = [u'@']
-
-        self.post_callback(keys, entry_attrs)
-
-        return dict(result=entry_attrs, value=keys[-1])
-
-    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
-        pass
-
-    def post_callback(self, keys, entry_attrs):
-        pass
-
-
-class dnsrecord_add_record(dnsrecord_mod_record):
-    """
-    Add records to DNS resource.
-    """
-    INTERNAL = True
-
-    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
-        for (a, v) in entry_attrs.iteritems():
-            if not isinstance(v, (list, tuple)):
-                v = [v]
-            old_entry_attrs.setdefault(a, [])
-            old_entry_attrs[a] += v
-
-api.register(dnsrecord_add_record)
-
-
-class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options):
-    """
-    Add new DNS resource record.
-    """
-    def get_options(self):
-        for option in super(dnsrecord_add, self).get_options():
-            yield option
-        for option in self.get_record_options():
-            yield option
-
-    def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
-        if call_func.func_name == 'add_entry':
-            if isinstance(exc, errors.DuplicateEntry):
-                self.obj.methods.add_record(
-                    *keys, **self.record_options_2_entry(**options)
-                )
-                return
-        raise exc
-
-api.register(dnsrecord_add)
-
-
-class dnsrecord_delentry(LDAPDelete):
-    """
-    Delete DNS record entry.
-    """
-    INTERNAL = True
-
-api.register(dnsrecord_delentry)
-
-
-class dnsrecord_del(dnsrecord_mod_record):
-    """
-    Delete DNS resource record.
-    """
-    def update_old_entry_callback(self, entry_attrs, old_entry_attrs):
-        for (a, v) in entry_attrs.iteritems():
-            if not isinstance(v, (list, tuple)):
-                v = [v]
-            for val in v:
-                try:
-                    old_entry_attrs[a].remove(val)
-                except (KeyError, ValueError):
-                    pass
-
-    def post_callback(self, keys, entry_attrs):
-        if not self.obj.is_pkey_zone_record(*keys):
-            for a in _record_attributes:
-                if a in entry_attrs and entry_attrs[a]:
-                    return
-            self.obj.methods.delentry(*keys)
-
-api.register(dnsrecord_del)
-
-
-class dnsrecord_show(LDAPRetrieve, dnsrecord_cmd_w_record_options):
-    """
-    Display DNS resource.
-    """
-    def has_output_params(self):
-        for option in self.get_record_options():
-            yield option
-
-    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        if self.obj.is_pkey_zone_record(*keys):
-            entry_attrs[self.obj.primary_key.name] = [u'@']
-        return dn
-
-api.register(dnsrecord_show)
-
-
-class dnsrecord_find(LDAPSearch, dnsrecord_cmd_w_record_options):
-    """
-    Search for DNS resources.
-    """
-    def get_options(self):
-        for option in super(dnsrecord_find, self).get_options():
-            yield option
-        for option in self.get_record_options():
-            yield option.clone(query=True)
-
-    def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
-        record_attrs = self.record_options_2_entry(**options)
-        record_filter = ldap.make_filter(record_attrs, rules=ldap.MATCH_ALL)
-        filter = ldap.combine_filters(
-            (filter, record_filter), rules=ldap.MATCH_ALL
-        )
-        return (filter, base_dn, ldap.SCOPE_SUBTREE)
-
-    def post_callback(self, ldap, entries, truncated, *args, **options):
-        if entries:
-            zone_obj = self.api.Object[self.obj.parent_object]
-            zone_dn = zone_obj.get_dn(args[0])
-            if entries[0][0] == zone_dn:
-                entries[0][1][zone_obj.primary_key.name] = [u'@']
-
-api.register(dnsrecord_find)
-
-class dns_resolve(Command):
-    """
-    Resolve a host name in DNS
-    """
-    has_output = output.standard_value
-    msg_summary = _('Found \'%(value)s\'')
-
-    takes_args = (
-        Str('hostname',
-            label=_('Hostname'),
-        ),
-    )
-
-    def execute(self, *args, **options):
-        query=args[0]
-        if query.find(api.env.domain) == -1 and query.find('.') == -1:
-            query = '%s.%s.' % (query, api.env.domain)
-        if query[-1] != '.':
-            query = query + '.'
-        reca = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
-        rec6 = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
-        records = reca + rec6
-        found = False
-        for rec in records:
-            if rec.dns_type == dnsclient.DNS_T_A or \
-              rec.dns_type == dnsclient.DNS_T_AAAA:
-                found = True
-                break
-
-        if not found:
-            raise errors.NotFound(reason=_('Host \'%(host)s\' not found' % {'host':query}))
-
-        return dict(result=True, value=query)
-
-api.register(dns_resolve)
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index d60f63776714689e754c3f1c18e972e062304edc..8639ce5a07e1b0d8f3707633ba184a7bee3ad51c 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -84,7 +84,7 @@ from ipalib.plugins.service import normalize_certificate
 from ipalib.plugins.service import set_certificate_attrs
 from ipalib.plugins.service import make_pem, check_writable_file
 from ipalib.plugins.service import write_certificate
-from ipalib.plugins.dns2 import dns_container_exists, _record_types
+from ipalib.plugins.dns import dns_container_exists, _record_types
 from ipalib import _, ngettext
 from ipalib import x509
 from ipapython.ipautil import ipa_generate_password
-- 
1.7.3.4



More information about the Freeipa-devel mailing list