diff options
author | Martin Babinsky <mbabinsk@redhat.com> | 2015-05-04 18:33:44 +0200 |
---|---|---|
committer | Petr Vobornik <pvoborni@redhat.com> | 2015-05-07 13:54:30 +0200 |
commit | e2a42efe33d5e6cb08e1988f7253caf56eda11df (patch) | |
tree | 3f8a38ffa9d84bbc5d46d7053a289a5ef837a656 /ipaserver/install | |
parent | a1ccdc33dfc5de3480b9ad407f4a95d01258008d (diff) | |
download | freeipa-e2a42efe33d5e6cb08e1988f7253caf56eda11df.tar.gz freeipa-e2a42efe33d5e6cb08e1988f7253caf56eda11df.tar.xz freeipa-e2a42efe33d5e6cb08e1988f7253caf56eda11df.zip |
prevent duplicate IDs when setting up multiple replicas against single master
This patch forces replicas to use DELETE+ADD operations to increment
'nsDS5ReplicaId' in 'cn=replication,cn=etc,$SUFFIX' on master, and retry
multiple times in the case of conflict with another update. Thus when multiple
replicas are set-up against single master none of them will have duplicate ID.
https://fedorahosted.org/freeipa/ticket/4378
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
Diffstat (limited to 'ipaserver/install')
-rw-r--r-- | ipaserver/install/replication.py | 74 |
1 files changed, 50 insertions, 24 deletions
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py index 66764c22f..4c16dc225 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -21,6 +21,7 @@ import time import datetime import sys import os +from random import randint import ldap @@ -230,34 +231,59 @@ class ReplicationManager(object): # Ok, either the entry doesn't exist or the attribute isn't set # so get it from the other master - retval = -1 - dn = DN(('cn','replication'),('cn','etc'), self.suffix) - try: - replica = master_conn.get_entry(dn) - except errors.NotFound: - root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise - else: - id_values = replica.get('nsDS5ReplicaId') - if not id_values: - root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") + return self._get_and_update_id_from_master(master_conn) - # nsDS5ReplicaId is single-valued now, but historically it could - # contain multiple values, of which we need the highest. - # see bug: https://fedorahosted.org/freeipa/ticket/3394 - retval = max(int(v) for v in id_values) + def _get_and_update_id_from_master(self, master_conn, attempts=5): + """ + Fetch replica ID from remote master and update nsDS5ReplicaId attribute + on 'cn=replication,cn=etc,$SUFFIX' entry. Do it as MOD_DELETE+MOD_ADD + operations and retry when conflict occurs, e.g. due to simultaneous + update from another replica. + :param master_conn: LDAP connection to master + :param attempts: number of attempts to update nsDS5ReplicaId + :return: value of nsDS5ReplicaId before incrementation + """ + dn = DN(('cn','replication'),('cn','etc'), self.suffix) - # Now update the value on the master - mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaId', str(retval + 1))] + for a in range(1, attempts + 1): + try: + root_logger.debug('Fetching nsDS5ReplicaId from master ' + '[attempt %d/%d]', a, attempts) + replica = master_conn.get_entry(dn) + id_values = replica.get('nsDS5ReplicaId') + if not id_values: + root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") + # nsDS5ReplicaId is single-valued now, but historically it could + # contain multiple values, of which we need the highest. + # see bug: https://fedorahosted.org/freeipa/ticket/3394 + retval = max(int(v) for v in id_values) + + # Now update the value on the master + mod_list = [(ldap.MOD_DELETE, 'nsDS5ReplicaId', str(retval)), + (ldap.MOD_ADD, 'nsDS5ReplicaId', str(retval + 1))] + + master_conn.modify_s(dn, mod_list) + root_logger.debug('Successfully updated nsDS5ReplicaId.') + return retval - try: - master_conn.modify_s(dn, mod) - except Exception, e: - root_logger.debug("Problem updating nsDS5ReplicaID %s" % e) - raise + except errors.NotFound: + root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise + # these errors signal a conflict in updating replica ID. + # We then wait for a random time interval and try again + except (ldap.NO_SUCH_ATTRIBUTE, ldap.OBJECT_CLASS_VIOLATION) as e: + sleep_interval = randint(1, 5) + root_logger.debug("Update failed (%s). Conflicting operation?", + e) + time.sleep(sleep_interval) + # in case of other error we bail out + except ldap.LDAPError as e: + root_logger.debug("Problem updating nsDS5ReplicaID %s" % e) + raise - return retval + raise RuntimeError("Failed to update nsDS5ReplicaId in %d attempts" + % attempts) def get_agreement_filter(self, agreement_types=None, host=None): """ |