|
|
f65af0 |
From 0598ed6e1c48c6600774c53065716aad759da5e7 Mon Sep 17 00:00:00 2001
|
|
|
979ee0 |
From: Christian Heimes <cheimes@redhat.com>
|
|
|
979ee0 |
Date: Tue, 10 Jul 2018 14:03:28 +0200
|
|
|
979ee0 |
Subject: [PATCH] Handle races in replica config
|
|
|
979ee0 |
|
|
|
979ee0 |
When multiple replicas are installed in parallel, two replicas may try
|
|
|
979ee0 |
to create the cn=replica entry at the same time. This leads to a
|
|
|
979ee0 |
conflict on one of the replicas. replica_config() and
|
|
|
979ee0 |
ensure_replication_managers() now handle conflicts.
|
|
|
979ee0 |
|
|
|
979ee0 |
ipaldap now maps TYPE_OR_VALUE_EXISTS to DuplicateEntry(). The type or
|
|
|
979ee0 |
value exists exception is raised, when an attribute value or type is
|
|
|
979ee0 |
already set.
|
|
|
979ee0 |
|
|
|
979ee0 |
Fixes: https://pagure.io/freeipa/issue/7566
|
|
|
979ee0 |
Signed-off-by: Christian Heimes <cheimes@redhat.com>
|
|
|
979ee0 |
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
|
|
|
979ee0 |
---
|
|
|
979ee0 |
ipapython/ipaldap.py | 5 ++
|
|
|
f65af0 |
ipaserver/install/replication.py | 123 ++++++++++++++++++-------------
|
|
|
979ee0 |
ipaserver/plugins/ldap2.py | 3 +-
|
|
|
f65af0 |
3 files changed, 79 insertions(+), 52 deletions(-)
|
|
|
979ee0 |
|
|
|
979ee0 |
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
|
|
|
f65af0 |
index dcac32c31c670753b83f99eac7f6483fbad2b14d..53fdf4967868961effea7f3f64dfb3c0edfc75f3 100644
|
|
|
979ee0 |
--- a/ipapython/ipaldap.py
|
|
|
979ee0 |
+++ b/ipapython/ipaldap.py
|
|
|
f65af0 |
@@ -1016,7 +1016,12 @@ class LDAPClient(object):
|
|
|
979ee0 |
except ldap.NO_SUCH_OBJECT:
|
|
|
979ee0 |
raise errors.NotFound(reason=arg_desc or 'no such entry')
|
|
|
979ee0 |
except ldap.ALREADY_EXISTS:
|
|
|
979ee0 |
+ # entry already exists
|
|
|
979ee0 |
raise errors.DuplicateEntry()
|
|
|
979ee0 |
+ except ldap.TYPE_OR_VALUE_EXISTS:
|
|
|
979ee0 |
+ # attribute type or attribute value already exists, usually only
|
|
|
979ee0 |
+ # occurs, when two machines try to write at the same time.
|
|
|
979ee0 |
+ raise errors.DuplicateEntry(message=unicode(desc))
|
|
|
979ee0 |
except ldap.CONSTRAINT_VIOLATION:
|
|
|
979ee0 |
# This error gets thrown by the uniqueness plugin
|
|
|
979ee0 |
_msg = 'Another entry with the same attribute value already exists'
|
|
|
979ee0 |
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
|
|
|
f65af0 |
index d0f92c76c108d237104a81c72567250583ac4ff1..78c4a43cc9b8d9a6740d26209f347f64b879743e 100644
|
|
|
979ee0 |
--- a/ipaserver/install/replication.py
|
|
|
979ee0 |
+++ b/ipaserver/install/replication.py
|
|
|
f65af0 |
@@ -34,7 +34,7 @@ import ldap
|
|
|
f65af0 |
from ipalib import api, errors
|
|
|
979ee0 |
from ipalib.cli import textui
|
|
|
979ee0 |
from ipalib.text import _
|
|
|
979ee0 |
-from ipapython import ipautil, ipaldap, kerberos
|
|
|
979ee0 |
+from ipapython import ipautil, ipaldap
|
|
|
979ee0 |
from ipapython.admintool import ScriptError
|
|
|
979ee0 |
from ipapython.dn import DN
|
|
|
f65af0 |
from ipapython.ipaldap import ldap_initialize
|
|
|
f65af0 |
@@ -461,7 +461,7 @@ class ReplicationManager(object):
|
|
|
979ee0 |
return DN(('cn', 'replica'), ('cn', self.db_suffix),
|
|
|
979ee0 |
('cn', 'mapping tree'), ('cn', 'config'))
|
|
|
979ee0 |
|
|
|
979ee0 |
- def set_replica_binddngroup(self, r_conn, entry):
|
|
|
979ee0 |
+ def _set_replica_binddngroup(self, r_conn, entry):
|
|
|
979ee0 |
"""
|
|
|
979ee0 |
Set nsds5replicabinddngroup attribute on remote master's replica entry.
|
|
|
979ee0 |
Older masters (ipa < 3.3) may not support setting this attribute. In
|
|
|
f65af0 |
@@ -476,11 +476,6 @@ class ReplicationManager(object):
|
|
|
979ee0 |
mod.append((ldap.MOD_ADD, 'nsds5replicabinddngroup',
|
|
|
979ee0 |
self.repl_man_group_dn))
|
|
|
979ee0 |
|
|
|
979ee0 |
- if 'nsds5replicabinddngroupcheckinterval' not in entry:
|
|
|
979ee0 |
- mod.append(
|
|
|
979ee0 |
- (ldap.MOD_ADD,
|
|
|
979ee0 |
- 'nsds5replicabinddngroupcheckinterval',
|
|
|
979ee0 |
- '60'))
|
|
|
979ee0 |
if mod:
|
|
|
979ee0 |
try:
|
|
|
979ee0 |
r_conn.modify_s(entry.dn, mod)
|
|
|
f65af0 |
@@ -488,49 +483,62 @@ class ReplicationManager(object):
|
|
|
f65af0 |
logger.debug(
|
|
|
979ee0 |
"nsds5replicabinddngroup attribute not supported on "
|
|
|
979ee0 |
"remote master.")
|
|
|
979ee0 |
+ except (ldap.ALREADY_EXISTS, ldap.CONSTRAINT_VIOLATION):
|
|
|
f65af0 |
+ logger.debug("No update to %s necessary", entry.dn)
|
|
|
979ee0 |
|
|
|
979ee0 |
def replica_config(self, conn, replica_id, replica_binddn):
|
|
|
979ee0 |
assert isinstance(replica_binddn, DN)
|
|
|
979ee0 |
dn = self.replica_dn()
|
|
|
979ee0 |
assert isinstance(dn, DN)
|
|
|
979ee0 |
|
|
|
f65af0 |
+ logger.debug("Add or update replica config %s", dn)
|
|
|
979ee0 |
try:
|
|
|
979ee0 |
entry = conn.get_entry(dn)
|
|
|
979ee0 |
except errors.NotFound:
|
|
|
979ee0 |
- pass
|
|
|
979ee0 |
- else:
|
|
|
979ee0 |
- binddns = entry.setdefault('nsDS5ReplicaBindDN', [])
|
|
|
979ee0 |
- if replica_binddn not in {DN(m) for m in binddns}:
|
|
|
979ee0 |
- # Add the new replication manager
|
|
|
979ee0 |
- binddns.append(replica_binddn)
|
|
|
979ee0 |
- for key, value in REPLICA_CREATION_SETTINGS.items():
|
|
|
979ee0 |
- entry[key] = value
|
|
|
979ee0 |
+ # no entry, create new one
|
|
|
979ee0 |
+ entry = conn.make_entry(
|
|
|
979ee0 |
+ dn,
|
|
|
979ee0 |
+ objectclass=["top", "nsds5replica", "extensibleobject"],
|
|
|
979ee0 |
+ cn=["replica"],
|
|
|
979ee0 |
+ nsds5replicaroot=[str(self.db_suffix)],
|
|
|
979ee0 |
+ nsds5replicaid=[str(replica_id)],
|
|
|
979ee0 |
+ nsds5replicatype=[self.get_replica_type()],
|
|
|
979ee0 |
+ nsds5flags=["1"],
|
|
|
979ee0 |
+ nsds5replicabinddn=[replica_binddn],
|
|
|
979ee0 |
+ nsds5replicabinddngroup=[self.repl_man_group_dn],
|
|
|
979ee0 |
+ nsds5replicalegacyconsumer=["off"],
|
|
|
979ee0 |
+ **REPLICA_CREATION_SETTINGS
|
|
|
979ee0 |
+ )
|
|
|
979ee0 |
try:
|
|
|
979ee0 |
- conn.update_entry(entry)
|
|
|
979ee0 |
- except errors.EmptyModlist:
|
|
|
979ee0 |
- pass
|
|
|
979ee0 |
+ conn.add_entry(entry)
|
|
|
979ee0 |
+ except errors.DuplicateEntry:
|
|
|
f65af0 |
+ logger.debug("Lost race against another replica, updating")
|
|
|
979ee0 |
+ # fetch entry that have been added by another replica
|
|
|
979ee0 |
+ entry = conn.get_entry(dn)
|
|
|
979ee0 |
+ else:
|
|
|
f65af0 |
+ logger.debug("Added replica config %s", dn)
|
|
|
979ee0 |
+ # added entry successfully
|
|
|
979ee0 |
+ return entry
|
|
|
979ee0 |
|
|
|
979ee0 |
- self.set_replica_binddngroup(conn, entry)
|
|
|
979ee0 |
+ # either existing entry or lost race
|
|
|
979ee0 |
+ binddns = entry.setdefault('nsDS5ReplicaBindDN', [])
|
|
|
979ee0 |
+ if replica_binddn not in {DN(m) for m in binddns}:
|
|
|
979ee0 |
+ # Add the new replication manager
|
|
|
979ee0 |
+ binddns.append(replica_binddn)
|
|
|
979ee0 |
|
|
|
979ee0 |
- # replication is already configured
|
|
|
979ee0 |
- return
|
|
|
979ee0 |
+ for key, value in REPLICA_CREATION_SETTINGS.items():
|
|
|
979ee0 |
+ entry[key] = value
|
|
|
979ee0 |
|
|
|
979ee0 |
- replica_type = self.get_replica_type()
|
|
|
979ee0 |
+ try:
|
|
|
979ee0 |
+ conn.update_entry(entry)
|
|
|
979ee0 |
+ except errors.EmptyModlist:
|
|
|
f65af0 |
+ logger.debug("No update to %s necessary", entry.dn)
|
|
|
979ee0 |
+ else:
|
|
|
f65af0 |
+ logger.debug("Update replica config %s", entry.dn)
|
|
|
979ee0 |
|
|
|
979ee0 |
- entry = conn.make_entry(
|
|
|
979ee0 |
- dn,
|
|
|
979ee0 |
- objectclass=["top", "nsds5replica", "extensibleobject"],
|
|
|
979ee0 |
- cn=["replica"],
|
|
|
979ee0 |
- nsds5replicaroot=[str(self.db_suffix)],
|
|
|
979ee0 |
- nsds5replicaid=[str(replica_id)],
|
|
|
979ee0 |
- nsds5replicatype=[replica_type],
|
|
|
979ee0 |
- nsds5flags=["1"],
|
|
|
979ee0 |
- nsds5replicabinddn=[replica_binddn],
|
|
|
979ee0 |
- nsds5replicabinddngroup=[self.repl_man_group_dn],
|
|
|
979ee0 |
- nsds5replicalegacyconsumer=["off"],
|
|
|
979ee0 |
- **REPLICA_CREATION_SETTINGS
|
|
|
979ee0 |
- )
|
|
|
979ee0 |
- conn.add_entry(entry)
|
|
|
979ee0 |
+ self._set_replica_binddngroup(conn, entry)
|
|
|
979ee0 |
+
|
|
|
979ee0 |
+ return entry
|
|
|
979ee0 |
|
|
|
979ee0 |
def setup_changelog(self, conn):
|
|
|
979ee0 |
ent = conn.get_entry(
|
|
|
f65af0 |
@@ -690,7 +698,10 @@ class ReplicationManager(object):
|
|
|
979ee0 |
uid=["passsync"],
|
|
|
979ee0 |
userPassword=[password],
|
|
|
979ee0 |
)
|
|
|
979ee0 |
- conn.add_entry(entry)
|
|
|
979ee0 |
+ try:
|
|
|
979ee0 |
+ conn.add_entry(entry)
|
|
|
979ee0 |
+ except errors.DuplicateEntry:
|
|
|
979ee0 |
+ pass
|
|
|
979ee0 |
|
|
|
979ee0 |
# Add the user to the list of users allowed to bypass password policy
|
|
|
979ee0 |
extop_dn = DN(('cn', 'ipa_pwd_extop'), ('cn', 'plugins'), ('cn', 'config'))
|
|
|
f65af0 |
@@ -1658,7 +1669,10 @@ class ReplicationManager(object):
|
|
|
979ee0 |
objectclass=['top', 'groupofnames'],
|
|
|
979ee0 |
cn=['replication managers']
|
|
|
979ee0 |
)
|
|
|
979ee0 |
- conn.add_entry(entry)
|
|
|
979ee0 |
+ try:
|
|
|
979ee0 |
+ conn.add_entry(entry)
|
|
|
979ee0 |
+ except errors.DuplicateEntry:
|
|
|
979ee0 |
+ pass
|
|
|
979ee0 |
|
|
|
979ee0 |
def ensure_replication_managers(self, conn, r_hostname):
|
|
|
979ee0 |
"""
|
|
|
f65af0 |
@@ -1668,23 +1682,24 @@ class ReplicationManager(object):
|
|
|
979ee0 |
On FreeIPA 3.x masters lacking support for nsds5ReplicaBinddnGroup
|
|
|
979ee0 |
attribute, add replica bind DN directly into the replica entry.
|
|
|
979ee0 |
"""
|
|
|
979ee0 |
- my_princ = kerberos.Principal((u'ldap', unicode(self.hostname)),
|
|
|
979ee0 |
- realm=self.realm)
|
|
|
979ee0 |
- remote_princ = kerberos.Principal((u'ldap', unicode(r_hostname)),
|
|
|
979ee0 |
- realm=self.realm)
|
|
|
979ee0 |
- services_dn = DN(api.env.container_service, api.env.basedn)
|
|
|
979ee0 |
-
|
|
|
979ee0 |
- mydn, remote_dn = tuple(
|
|
|
979ee0 |
- DN(('krbprincipalname', unicode(p)), services_dn) for p in (
|
|
|
979ee0 |
- my_princ, remote_princ))
|
|
|
979ee0 |
+ my_dn = DN(
|
|
|
979ee0 |
+ ('krbprincipalname', u'ldap/%s@%s' % (self.hostname, self.realm)),
|
|
|
979ee0 |
+ api.env.container_service,
|
|
|
979ee0 |
+ api.env.basedn
|
|
|
979ee0 |
+ )
|
|
|
979ee0 |
+ remote_dn = DN(
|
|
|
979ee0 |
+ ('krbprincipalname', u'ldap/%s@%s' % (r_hostname, self.realm)),
|
|
|
979ee0 |
+ api.env.container_service,
|
|
|
979ee0 |
+ api.env.basedn
|
|
|
979ee0 |
+ )
|
|
|
979ee0 |
|
|
|
979ee0 |
try:
|
|
|
979ee0 |
conn.get_entry(self.repl_man_group_dn)
|
|
|
979ee0 |
except errors.NotFound:
|
|
|
979ee0 |
- self._add_replica_bind_dn(conn, mydn)
|
|
|
979ee0 |
+ self._add_replica_bind_dn(conn, my_dn)
|
|
|
979ee0 |
self._add_replication_managers(conn)
|
|
|
979ee0 |
|
|
|
979ee0 |
- self._add_dn_to_replication_managers(conn, mydn)
|
|
|
979ee0 |
+ self._add_dn_to_replication_managers(conn, my_dn)
|
|
|
979ee0 |
self._add_dn_to_replication_managers(conn, remote_dn)
|
|
|
979ee0 |
|
|
|
979ee0 |
def add_temp_sasl_mapping(self, conn, r_hostname):
|
|
|
f65af0 |
@@ -1704,7 +1719,10 @@ class ReplicationManager(object):
|
|
|
979ee0 |
|
|
|
979ee0 |
entry = conn.get_entry(self.replica_dn())
|
|
|
979ee0 |
entry['nsDS5ReplicaBindDN'].append(replica_binddn)
|
|
|
979ee0 |
- conn.update_entry(entry)
|
|
|
979ee0 |
+ try:
|
|
|
979ee0 |
+ conn.update_entry(entry)
|
|
|
979ee0 |
+ except errors.EmptyModlist:
|
|
|
979ee0 |
+ pass
|
|
|
979ee0 |
|
|
|
979ee0 |
entry = conn.make_entry(
|
|
|
979ee0 |
DN(('cn', 'Peer Master'), ('cn', 'mapping'), ('cn', 'sasl'),
|
|
|
f65af0 |
@@ -1716,7 +1734,10 @@ class ReplicationManager(object):
|
|
|
979ee0 |
nsSaslMapFilterTemplate=['(cn=&@%s)' % self.realm],
|
|
|
979ee0 |
nsSaslMapPriority=['1'],
|
|
|
979ee0 |
)
|
|
|
979ee0 |
- conn.add_entry(entry)
|
|
|
979ee0 |
+ try:
|
|
|
979ee0 |
+ conn.add_entry(entry)
|
|
|
979ee0 |
+ except errors.DuplicateEntry:
|
|
|
979ee0 |
+ pass
|
|
|
979ee0 |
|
|
|
979ee0 |
def remove_temp_replication_user(self, conn, r_hostname):
|
|
|
979ee0 |
"""
|
|
|
979ee0 |
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
|
|
|
f65af0 |
index 8745866ad11cf6248d2b9dcff560c0a5ee6d109b..5d39cdf628d7e20ce5b259d97b479ef7c8ed88d5 100644
|
|
|
979ee0 |
--- a/ipaserver/plugins/ldap2.py
|
|
|
979ee0 |
+++ b/ipaserver/plugins/ldap2.py
|
|
|
f65af0 |
@@ -419,7 +419,8 @@ class ldap2(CrudBackend, LDAPClient):
|
|
|
979ee0 |
modlist = [(a, b, self.encode(c))
|
|
|
979ee0 |
for a, b, c in modlist]
|
|
|
979ee0 |
self.conn.modify_s(str(group_dn), modlist)
|
|
|
979ee0 |
- except errors.DatabaseError:
|
|
|
979ee0 |
+ except errors.DuplicateEntry:
|
|
|
979ee0 |
+ # TYPE_OR_VALUE_EXISTS
|
|
|
979ee0 |
raise errors.AlreadyGroupMember()
|
|
|
979ee0 |
|
|
|
979ee0 |
def remove_entry_from_group(self, dn, group_dn, member_attr='member'):
|
|
|
979ee0 |
--
|
|
|
979ee0 |
2.17.1
|
|
|
979ee0 |
|