Remove common entries when deleting a master.

Fixes: https://fedorahosted.org/freeipa/ticket/550
This commit is contained in:
Simo Sorce
2010-12-10 09:48:06 -05:00
parent 5884fdf0f8
commit 1cf67fe850
5 changed files with 152 additions and 40 deletions

View File

@@ -23,6 +23,11 @@ changetype: modify
add: aci
aci: (targetfilter = "(objectClass=ipaGuiConfig)")(targetattr != "aci")(version 3.0;acl "Admins can change GUI config"; allow (read, search, compare, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
dn: cn=ipa,cn=etc,$SUFFIX
changetype: modify
add: aci
aci: (targetfilter = "(|(objectClass=ipaConfigObject)(dnahostname=*))")(version 3.0;acl "Admins can change GUI config"; allow (delete) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
dn: cn=accounts,$SUFFIX
changetype: modify
add: aci

View File

@@ -76,16 +76,6 @@ def get_suffix():
suffix = l.normalize_dn(util.realm_to_suffix(get_realm_name()))
return suffix
def get_host_name():
hostname = installutils.get_fqdn()
try:
installutils.verify_fqdn(hostname)
except RuntimeError, e:
logging.error(str(e))
sys.exit(1)
return hostname
def test_connection(host):
"""
Make a GSSAPI connection to the remote LDAP server to test out credentials.
@@ -114,41 +104,55 @@ def list_masters(replman, verbose):
print " last init ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastinitend))
print " last update status: %s" % entry.nsds5replicalastupdatestatus
print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.nsds5replicalastupdateend))
def del_master(replman, hostname, force=False):
has_repl_agreement = True
try:
t = replman.get_agreement_type(hostname)
except ldap.NO_SUCH_OBJECT:
print "No replication agreement found for '%s'" % hostname
return
if force:
has_repl_agreement = False
else:
return
except errors.NotFound:
print "No replication agreement found for '%s'" % hostname
return
if force:
has_repl_agreement = False
else:
return
# Delete the remote agreement first
if t == replication.IPA_REPLICA:
failed = False
try:
other_replman = replication.ReplicationManager(hostname, replman.dirman_passwd)
other_replman.suffix = get_suffix()
other_replman.delete_agreement(replman.conn.host)
except ldap.LDAPError, e:
desc = e.args[0]['desc'].strip()
info = e.args[0].get('info', '').strip()
print "Unable to remove agreement on %s: %s: %s" % (hostname, desc, info)
failed = True
except Exception, e:
print "Unable to remove agreement on %s: %s" % (hostname, str(e))
failed = True
if has_repl_agreement:
# Delete the remote agreement first
if t == replication.IPA_REPLICA:
failed = False
try:
other_replman = replication.ReplicationManager(hostname, replman.dirman_passwd)
other_replman.suffix = get_suffix()
other_replman.delete_agreement(replman.conn.host)
except ldap.LDAPError, e:
desc = e.args[0]['desc'].strip()
info = e.args[0].get('info', '').strip()
print "Unable to remove agreement on %s: %s: %s" % (hostname, desc, info)
failed = True
except Exception, e:
print "Unable to remove agreement on %s: %s" % (hostname, str(e))
failed = True
if failed:
if force:
print "Forcing removal on local server"
else:
return
if failed:
if force:
print "Forcing removal on local server"
else:
return
# Delete the local agreement
replman.delete_agreement(hostname)
# Delete the local agreement
replman.delete_agreement(hostname)
try:
replman.replica_cleanup(hostname, get_realm_name(), force=True)
except Exception, e:
print "Failed to cleanup %s entries: %s" % (hostname, str(e))
print "You may need to manually remove them from the tree"
def add_master(replman, hostname, options):
other_args = {}
@@ -210,13 +214,13 @@ def synch_master(replman, hostname):
def main():
options, args = parse_options()
dirman_passwd = None
if options.host:
host = options.host
else:
host = get_host_name()
host = installutils.get_fqdn()
if options.dirman_passwd:
dirman_passwd = options.dirman_passwd

View File

@@ -573,3 +573,76 @@ class ReplicationManager:
return WINSYNC
return IPA_REPLICA
def replica_cleanup(self, replica, realm, force=False):
err = None
if replica == self.hostname:
raise RuntimeError("Can't cleanup self")
if not self.suffix or self.suffix == "":
self.suffix = util.realm_to_suffix(realm)
self.suffix = ipaldap.IPAdmin.normalizeDN(self.suffix)
# delete master kerberos key and all its svc principals
try:
filter='(krbprincipalname=*/%s@%s)' % (replica, realm)
entries = self.conn.search_s(self.suffix, ldap.SCOPE_SUBTREE,
filterstr=filter)
if len(entries) != 0:
dnset = self.conn.get_dns_sorted_by_length(entries,
reverse=True)
for dns in dnset:
for dn in dns:
self.conn.deleteEntry(dn)
except ldap.NO_SUCH_OBJECT:
pass
except errors.NotFound:
pass
except Exception, e:
if not force:
raise e
else:
err = e
# delete master entry with all active services
try:
dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (replica, self.suffix)
entries = self.conn.search_s(dn, ldap.SCOPE_SUBTREE)
if len(entries) != 0:
dnset = self.conn.get_dns_sorted_by_length(entries,
reverse=True)
for dns in dnset:
for dn in dns:
self.conn.deleteEntry(dn)
except ldap.NO_SUCH_OBJECT:
pass
except errors.NotFound:
pass
except Exception, e:
if not force:
raise e
elif not err:
err = e
try:
basedn = 'cn=etc,%s' % self.suffix
filter = '(dnaHostname=%s)' % replica
entries = self.conn.search_s(basedn, ldap.SCOPE_SUBTREE,
filterstr=filter)
if len(entries) != 0:
for e in entries:
self.conn.deleteEntry(e.dn)
except ldap.NO_SUCH_OBJECT:
pass
except errors.NotFound:
pass
except Exception, e:
if force and err:
raise err
else:
raise e
if err:
raise err

View File

@@ -640,6 +640,35 @@ class IPAdmin(SimpleLDAPObject):
return ",".join(ary)
normalizeDN = staticmethod(normalizeDN)
def get_dns_sorted_by_length(self, entries, reverse=False):
"""
Sorts a list of entries [(dn, entry_attrs)] based on their DN.
Entries within the same node are not sorted in any meaningful way.
If Reverse is set to True, leaf entries are returned first. This is
useful to perform recursive deletes where you need to delete entries
starting from the leafs and go up to delete nodes only when all its
leafs are removed.
Returns a "sorted" dict keyed by dn lengths and corresponding list
of DNs.
{'1': [dn1, dn2, dn3], '2': [dn4, dn5], ..}
"""
res = dict()
for e in entries:
sdn = ldap.dn.str2dn(e.dn)
l = len(sdn)
if not l in res:
res[l] = []
res[l].append(e.dn)
keys = res.keys()
keys.sort(reverse=reverse)
return map(res.get, keys)
def notfound(args):
"""Return a string suitable for displaying as an error when a
search returns no results.

View File

@@ -506,9 +506,10 @@ class ldap2(CrudBackend, Encoder):
scope=_ldap.SCOPE_SUBTREE, time_limit=None, size_limit=None,
normalize=True):
"""
Return a list of entries [(dn, entry_attrs)] matching specified
search parameters followed by truncated flag. If the truncated flag is
True, search hit a server limit and its results are incomplete.
Return a list of entries and indication of whteher the results where
truncated ([(dn, entry_attrs)], truncated) matching specified search
parameters followed by truncated flag. If the truncated flag is True,
search hit a server limit and its results are incomplete.
Keyword arguments:
attrs_list -- list of attributes to return, all if None (default None)