Finally managed to rewrite the DNS plugin again. Sorry, it took so long, we had training in the office and I also had a nasty bug in baseldap.py I couldn't find.

Anyway, this version has it all:
- changes we agreed on meeting, the "resource" abstraction is gone and we now only have zones and records = adding new record automatically updates and existing entry or creates it if it wasn't there and deleting the last record deletes the whole entry - all of it transparent to the user
- unit tests
- ipa help documentation

Fixes tickets:
#36
#450

I also closed bug #654412.

It has a new patch sequence number, because it depends on another patch with a higher number and didn't want to create forward dependencies.

Depends on my patches number:
35 (will repost if needed)
38 (posted a while ago on freeipa-devel)

Pavel
>From 9ff886618623abb7253956dc92e652361fe4076e Mon Sep 17 00:00:00 2001
From: Pavel Zuna <pz...@redhat.com>
Date: Mon, 8 Nov 2010 22:34:14 -0500
Subject: [PATCH 2/3] Add new version of DNS plugin: complete rework with baseldap + unit tests.

Ticket #36
Ticket #450
---
 ipa.spec.in                          |    1 +
 ipalib/plugins/dns2.py               |  584 ++++++++++++++++++++++++++++++++++
 tests/test_xmlrpc/test_dns_plugin.py |  341 ++++++++++++++++++++
 3 files changed, 926 insertions(+), 0 deletions(-)
 create mode 100644 ipalib/plugins/dns2.py
 create mode 100644 tests/test_xmlrpc/test_dns_plugin.py

diff --git a/ipa.spec.in b/ipa.spec.in
index 5a3ea2b..1225bb0 100644
--- a/ipa.spec.in
+++ b/ipa.spec.in
@@ -178,6 +178,7 @@ Requires: gnupg
 Requires: pyOpenSSL
 Requires: python-nss >= 0.9-8
 Requires: python-lxml
+Requires: python-netaddr
 
 %description python
 IPA is an integrated solution to provide centrally managed Identity (machine,
diff --git a/ipalib/plugins/dns2.py b/ipalib/plugins/dns2.py
new file mode 100644
index 0000000..2f72fec
--- /dev/null
+++ b/ipalib/plugins/dns2.py
@@ -0,0 +1,584 @@
+# Authors:
+#   Pavel Zuna <pz...@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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+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 ad...@example.com
+
+ edd 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,
+}
+
+
+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 zone')
+
+    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'),
+        ),
+    )
+
+    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)
+
+
+class dnszone_add(LDAPCreate):
+    """
+    Create new DNS zone (SOA record).
+    """
+    takes_options = (
+        Flag('idnsallowdynupdate',
+            cli_name='allow_dynupdate',
+            label=_('Dynamic update'),
+            doc=_('allow dynamic update?'),
+            attribute=True,
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj.check_container_exists()
+        entry_attrs['idnszoneactive'] = 'TRUE'
+        entry_attrs['idnsallowdynupdate'] = str(
+            entry_attrs.get('idnsallowdynupdate', False)
+        ).upper()
+        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).
+    """
+    takes_options = (
+        Flag('idnsallowdynupdate',
+            cli_name='allow_dynupdate',
+            label=_('Dynamic update'),
+            doc=_('allow dynamic update?'),
+            attribute=True,
+        ),
+    )
+
+    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)
+
diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py
new file mode 100644
index 0000000..1ceee98
--- /dev/null
+++ b/tests/test_xmlrpc/test_dns_plugin.py
@@ -0,0 +1,341 @@
+# Authors:
+#   Pavel Zuna <pz...@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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+Test the `ipalib/plugins/dns.py` module.
+"""
+
+from ipalib import api, errors
+from tests.test_xmlrpc import objectclasses
+from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid
+
+dnszone1 = u'dnszone.test'
+dnsres1 = u'testdnsres'
+
+class test_dns(Declarative):
+
+    cleanup_commands = [
+        ('dnszone_del', [dnszone1], {}),
+        ('dnsrecord_del', [dnszone1, dnsres1], {}),
+    ]
+
+    tests = [
+
+        dict(
+            desc='Try to retrieve non-existent zone %r' % dnszone1,
+            command=('dnszone_show', [dnszone1], {}),
+            expected=errors.NotFound(reason='DNS zone not found'),
+        ),
+
+
+        dict(
+            desc='Try to update non-existent zone %r' % dnszone1,
+            command=('dnszone_mod', [dnszone1], {'idnssoamname': u'foobar'}),
+            expected=errors.NotFound(reason='DNS zone not found'),
+        ),
+
+
+        dict(
+            desc='Try to delete non-existent zone %r' % dnszone1,
+            command=('dnszone_del', [dnszone1], {}),
+            expected=errors.NotFound(reason='DNS zone not found'),
+        ),
+
+
+        dict(
+            desc='Create zone %r' % dnszone1,
+            command=(
+                'dnszone_add', [dnszone1], {
+                    'idnssoamname': u'ns1.%s' % dnszone1,
+                    'idnssoarname': u'root.%s' % dnszone1,
+                }
+            ),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'TRUE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [fuzzy_digits],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                    'idnsallowdynupdate': [u'FALSE'],
+                    'objectclass': [u'top', u'idnsrecord', u'idnszone'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Try to create duplicate zone %r' % dnszone1,
+            command=(
+                'dnszone_add', [dnszone1], {
+                    'idnssoamname': u'ns1.%s' % dnszone1,
+                    'idnssoarname': u'root.%s' % dnszone1,
+                }
+            ),
+            expected=errors.DuplicateEntry(),
+        ),
+
+
+        dict(
+            desc='Retrieve zone %r' % dnszone1,
+            command=('dnszone_show', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'TRUE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [fuzzy_digits],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Update zone %r' % dnszone1,
+            command=('dnszone_mod', [dnszone1], {'idnssoarefresh': 5478}),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': {
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'TRUE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [u'5478'],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                    'idnsallowdynupdate': [u'FALSE'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Search for zones with name server %r' % (u'ns1.%s' % dnszone1),
+            command=('dnszone_find', [], {'idnssoamname': u'ns1.%s' % dnszone1}),
+            expected={
+                'summary': None,
+                'count': 1,
+                'truncated': False,
+                'result': [{
+                    'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'TRUE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [u'5478'],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                }],
+            },
+        ),
+
+
+        dict(
+            desc='Disable zone %r' % dnszone1,
+            command=('dnszone_disable', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': u'Disabled DNS zone "%s"' % dnszone1,
+                'result': True,
+            },
+        ),
+
+
+        dict(
+            desc='Check if zone %r is really disabled' % dnszone1,
+            command=('dnszone_show', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'FALSE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [fuzzy_digits],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Enable zone %r' % dnszone1,
+            command=('dnszone_enable', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': u'Enabled DNS zone "%s"' % dnszone1,
+                'result': True,
+            },
+        ),
+
+
+        dict(
+            desc='Check if zone %r is really enabled' % dnszone1,
+            command=('dnszone_show', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                    'idnsname': [dnszone1],
+                    'idnszoneactive': [u'TRUE'],
+                    'idnssoamname': [u'ns1.%s' % dnszone1],
+                    'idnssoarname': [u'root.%s.' % dnszone1],
+                    'idnssoaserial': [fuzzy_digits],
+                    'idnssoarefresh': [fuzzy_digits],
+                    'idnssoaretry': [fuzzy_digits],
+                    'idnssoaexpire': [fuzzy_digits],
+                    'idnssoaminimum': [fuzzy_digits],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Try to retrieve non-existent record %r in zone %r' % (dnsres1, dnszone1),
+            command=('dnsrecord_show', [dnszone1, dnsres1], {}),
+            expected=errors.NotFound(reason='DNS resource record not found'),
+        ),
+
+
+        dict(
+            desc='Try to delete non-existent record %r in zone %r' % (dnsres1, dnszone1),
+            command=('dnsrecord_del', [dnszone1, dnsres1], {}),
+            expected=errors.NotFound(reason='DNS resource record not found'),
+        ),
+
+
+        dict(
+            desc='Create record %r in zone %r' % (dnszone1, dnsres1),
+            command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'127.0.0.1'}),
+            expected={
+                'value': dnsres1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn),
+                    'idnsname': [dnsres1],
+                    'objectclass': [u'top', u'idnsrecord'],
+                    'arecord': [u'127.0.0.1'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Search for all records in zone %r' % dnszone1,
+            command=('dnsrecord_find', [dnszone1], {}),
+            expected={
+                'summary': None,
+                'count': 2,
+                'truncated': False,
+                'result': [
+                    {
+                        'dn': u'idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn),
+                        'idnsname': [u'@'],
+                    },
+                    {
+                        'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn),
+                        'idnsname': [dnsres1],
+                        'arecord': [u'127.0.0.1'],
+                    },
+                ],
+            },
+        ),
+
+
+        dict(
+            desc='Add A record to %r in zone %r' % (dnszone1, dnsres1),
+            command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'10.10.0.1'}),
+            expected={
+                'value': dnsres1,
+                'summary': None,
+                'result': {
+                    'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn),
+                    'idnsname': [dnsres1],
+                    'arecord': [u'127.0.0.1', u'10.10.0.1'],
+                    'objectclass': [u'top', u'idnsrecord'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Remove A record from %r in zone %r' % (dnszone1, dnsres1),
+            command=('dnsrecord_del', [dnszone1, dnsres1], {'arecord': u'127.0.0.1'}),
+            expected={
+                'value': dnsres1,
+                'summary': None,
+                'result': {
+                    'idnsname': [dnsres1],
+                    'arecord': [u'10.10.0.1'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Delete record %r in zone %r' % (dnsres1, dnszone1),
+            command=('dnsrecord_del', [dnszone1, dnsres1], {}),
+            expected={
+                'value': dnsres1,
+                'summary': None,
+                'result': {
+                    'idnsname': [dnsres1],
+                    'arecord': [u'10.10.0.1'],
+                }
+            },
+        ),
+
+
+        dict(
+            desc='Delete zone %r' % dnszone1,
+            command=('dnszone_del', [dnszone1], {}),
+            expected={
+                'value': dnszone1,
+                'summary': None,
+                'result': True,
+            },
+        ),
+
+    ]
+
-- 
1.7.1.1

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to