IPA 3.0 introduced range ID objects in replicated space which specify
a range of IDs assigned via DNA plugin. ipa-ldap-updater generates the
default ID range which should correspond with IDs assigned to IPA
users.

However, since correct range size is not known, we should at least
warn that a range with invalid size was created so that user can
amend it.


I created 2 new tickets to add further improve this area:

1) #2918: [doc] Upgrade procedure section should mention ipa-ldap-updater
2) #2919: Improve safety checks in range command


To test this patch, you can:
1) Install unpatched IPA server (and you may install replicas too) with custom
--idstart and --idmax options where difference is greater then 200000
2) Remove default range with range-del command (will be restored during upgrade)
3) Run RPM upgrade with RPMs built from patched sources - ERROR should now be
printed during update stating that a new range was created but its size is not
right

Martin
>From a61488b5fa77ed983c8de11d211ebb56d2337fee Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 11 Jul 2012 14:09:17 +0200
Subject: [PATCH 1/3] Add range-mod command

range plugin was missing range-mod command that could be used for
example to fix a size for a range generated during upgrades. The
range should be updated with a caution though, a misconfiguration
could break trusts.

iparangetype is now also handled better and filled in all commands
instead of just range-show. objectclass attribute is deleted only
when really needed now.
---
 API.txt                                |   19 +++++++++++++++
 VERSION                                |    2 +-
 ipalib/plugins/range.py                |   41 +++++++++++++++++++++++++++-----
 tests/test_xmlrpc/test_range_plugin.py |   23 ++++++++++++++++--
 4 files changed, 76 insertions(+), 9 deletions(-)

diff --git a/API.txt b/API.txt
index 54313404142129a863792c67b706262973a268d6..691a9c4dec69f1006e52eafd3a94e351750165b7 100644
--- a/API.txt
+++ b/API.txt
@@ -2411,6 +2411,25 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
 output: Output('count', <type 'int'>, None)
 output: Output('truncated', <type 'bool'>, None)
+command: range_mod
+args: 1,13,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Int('ipabaseid', attribute=True, autofill=False, cli_name='base_id', multivalue=False, required=False)
+option: Int('ipaidrangesize', attribute=True, autofill=False, cli_name='range_size', multivalue=False, required=False)
+option: Int('ipabaserid', attribute=True, autofill=False, cli_name='rid_base', multivalue=False, required=False)
+option: Int('ipasecondarybaserid', attribute=True, autofill=False, cli_name='secondary_rid_base', multivalue=False, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='dom_sid', multivalue=False, required=False)
+option: Str('iparangetype', attribute=True, autofill=False, cli_name='iparangetype', multivalue=False, required=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('value', <type 'unicode'>, None)
 command: range_show
 args: 1,4,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
diff --git a/VERSION b/VERSION
index 542dd5fcaa6627711cb59f93d1a7585f6a02241a..8d9efe6573bd148f2e74961c6ce7247f9bc3393d 100644
--- a/VERSION
+++ b/VERSION
@@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=39
+IPA_API_VERSION_MINOR=40
diff --git a/ipalib/plugins/range.py b/ipalib/plugins/range.py
index 4448aad818ea4c9065c387e11035e165d52e4328..39849b661e35cc9092221b3d2bf7d13fb27f506c 100644
--- a/ipalib/plugins/range.py
+++ b/ipalib/plugins/range.py
@@ -80,6 +80,16 @@ class range(LDAPObject):
         )
     )
 
+    def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False):
+        if not options.get('pkey_only', False):
+            if 'ipatrustedaddomainrange' in entry_attrs.get('objectclass', []):
+                entry_attrs['iparangetype'] = [unicode(_('Active Directory domain range'))]
+            else:
+                entry_attrs['iparangetype'] = [unicode(_(u'local domain range'))]
+        if not keep_objectclass:
+            if not options.get('all', False) or options.get('pkey_only', False):
+                entry_attrs.pop('objectclass', None)
+
 class range_add(LDAPCreate):
     __doc__ = _('Add new ID range.')
 
@@ -99,6 +109,10 @@ class range_add(LDAPCreate):
 
         return dn
 
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj.handle_iparangetype(entry_attrs, options, keep_objectclass=True)
+        return dn
+
 class range_del(LDAPDelete):
     __doc__ = _('Delete an ID range.')
 
@@ -114,8 +128,14 @@ class range_find(LDAPSearch):
     # Since all range types are stored within separate containers under
     # 'cn=ranges,cn=etc' search can be done on a one-level scope
     def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+        attrs_list.append('objectclass')
         return (filters, base_dn, ldap.SCOPE_ONELEVEL)
 
+    def post_callback(self, ldap, entries, truncated, *args, **options):
+        for dn,entry in entries:
+            self.obj.handle_iparangetype(entry, options)
+        return truncated
+
 class range_show(LDAPRetrieve):
     __doc__ = _('Display information about a range.')
 
@@ -124,16 +144,25 @@ class range_show(LDAPRetrieve):
         return dn
 
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        if 'ipatrustedaddomainrange' in entry_attrs['objectclass']:
-            entry_attrs['iparangetype']=(u'Active Directory domain range')
-        else:
-            entry_attrs['iparangetype']=(u'local domain range')
-        del entry_attrs['objectclass']
+        self.obj.handle_iparangetype(entry_attrs, options)
+        return dn
+
+class range_mod(LDAPUpdate):
+    __doc__ = _('Modify ID range.')
+
+    msg_summary = _('Modified ID range "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        attrs_list.append('objectclass')
+        return dn
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        self.obj.handle_iparangetype(entry_attrs, options)
         return dn
 
 api.register(range)
 api.register(range_add)
-#api.register(range_mod)
+api.register(range_mod)
 api.register(range_del)
 api.register(range_find)
 api.register(range_show)
diff --git a/tests/test_xmlrpc/test_range_plugin.py b/tests/test_xmlrpc/test_range_plugin.py
index 7c95cd57a4a2fdd91862a7659cb2aac19c5edfcc..76ffc58b724e50529b8617f7d2d96923342eb0d8 100644
--- a/tests/test_xmlrpc/test_range_plugin.py
+++ b/tests/test_xmlrpc/test_range_plugin.py
@@ -49,7 +49,8 @@ class test_range(Declarative):
                     ipabaseid=[u'900000'],
                     ipabaserid=[u'1000'],
                     ipasecondarybaserid=[u'20000'],
-                    ipaidrangesize=[u'99999']
+                    ipaidrangesize=[u'99999'],
+                    iparangetype=[u'local domain range'],
                 ),
                 value=testrange1,
                 summary=u'Added ID range "%s"' % (testrange1),
@@ -69,11 +70,29 @@ class test_range(Declarative):
                     ipabaserid=[u'1000'],
                     ipasecondarybaserid=[u'20000'],
                     ipaidrangesize=[u'99999'],
-                    iparangetype=u'local domain range',
+                    iparangetype=[u'local domain range'],
                 ),
                 value=testrange1,
                 summary=None,
             ),
         ),
 
+
+        dict(
+            desc='Modify range %r' % (testrange1),
+            command=('range_mod', [testrange1], dict(ipaidrangesize=90000)),
+            expected=dict(
+                result=dict(
+                    cn=[testrange1],
+                    ipabaseid=[u'900000'],
+                    ipabaserid=[u'1000'],
+                    ipasecondarybaserid=[u'20000'],
+                    ipaidrangesize=[u'90000'],
+                    iparangetype=[u'local domain range'],
+                ),
+                value=testrange1,
+                summary=u'Modified ID range "%s"' % (testrange1),
+            ),
+        ),
+
     ]
-- 
1.7.10.4

>From 78ce0c8d0b44c051cbb82e1fbb62bf114c0c39a8 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 11 Jul 2012 16:13:25 +0200
Subject: [PATCH 2/3] Warn user if an ID range with incorrect size was created

IPA 3.0 introduced range ID objects in replicated space which specify
a range of IDs assigned via DNA plugin. ipa-ldap-updater generates the
default ID range which should correspond with IDs assigned to IPA
users.

However, since correct range size is not known, we should at least
warn that a range with invalid size was created so that user can
amend it.

https://fedorahosted.org/freeipa/ticket/2892
---
 ipalib/constants.py                  |    2 ++
 ipaserver/install/plugins/adtrust.py |   59 ++++++++++++++++++++++++++++++----
 2 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/ipalib/constants.py b/ipalib/constants.py
index ad6d188692aa3de70cf29d5662d84672054ab4ef..f0f89a3b3ff51e06709809d4d606af96d13a7063 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -105,6 +105,8 @@ DEFAULT_CONFIG = (
     ('container_trusts', 'cn=trusts'),
     ('container_adtrusts', 'cn=ad,cn=trusts'),
     ('container_ranges', 'cn=ranges,cn=etc'),
+    ('container_dna', 'cn=dna,cn=ipa,cn=etc'),
+    ('container_dna_posix_ids', 'cn=posix-ids,cn=dna,cn=ipa,cn=etc'),
 
     # Ports, hosts, and URIs:
     # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py
index abd676a2bcbec57f59eb6710560281d945cf0cea..78c1fc10bd5028b3f861478ab1493d2d73c3cc56 100644
--- a/ipaserver/install/plugins/adtrust.py
+++ b/ipaserver/install/plugins/adtrust.py
@@ -23,6 +23,8 @@ from ipalib import api, errors
 from ipalib.dn import DN
 from ipapython.ipa_log_manager import *
 
+DEFAULT_ID_RANGE_SIZE = 200000
+
 class update_default_range(PostUpdate):
     """
     Create default ID range for upgraded servers.
@@ -46,18 +48,19 @@ class update_default_range(PostUpdate):
         try:
             (dn, admins_entry) = ldap.get_entry(dn, ['gidnumber'])
         except errors.NotFound:
-            root_logger.error("No local ID range and no admins group found. "
-                              "Cannot create default ID range")
+            root_logger.error("default_range: No local ID range and no admins "
+                              "group found. Cannot create default ID range")
             return (False, False, [])
 
-        base_id = admins_entry['gidnumber'][0]
-        id_range_size = 200000
+        id_range_base_id = admins_entry['gidnumber'][0]
+        id_range_name = '%s_id_range' % api.env.realm
+        id_range_size = DEFAULT_ID_RANGE_SIZE
 
         range_entry = ['objectclass:top',
                        'objectclass:ipaIDrange',
                        'objectclass:ipaDomainIDRange',
-                       'cn:%s_id_range' % api.env.realm,
-                       'ipabaseid:%s' % base_id,
+                       'cn:%s' % id_range_name,
+                       'ipabaseid:%s' % id_range_base_id,
                        'ipaidrangesize:%s' % id_range_size,
                       ]
 
@@ -69,6 +72,50 @@ class update_default_range(PostUpdate):
         range_entry = map(str, range_entry)
         updates[dn] = {'dn' : dn, 'default' : range_entry}
 
+        # Default range entry has a hard-coded range size to 200000 which is
+        # a default range size in ipa-server-install. This could cause issues
+        # if user did not use a default range, but rather defined an own,
+        # bigger range (option --idmax).
+        # We should make our best to check if this is the case and provide
+        # user with an information how to fix it.
+        dn = str(DN(api.env.container_dna_posix_ids, api.env.basedn))
+        search_filter = "objectclass=dnaSharedConfig"
+        attrs = ['dnaHostname', 'dnaRemainingValues']
+        try:
+            (entries, truncated) = ldap.find_entries(search_filter, attrs, dn)
+        except errors.NotFound:
+            root_logger.warning("default_range: no dnaSharedConfig object found. "
+                                "Cannot check default range size.")
+        else:
+            masters = set()
+            remaining_values_sum = 0
+            for entry_dn, entry in entries:
+                hostname = entry.get('dnahostname', [None])[0]
+                if hostname is None or hostname in masters:
+                    continue
+                remaining_values = entry.get('dnaremainingvalues', [''])[0]
+                try:
+                    remaining_values = int(remaining_values)
+                except ValueError:
+                    root_logger.warning("default_range: could not parse "
+                        "remaining values from '%s'", remaining_values)
+                    continue
+                else:
+                    remaining_values_sum += remaining_values
+
+                masters.add(hostname)
+
+            if remaining_values_sum > DEFAULT_ID_RANGE_SIZE:
+                msg = ['could not verify default ID range size',
+                       'Please use the following command to set correct ID range size',
+                       '  $ ipa range-mod %s --range-size=RANGE_SIZE"' % id_range_name,
+                       'RANGE_SIZE may be computed from --idstart and --idmax options '
+                       'used during IPA server installation:',
+                       '  RANGE_SIZE = (--idmax) - (--idstart) + 1'
+                      ]
+
+                root_logger.error("default_range: %s", "\n".join(msg))
+
         return (False, True, [updates])
 
 api.register(update_default_range)
-- 
1.7.10.4

>From fb563a604fda390e0d2d9c22996d34a8440bbdfb Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 11 Jul 2012 16:22:13 +0200
Subject: [PATCH 3/3] Print ipa-ldap-updater errors during RPM upgrade

ipa-ldap-updater does a lot of essential LDAP changes and if it
fails, user may be surprised after the upgrade why things does not
work.

Modify ipa-ldap-updater to print ERROR logging messages by default
and modify RPM upgrade scriptlet to show these errors to user.

https://fedorahosted.org/freeipa/ticket/2892
---
 freeipa.spec.in                |    2 +-
 install/tools/ipa-ldap-updater |    4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index deeb3341b9379a2cc82e50ca37889287c4c74813..7106310915c8a4e52a009036f7152a38a4c5f18d 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -465,7 +465,7 @@ fi
 %posttrans server
 # This must be run in posttrans so that updates from previous
 # execution that may no longer be shipped are not applied.
-/usr/sbin/ipa-ldap-updater --upgrade >/dev/null 2>&1 || :
+/usr/sbin/ipa-ldap-updater --upgrade >/dev/null || :
 
 %preun server
 if [ $1 = 0 ]; then
diff --git a/install/tools/ipa-ldap-updater b/install/tools/ipa-ldap-updater
index 197b840b07867269340f56e32989820d8af59ae1..e93cccd9679287fe5202d1071d3ee53f0ad22823 100755
--- a/install/tools/ipa-ldap-updater
+++ b/install/tools/ipa-ldap-updater
@@ -116,9 +116,9 @@ def main():
                 sys.exit("\nDirectory Manager password required")
 
     if options.upgrade:
-        standard_logging_setup('/var/log/ipaupgrade.log', verbose=True, debug=options.debug, filemode='a')
+        standard_logging_setup('/var/log/ipaupgrade.log', debug=options.debug, filemode='a')
     else:
-        standard_logging_setup(None, verbose=True, debug=options.debug)
+        standard_logging_setup(None, debug=options.debug)
 
     cfg = dict (
         in_server=True,
-- 
1.7.10.4

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

Reply via email to