The patchset ensure, the upgrade order will respect ordering of entries in *.update files.

Required for: https://fedorahosted.org/freeipa/ticket/4904

Patch 205 also fixes https://fedorahosted.org/freeipa/ticket/3560

Required patch mbasti-0203

Patches attached.

--
Martin Basti

From 9dd32e80f37b852feb980fd4ef2ec7c082ffc1a5 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 26 Feb 2015 12:01:19 +0100
Subject: [PATCH 2/5] Server Upgrade: do not sort updates by DN

Ticket: https://fedorahosted.org/freeipa/ticket/4904
---
 ipaserver/install/ldapupdate.py | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 53d5407d5e8a15abe13f2f6d8b3df74ca100ea5a..e8516ff86a951f828c4213f8e70db613b99ed8c4 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -784,22 +784,11 @@ class LDAPUpdate:
             raise RuntimeError("Offline updates are not supported.")
 
     def _run_updates(self, all_updates):
-        # For adds and updates we want to apply updates from shortest
-        # to greatest length of the DN.
-        # For deletes we want the reverse
-        def update_sort_key(dn_update):
-            dn, update = dn_update
-            assert isinstance(dn, DN)
-            return len(dn)
 
-        sorted_updates = sorted(all_updates.iteritems(), key=update_sort_key)
-
-        for dn, update in sorted_updates:
+        for dn, update in all_updates.iteritems():
             self._update_record(update)
 
-        # Now run the deletes in reversed order
-        sorted_updates.reverse()
-        for dn, update in sorted_updates:
+        for dn, update in all_updates.iteritems():
             self._delete_record(update)
 
     def update(self, files, ordered=False):
-- 
2.1.0

From 689caca4d322a18108756b530522be625f5b0964 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 5 Mar 2015 16:56:02 +0100
Subject: [PATCH 3/5] Server Upgrade: Upgrade one file per time

* Files are sorted alphabetically, no numbering required anymore
* One file updated per time

Ticket: https://fedorahosted.org/freeipa/ticket/3560
---
 ipaserver/install/ldapupdate.py | 54 ++++++++++++++---------------------------
 1 file changed, 18 insertions(+), 36 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index e8516ff86a951f828c4213f8e70db613b99ed8c4..3b4aa58d9e84006510c23e4aa7d52a84c205f79c 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -793,44 +793,27 @@ class LDAPUpdate:
 
     def update(self, files, ordered=False):
         """Execute the update. files is a list of the update files to use.
+        :param ordered: Update files are executed in alphabetical order
 
-           If ordered is True then the updates the file must be of the form
-           ##-name.update where ## is an integer between 10 and 89. The
-           changes are applied to LDAP at the end of each value divisible
-           by 10, so after 20, 30, etc.
-
-           returns True if anything was changed, otherwise False
+        returns True if anything was changed, otherwise False
         """
 
-        pat = re.compile(r'(\d+)-.*\.update')
         all_updates = {}
-        r = 20
-        if self.plugins:
-            self.info('PRE_UPDATE')
-            updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run)
-            self.merge_updates(all_updates, updates)
         try:
             self.create_connection()
-            if ordered and all_updates:
+            if self.plugins:
+                self.info('PRE_UPDATE')
+                updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run)
+                self.merge_updates(all_updates, updates)
                 # flush out PRE_UPDATE plugin updates before we begin
                 self._run_updates(all_updates)
                 all_updates = {}
 
-            for f in files:
-                name = os.path.basename(f)
-                if ordered:
-                    m = pat.match(name)
-                    if not m:
-                        raise RuntimeError("Filename does not match format #-name.update: %s" % f)
-                    index = int(m.group(1))
-                    if index < 10 or index > 90:
-                        raise RuntimeError("Index not legal range: %d" % index)
-
-                    if index >= r:
-                        self._run_updates(all_updates)
-                        all_updates = {}
-                        r += 10
+            upgrade_files = files
+            if ordered:
+                upgrade_files = sorted(files)
 
+            for f in upgrade_files:
                 try:
                     self.info("Parsing update file '%s'" % f)
                     data = self.read_file(f)
@@ -839,17 +822,16 @@ class LDAPUpdate:
                     sys.exit(e)
 
                 self.parse_update_file(f, data, all_updates)
+                self._run_updates(all_updates)
+                all_updates = {}
 
-            self._run_updates(all_updates)
+            if self.plugins:
+                self.info('POST_UPDATE')
+                updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run)
+                self.merge_updates(all_updates, updates)
+                self._run_updates(all_updates)
         finally:
-            if self.conn: self.conn.unbind()
-
-        if self.plugins:
-            self.info('POST_UPDATE')
-            all_updates = {}
-            updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run)
-            self.merge_updates(all_updates, updates)
-            self._run_updates(all_updates)
+            self.close_connection()
 
         return self.modified
 
-- 
2.1.0

From 4545a8ebdbe2b5e36682328097ff47c12c311b1a Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 5 Mar 2015 18:42:03 +0100
Subject: [PATCH 4/5] Server Upgrade: Set modified to false, before each update

Variable self.modified should be set to false before each run of update

Ticket: https://fedorahosted.org/freeipa/ticket/3560
---
 ipaserver/install/ldapupdate.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 3b4aa58d9e84006510c23e4aa7d52a84c205f79c..92b6d56ae51537b8020c3a162be5593364d31aca 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -797,7 +797,7 @@ class LDAPUpdate:
 
         returns True if anything was changed, otherwise False
         """
-
+        self.modified = False
         all_updates = {}
         try:
             self.create_connection()
@@ -841,6 +841,7 @@ class LDAPUpdate:
         Apply updates internally as opposed to from a file.
         updates is a dictionary containing the updates
         """
+        self.modified = False
         if not self.conn:
             self.create_connection()
 
-- 
2.1.0

From 4966763205c5990dc34188efb1fe59261fa7e851 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Fri, 6 Mar 2015 15:14:17 +0100
Subject: [PATCH 5/5] Server Upgrade: Update entries in order specified in file

Dictionary replaced with list. Particular upgrades are
executed in the same order as they are specified in update
a file.

Different updates for the smae cn, are not merged into one upgrade

https://fedorahosted.org/freeipa/ticket/4904
---
 ipaserver/install/ldapupdate.py                | 75 +++++---------------------
 ipaserver/install/plugins/adtrust.py           | 10 ++--
 ipaserver/install/plugins/ca_renewal_master.py |  2 -
 ipaserver/install/plugins/dns.py               |  6 +--
 ipaserver/install/plugins/rename_managed.py    |  7 +--
 ipaserver/install/plugins/update_passsync.py   |  3 +-
 ipaserver/install/plugins/update_uniqueness.py |  2 +-
 ipaserver/install/plugins/updateclient.py      |  5 +-
 ipaserver/install/plugins/upload_cacrt.py      |  8 +--
 9 files changed, 30 insertions(+), 88 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 92b6d56ae51537b8020c3a162be5593364d31aca..3e4fc3f7a1de52f7019e674bb04000a082e53ea7 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -137,22 +137,20 @@ class LDAPUpdate:
             4: 'cn=bob,ou=people,dc=example,dc=com',
         }
 
-        all_updates = {
-        'dn': 'cn=config,dc=example,dc=com':
+        all_updates = [
             {
                 'dn': 'cn=config,dc=example,dc=com',
                 'default': ['attr1':default1'],
                 'updates': ['action:attr1:value1',
                             'action:attr2:value2]
             },
-        'dn': 'cn=bob,ou=people,dc=example,dc=com':
             {
                 'dn': 'cn=bob,ou=people,dc=example,dc=com',
                 'default': ['attr3':default3'],
                 'updates': ['action:attr3:value3',
                             'action:attr4:value4],
             }
-        }
+        ]
 
         The default and update lists are "dispositions"
 
@@ -279,49 +277,6 @@ class LDAPUpdate:
         if fd != sys.stdin: fd.close()
         return text
 
-    def _combine_updates(self, all_updates, update):
-        'Combine a new update with the list of total updates'
-        dn = update.get('dn')
-        assert isinstance(dn, DN)
-
-        if not all_updates.get(dn):
-            all_updates[dn] = update
-            return
-
-        existing_update = all_updates[dn]
-        if 'default' in update:
-            disposition_list = existing_update.setdefault('default', [])
-            disposition_list.extend(update['default'])
-        elif 'updates' in update:
-            disposition_list = existing_update.setdefault('updates', [])
-            disposition_list.extend(update['updates'])
-        else:
-            self.debug("Unknown key in updates %s" % update.keys())
-
-    def merge_updates(self, all_updates, updates):
-        '''
-        Add the new_update dict to the all_updates dict.  If an entry
-        in the new_update already has an entry in all_updates merge
-        the two entries sensibly assuming the new entries take
-        precedence. Otherwise just add the new entry.
-        '''
-
-        for new_update in updates:
-            for new_dn, new_entry in new_update.iteritems():
-                existing_entry = all_updates.get(new_dn)
-                if existing_entry:
-                    # If the existing entry is marked for deletion but the
-                    # new entry is not also a delete then clear the delete
-                    # flag otherwise the newer update will be lost.
-                    if existing_entry.has_key('deleteentry') and not new_entry.has_key('deleteentry'):
-                        self.warning("ldapupdate: entry '%s' previously marked for deletion but" +
-                                     " this subsequent update reestablishes it: %s", new_dn, new_entry)
-                        del existing_entry['deleteentry']
-                    existing_entry.update(new_entry)
-                else:
-                    all_updates[new_dn] = new_entry
-
-
     def parse_update_file(self, data_source_name, source_data, all_updates):
         """Parse the update file into a dictonary of lists and apply the update
            for each DN in the file."""
@@ -380,11 +335,12 @@ class LDAPUpdate:
 
         def emit_update(update):
             '''
-            When processing a dn is completed emit the update by merging it into
-            the set of all updates.
+            When processing a dn is completed emit the update by appending it
+            into list of all updates
             '''
-
-            self._combine_updates(all_updates, update)
+            dn = update.get('dn')
+            assert isinstance(dn, DN)
+            all_updates.append(update)
 
         # Iterate over source input lines
         for source_line in source_data:
@@ -421,7 +377,6 @@ class LDAPUpdate:
                     continue
                 else:
                     emit_item(logical_line)
-                    logical_line = ''
                     logical_line = source_line
 
         if dn is not None:
@@ -784,11 +739,10 @@ class LDAPUpdate:
             raise RuntimeError("Offline updates are not supported.")
 
     def _run_updates(self, all_updates):
-
-        for dn, update in all_updates.iteritems():
+        for update in all_updates:
             self._update_record(update)
 
-        for dn, update in all_updates.iteritems():
+        for update in all_updates:
             self._delete_record(update)
 
     def update(self, files, ordered=False):
@@ -798,16 +752,14 @@ class LDAPUpdate:
         returns True if anything was changed, otherwise False
         """
         self.modified = False
-        all_updates = {}
+        all_updates = []
         try:
             self.create_connection()
             if self.plugins:
                 self.info('PRE_UPDATE')
                 updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run)
-                self.merge_updates(all_updates, updates)
                 # flush out PRE_UPDATE plugin updates before we begin
-                self._run_updates(all_updates)
-                all_updates = {}
+                self._run_updates(updates)
 
             upgrade_files = files
             if ordered:
@@ -823,13 +775,12 @@ class LDAPUpdate:
 
                 self.parse_update_file(f, data, all_updates)
                 self._run_updates(all_updates)
-                all_updates = {}
+                all_updates = []
 
             if self.plugins:
                 self.info('POST_UPDATE')
                 updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run)
-                self.merge_updates(all_updates, updates)
-                self._run_updates(all_updates)
+                self._run_updates(updates)
         finally:
             self.close_connection()
 
diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py
index 1290278cf5e5a8d25638e20c95690360d5837eac..dbec429aa4890d0a3eeef1ea3ed4524ecbcb39e8 100644
--- a/ipaserver/install/plugins/adtrust.py
+++ b/ipaserver/install/plugins/adtrust.py
@@ -65,11 +65,10 @@ class update_default_range(PostUpdate):
                        'iparangetype:ipa-local',
                       ]
 
-        updates = {}
         dn = DN(('cn', '%s_id_range' % api.env.realm),
                 api.env.container_ranges, api.env.basedn)
 
-        updates[dn] = {'dn': dn, 'default': range_entry}
+        update = {'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
@@ -115,7 +114,7 @@ class update_default_range(PostUpdate):
 
                 root_logger.error("default_range: %s", "\n".join(msg))
 
-        return (False, True, [updates])
+        return (False, True, [update])
 
 
 class update_default_trust_view(PostUpdate):
@@ -156,13 +155,12 @@ class update_default_trust_view(PostUpdate):
         # We have a server with AD trust support without Default Trust View.
         # Create the Default Trust View entry.
 
-        updates = {}
-        updates[default_trust_view_dn] = {
+        update = {
             'dn': default_trust_view_dn,
             'default': default_trust_view_entry
         }
 
-        return (False, True, [updates])
+        return (False, True, [update])
 
 api.register(update_default_range)
 api.register(update_default_trust_view)
diff --git a/ipaserver/install/plugins/ca_renewal_master.py b/ipaserver/install/plugins/ca_renewal_master.py
index e246639103ed55997af5d283d996579102d2d265..b0fb527a3e02364bb3593ff3068ae510c4ff051e 100644
--- a/ipaserver/install/plugins/ca_renewal_master.py
+++ b/ipaserver/install/plugins/ca_renewal_master.py
@@ -98,10 +98,8 @@ class update_ca_renewal_master(PostUpdate):
 
         dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
         update = {
-            dn: {
                 'dn': dn,
                 'updates': ['add:ipaConfigString: caRenewalMaster'],
-            },
         }
 
         return (False, True, [update])
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index 5f40be4926a7bcaf2853ff764761e18e54e51c7c..f562978bcbcc02321c0e9a668af88b4f596f8556 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -133,13 +133,11 @@ class update_dns_limits(PostUpdate):
         for limit in self.limit_attributes:
             limit_updates.append('only:%s:%s' % (limit, self.limit_value))
 
-        dnsupdates = {}
-        dnsupdates[dns_service_dn] = {'dn': dns_service_dn,
-                                      'updates': limit_updates}
+        dnsupdate = {'dn': dns_service_dn, 'updates': limit_updates}
         root_logger.debug("DNS: limits for service %s will be updated" % dns_service_dn)
 
 
-        return (False, True, [dnsupdates])
+        return (False, True, [dnsupdate])
 
 api.register(update_dns_limits)
 
diff --git a/ipaserver/install/plugins/rename_managed.py b/ipaserver/install/plugins/rename_managed.py
index 13e6dae5d226426bb0867e5943b05722fc04aa98..adb814c1799ebbdb57118acf1ba6a52550f2f818 100644
--- a/ipaserver/install/plugins/rename_managed.py
+++ b/ipaserver/install/plugins/rename_managed.py
@@ -98,7 +98,8 @@ class GenerateUpdateMixin(object):
                     old_update = {'dn': entry.dn, 'deleteentry': None}
 
                     # Add the delete and replacement updates to the list of all updates
-                    update_list.append({entry.dn: old_update, new_dn: new_update})
+                    update_list.append(old_update)
+                    update_list.append(new_update)
 
             else:
                 # Update the template dn by replacing the old containter with the new container
@@ -125,11 +126,11 @@ class GenerateUpdateMixin(object):
                               'default': entry_to_update(entry)}
 
                 # Add the replacement update to the collection of all updates
-                update_list.append({new_dn: new_update})
+                update_list.append(new_update)
 
         if len(update_list) > 0:
             restart = True
-            update_list.sort(reverse=True)
+            update_list.sort(reverse=True, key=lambda x: x['dn'])
 
         return (restart, update_list)
 
diff --git a/ipaserver/install/plugins/update_passsync.py b/ipaserver/install/plugins/update_passsync.py
index d6595a06f4deb62b853d716012a8c594c6a76451..e0d2fc01cbc66af24cb908b80d3a031903fdc463 100644
--- a/ipaserver/install/plugins/update_passsync.py
+++ b/ipaserver/install/plugins/update_passsync.py
@@ -70,9 +70,8 @@ class update_passync_privilege_update(PostUpdate):
 
         update = {'dn': passsync_privilege_dn,
                   'updates': ["add:member:'%s'" % passsync_dn]}
-        updates = {passsync_privilege_dn: update}
 
         sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
-        return (False, True, [updates])
+        return (False, True, [update])
 
 api.register(update_passync_privilege_update)
diff --git a/ipaserver/install/plugins/update_uniqueness.py b/ipaserver/install/plugins/update_uniqueness.py
index 3017d5ac13b223a80ad1171d5adcde8fb4343562..e0ee150a7337a052731a0ed26eb64a4a8c01fb90 100644
--- a/ipaserver/install/plugins/update_uniqueness.py
+++ b/ipaserver/install/plugins/update_uniqueness.py
@@ -218,7 +218,7 @@ class update_uniqueness_plugins_to_new_syntax(PreUpdate):
                                   "plugin %s (%s)",
                                   entry.dn, e)
 
-            update_list.append({entry.dn: update})
+            update_list.append(update)
 
         return False, True, update_list
 
diff --git a/ipaserver/install/plugins/updateclient.py b/ipaserver/install/plugins/updateclient.py
index 8f5c5b5fdbc2b7bfec8be342ee267425c93b47cf..85ee3f8a0d17710f99a4d18c95fa3c4f93cba672 100644
--- a/ipaserver/install/plugins/updateclient.py
+++ b/ipaserver/install/plugins/updateclient.py
@@ -128,10 +128,7 @@ class updateclient(backend.Executioner):
                 self.restart(dm_password, live_run)
 
             if apply_now:
-                updates = {}
-                for entry in res:
-                    updates.update(entry)
-                ld.update_from_dict(updates)
+                ld.update_from_dict(res)
             elif res:
                 result.extend(res)
 
diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py
index 66270ae7613e935fc8df4bc90aa5001296e1c06d..dcdefee0536eb2e80fbc34823526646aee1bd61c 100644
--- a/ipaserver/install/plugins/upload_cacrt.py
+++ b/ipaserver/install/plugins/upload_cacrt.py
@@ -45,7 +45,7 @@ class update_upload_cacrt(PostUpdate):
                 if ca_chain:
                     ca_nickname = ca_chain[-1]
 
-        updates = {}
+        updates = []
 
         for nickname, trust_flags in db.list_certs():
             if 'u' in trust_flags:
@@ -64,7 +64,7 @@ class update_upload_cacrt(PostUpdate):
                 if ca_enabled:
                     entry.append('ipaConfigString:ipaCA')
                 entry.append('ipaConfigString:compatCA')
-            updates[dn] = {'dn': dn, 'default': entry}
+            updates.append({'dn': dn, 'default': entry})
 
         if ca_cert:
             dn = DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'),
@@ -74,9 +74,9 @@ class update_upload_cacrt(PostUpdate):
                      'cn:CAcert',
                      'cACertificate;binary:%s' % ca_cert,
                     ]
-            updates[dn] = {'dn': dn, 'default': entry}
+            updates.append({'dn': dn, 'default': entry})
 
-        return (False, True, [updates])
+        return (False, True, updates)
 
     def _make_entry(self, cert, nickname, trust_flags):
         dn = DN(('cn', nickname), ('cn', 'certificates'), ('cn', 'ipa'),
-- 
2.1.0

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

Reply via email to