Run the CLEANALLRUV task when deleting a replication agreement.

This adds two new commands to ipa-replica-manage: list-ruv & clean-ruv

list-ruv can be use to list the update vectors the master has
configugured

clean-ruv can be used to fire off the CLEANRUV task to remove a
replication vector. It should be used with caution.

https://fedorahosted.org/freeipa/ticket/2303
This commit is contained in:
Rob Crittenden
2012-09-17 17:45:42 +02:00
committed by Martin Kosek
parent c0630950a1
commit c9c55a2845
7 changed files with 343 additions and 30 deletions

View File

@@ -24,7 +24,7 @@ Source0: freeipa-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%if ! %{ONLY_CLIENT} %if ! %{ONLY_CLIENT}
BuildRequires: 389-ds-base-devel >= 1.2.10-0.6.a6 BuildRequires: 389-ds-base-devel >= 1.2.11.14
BuildRequires: svrcore-devel BuildRequires: svrcore-devel
BuildRequires: /usr/share/selinux/devel/Makefile BuildRequires: /usr/share/selinux/devel/Makefile
BuildRequires: policycoreutils >= %{POLICYCOREUTILSVER} BuildRequires: policycoreutils >= %{POLICYCOREUTILSVER}
@@ -100,11 +100,7 @@ Requires: %{name}-python = %{version}-%{release}
Requires: %{name}-client = %{version}-%{release} Requires: %{name}-client = %{version}-%{release}
Requires: %{name}-admintools = %{version}-%{release} Requires: %{name}-admintools = %{version}-%{release}
Requires: %{name}-server-selinux = %{version}-%{release} Requires: %{name}-server-selinux = %{version}-%{release}
%if 0%{?fedora} >= 17 Requires(pre): 389-ds-base >= 1.2.11.14-1
Requires(pre): 389-ds-base >= 1.2.11.8-1
%else
Requires(pre): 389-ds-base >= 1.2.10.10-1
%endif
Requires: openldap-clients Requires: openldap-clients
Requires: nss Requires: nss
Requires: nss-tools Requires: nss-tools
@@ -752,6 +748,10 @@ fi
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
%changelog %changelog
* Mon Sep 17 2012 Rob Crittenden <rcritten@redhat.com> - 2.99.0-45
- Set min for 389-ds-base to 1.2.11.14-1 on F17+ to pull in updated
RUV code and nsslapd-readonly schema.
* Fri Sep 14 2012 Sumit Bose <sbose@redhat.com> - 2.99.0-44 * Fri Sep 14 2012 Sumit Bose <sbose@redhat.com> - 2.99.0-44
- Updated samba4-devel dependency due to API change - Updated samba4-devel dependency due to API change

View File

@@ -20,6 +20,11 @@ changetype: modify
add: aci add: aci
aci: (targetattr=*)(targetfilter="(|(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement))")(version 3.0;acl "permission:Remove Replication Agreements";allow (delete) groupdn = "ldap:///cn=Remove Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";) aci: (targetattr=*)(targetfilter="(|(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement))")(version 3.0;acl "permission:Remove Replication Agreements";allow (delete) groupdn = "ldap:///cn=Remove Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
changetype: modify
add: aci
aci: (targetattr=nsslapd-readonly)(version 3.0; acl "Allow marking the database readonly"; allow (write) groupdn = "ldap:///cn=Remove Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
dn: cn=tasks,cn=config dn: cn=tasks,cn=config
changetype: modify changetype: modify
add: aci add: aci

View File

@@ -22,6 +22,7 @@ import os
import ldap, re, krbV import ldap, re, krbV
import traceback import traceback
from urllib2 import urlparse
from ipapython import ipautil from ipapython import ipautil
from ipaserver.install import replication, dsinstance, installutils from ipaserver.install import replication, dsinstance, installutils
@@ -38,6 +39,7 @@ CACERT = "/etc/ipa/ca.crt"
# dict of command name and tuples of min/max num of args needed # dict of command name and tuples of min/max num of args needed
commands = { commands = {
"list":(0, 1, "[master fqdn]", ""), "list":(0, 1, "[master fqdn]", ""),
"list-ruv":(0, 0, "", ""),
"connect":(1, 2, "<master fqdn> [other master fqdn]", "connect":(1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the servers to connect"), "must provide the name of the servers to connect"),
"disconnect":(1, 2, "<master fqdn> [other master fqdn]", "disconnect":(1, 2, "<master fqdn> [other master fqdn]",
@@ -45,9 +47,23 @@ commands = {
"del":(1, 1, "<master fqdn>", "del":(1, 1, "<master fqdn>",
"must provide hostname of master to delete"), "must provide hostname of master to delete"),
"re-initialize":(0, 0, "", ""), "re-initialize":(0, 0, "", ""),
"force-sync":(0, 0, "", "") "force-sync":(0, 0, "", ""),
"clean-ruv":(1, 1, "Replica ID of to clean", "must provide replica ID to clean"),
"abort-clean-ruv":(1, 1, "Replica ID to abort cleaning", "must provide replica ID to abort cleaning"),
"list-clean-ruv":(0, 0, "", ""),
} }
def convert_error(exc):
"""
LDAP exceptions are a dictionary, make them prettier.
"""
if isinstance(exc, ldap.LDAPError):
desc = exc.args[0]['desc'].strip()
info = exc.args[0].get('info', '').strip()
return '%s %s' % (desc, info)
else:
return str(exc)
def parse_options(): def parse_options():
parser = IPAOptionParser(version=version.VERSION) parser = IPAOptionParser(version=version.VERSION)
parser.add_option("-H", "--host", dest="host", help="starting host") parser.add_option("-H", "--host", dest="host", help="starting host")
@@ -132,7 +148,7 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose):
try: try:
entries = conn.getList(dn, ldap.SCOPE_ONELEVEL) entries = conn.getList(dn, ldap.SCOPE_ONELEVEL)
except: except:
print "Failed read master data from '%s': %s" % (host, str(e)) print "Failed to read master data from '%s': %s" % (host, str(e))
return return
else: else:
for ent in entries: for ent in entries:
@@ -177,7 +193,7 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose):
entries = repl.find_replication_agreements() entries = repl.find_replication_agreements()
ent_type = 'replica' ent_type = 'replica'
except Exception, e: except Exception, e:
print "Failed to get data from '%s': %s" % (replica, str(e)) print "Failed to get data from '%s': %s" % (replica, convert_error(e))
return return
for entry in entries: for entry in entries:
@@ -190,6 +206,15 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose):
print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastupdateend'))) print " last update ended: %s" % str(ipautil.parse_generalized_time(entry.getValue('nsds5replicalastupdateend')))
def del_link(realm, replica1, replica2, dirman_passwd, force=False): def del_link(realm, replica1, replica2, dirman_passwd, force=False):
"""
Delete a replication agreement from host A to host B.
@realm: the Kerberos realm
@replica1: the hostname of master A
@replica2: the hostname of master B
@dirman_passwd: the Directory Manager password
@force: force deletion even if one server is down
"""
repl2 = None repl2 = None
@@ -202,14 +227,14 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
if not force and len(repl_list) <= 1 and type1 == replication.IPA_REPLICA: if not force and len(repl_list) <= 1 and type1 == replication.IPA_REPLICA:
print "Cannot remove the last replication link of '%s'" % replica1 print "Cannot remove the last replication link of '%s'" % replica1
print "Please use the 'del' command to remove it from the domain" print "Please use the 'del' command to remove it from the domain"
return return False
except (ldap.NO_SUCH_OBJECT, errors.NotFound): except (ldap.NO_SUCH_OBJECT, errors.NotFound):
print "'%s' has no replication agreement for '%s'" % (replica1, replica2) print "'%s' has no replication agreement for '%s'" % (replica1, replica2)
return return False
except Exception, e: except Exception, e:
print "Failed to get data from '%s': %s" % (replica1, str(e)) print "Failed to determine agreement type for '%s': %s" % (replica1, convert_error(e))
return return False
if type1 == replication.IPA_REPLICA: if type1 == replication.IPA_REPLICA:
try: try:
@@ -219,36 +244,41 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
if not force and len(repl_list) <= 1: if not force and len(repl_list) <= 1:
print "Cannot remove the last replication link of '%s'" % replica2 print "Cannot remove the last replication link of '%s'" % replica2
print "Please use the 'del' command to remove it from the domain" print "Please use the 'del' command to remove it from the domain"
return return False
except (ldap.NO_SUCH_OBJECT, errors.NotFound): except (ldap.NO_SUCH_OBJECT, errors.NotFound):
print "'%s' has no replication agreement for '%s'" % (replica2, replica1) print "'%s' has no replication agreement for '%s'" % (replica2, replica1)
if not force: if not force:
return return False
except Exception, e: except Exception, e:
print "Failed to get data from '%s': %s" % (replica2, str(e)) print "Failed to get list of agreements from '%s': %s" % (replica2, convert_error(e))
if not force: if not force:
return return False
if repl2 and type1 == replication.IPA_REPLICA: if repl2 and type1 == replication.IPA_REPLICA:
failed = False failed = False
try: try:
repl2.set_readonly(readonly=True)
repl2.force_sync(repl2.conn, replica1)
cn, dn = repl2.agreement_dn(repl1.conn.host)
repl2.wait_for_repl_update(repl2.conn, dn, 30)
repl2.delete_agreement(replica1) repl2.delete_agreement(replica1)
repl2.delete_referral(replica1) repl2.delete_referral(replica1)
repl2.set_readonly(readonly=False)
except ldap.LDAPError, e: except ldap.LDAPError, e:
desc = e.args[0]['desc'].strip() desc = e.args[0]['desc'].strip()
info = e.args[0].get('info', '').strip() info = e.args[0].get('info', '').strip()
print "Unable to remove agreement on %s: %s: %s" % (replica2, desc, info) print "Unable to remove agreement on %s: %s: %s" % (replica2, desc, info)
failed = True failed = True
except Exception, e: except Exception, e:
print "Unable to remove agreement on %s: %s" % (replica2, str(e)) print "Unable to remove agreement on %s: %s" % (replica2, convert_error(e))
failed = True failed = True
if failed: if failed:
if force: if force:
print "Forcing removal on '%s'" % replica1 print "Forcing removal on '%s'" % replica1
else: else:
return return False
if not repl2 and force: if not repl2 and force:
print "Forcing removal on '%s'" % replica1 print "Forcing removal on '%s'" % replica1
@@ -268,10 +298,171 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
for dn in dns: for dn in dns:
repl1.conn.deleteEntry(dn) repl1.conn.deleteEntry(dn)
except Exception, e: except Exception, e:
print "Error deleting winsync replica shared info: %s" % str(e) print "Error deleting winsync replica shared info: %s" % convert_error(e)
print "Deleted replication agreement from '%s' to '%s'" % (replica1, replica2) print "Deleted replication agreement from '%s' to '%s'" % (replica1, replica2)
return True
def get_ruv(realm, host, dirman_passwd):
"""
Return the RUV entries as a list of tuples: (hostname, rid)
"""
try:
thisrepl = replication.ReplicationManager(realm, host, dirman_passwd)
except Exception, e:
print "Failed to connect to server %s: %s" % (host, convert_error(e))
sys.exit(1)
search_filter = '(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))'
try:
entries = thisrepl.conn.search_s(api.env.basedn, ldap.SCOPE_ONELEVEL,
search_filter, ['nsds50ruv'])
except ldap.NO_SUCH_OBJECT:
print "No RUV records found."
sys.exit(0)
servers = []
for ruv in entries[0][1]['nsds50ruv']:
if ruv.startswith('{replicageneration'):
continue
data = re.match('\{replica (\d+) (ldap://.*:\d+)\}(\s+\w+\s+\w*){0,1}', ruv)
if data:
rid = data.group(1)
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(data.group(2))
servers.append((netloc, rid))
else:
print "unable to decode: %s" % ruv
return servers
def list_ruv(realm, host, dirman_passwd, verbose):
"""
List the Replica Update Vectors on this host to get the available
replica IDs.
"""
servers = get_ruv(realm, host, dirman_passwd)
for (netloc, rid) in servers:
print "%s: %s" % (netloc, rid)
def get_rid_by_host(realm, sourcehost, host, dirman_passwd):
"""
Try to determine the RID by host name.
"""
servers = get_ruv(realm, sourcehost, dirman_passwd)
for (netloc, rid) in servers:
if '%s:389' % host == netloc:
return int(rid)
def clean_ruv(realm, ruv, options):
"""
Given an RID create a CLEANALLRUV task to clean it up.
"""
try:
ruv = int(ruv)
except ValueError:
sys.exit("Replica ID must be an integer: %s" % ruv)
servers = get_ruv(realm, options.host, options.dirman_passwd)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
if not found:
sys.exit("Replica ID %s not found" % ruv)
print "Clean the Replication Update Vector for %s" % hostname
print
print "Cleaning the wrong replica ID will cause that server to no"
print "longer replicate so it may miss updates while the process"
print "is running. It would need to be re-initialized to maintain"
print "consistency. Be very careful."
if not options.force and not ipautil.user_input("Continue to clean?", False):
sys.exit("Aborted")
thisrepl = replication.ReplicationManager(realm, options.host,
options.dirman_passwd)
thisrepl.cleanallruv(ruv)
print "Cleanup task created"
def abort_clean_ruv(realm, ruv, options):
"""
Given an RID abort a CLEANALLRUV task.
"""
try:
ruv = int(ruv)
except ValueError:
sys.exit("Replica ID must be an integer: %s" % ruv)
servers = get_ruv(realm, options.host, options.dirman_passwd)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
if not found:
sys.exit("Replica ID %s not found" % ruv)
servers = get_ruv(realm, options.host, options.dirman_passwd)
found = False
for (netloc, rid) in servers:
if ruv == int(rid):
found = True
hostname = netloc
break
if not found:
sys.exit("Replica ID %s not found" % ruv)
print "Aborting the clean Replication Update Vector task for %s" % hostname
print
thisrepl = replication.ReplicationManager(realm, options.host,
options.dirman_passwd)
thisrepl.abortcleanallruv(ruv)
print "Cleanup task stopped"
def list_clean_ruv(realm, host, dirman_passwd, verbose):
"""
List all clean RUV tasks.
"""
repl = replication.ReplicationManager(realm, host, dirman_passwd)
dn = DN(('cn', 'cleanallruv'),('cn', 'tasks'), ('cn', 'config'))
try:
entries = repl.conn.getList(dn, ldap.SCOPE_ONELEVEL)
except errors.NotFound:
print "No CLEANALLRUV tasks running"
else:
print "CLEANALLRUV tasks"
for entry in entries:
name = entry.getValue('cn').replace('clean ', '')
status = entry.getValue('nsTaskStatus')
print "RID %s: %s" % (name, status)
if verbose:
print str(dn)
print entry.getValue('nstasklog')
print
dn = DN(('cn', 'abort cleanallruv'),('cn', 'tasks'), ('cn', 'config'))
try:
entries = repl.conn.getList(dn, ldap.SCOPE_ONELEVEL)
except errors.NotFound:
print "No abort CLEANALLRUV tasks running"
else:
print "Abort CLEANALLRUV tasks"
for entry in entries:
name = entry.getValue('cn').replace('abort ', '')
status = entry.getValue('nsTaskStatus')
print "RID %s: %s" % (name, status)
if verbose:
print str(dn)
print entry.getValue('nstasklog')
def del_master(realm, hostname, options): def del_master(realm, hostname, options):
force_del = False force_del = False
@@ -281,7 +472,7 @@ def del_master(realm, hostname, options):
thisrepl = replication.ReplicationManager(realm, options.host, thisrepl = replication.ReplicationManager(realm, options.host,
options.dirman_passwd) options.dirman_passwd)
except Exception, e: except Exception, e:
print "Failed to connect to server %s: %s" % (options.host, str(e)) print "Failed to connect to server %s: %s" % (options.host, convert_error(e))
sys.exit(1) sys.exit(1)
# 2. Ensure we have an agreement with the master # 2. Ensure we have an agreement with the master
@@ -297,7 +488,7 @@ def del_master(realm, hostname, options):
delrepl = replication.ReplicationManager(realm, hostname, options.dirman_passwd) delrepl = replication.ReplicationManager(realm, hostname, options.dirman_passwd)
except Exception, e: except Exception, e:
if not options.force: if not options.force:
print "Unable to delete replica %s: %s" % (hostname, str(e)) print "Unable to delete replica %s: %s" % (hostname, convert_error(e))
sys.exit(1) sys.exit(1)
else: else:
print "Unable to connect to replica %s, forcing removal" % hostname print "Unable to connect to replica %s, forcing removal" % hostname
@@ -325,21 +516,35 @@ def del_master(realm, hostname, options):
if not ipautil.user_input("Continue to delete?", False): if not ipautil.user_input("Continue to delete?", False):
sys.exit("Deletion aborted") sys.exit("Deletion aborted")
# Save the RID value before we start deleting
if repltype == replication.IPA_REPLICA:
rid = get_rid_by_host(realm, options.host, hostname, options.dirman_passwd)
# 4. Remove each agreement # 4. Remove each agreement
print "Deleting replication agreements between %s and %s" % (hostname, ', '.join(replica_names))
for r in replica_names: for r in replica_names:
try: try:
del_link(realm, r, hostname, options.dirman_passwd, force=True) if not del_link(realm, r, hostname, options.dirman_passwd, force=True):
print "Unable to remove replication agreement for %s from %s." % (hostname, r)
except Exception, e: except Exception, e:
print "There were issues removing a connection: %s" % str(e) print "There were issues removing a connection: %s" % convert_error(e)
# 5. Finally clean up the removed replica common entries. # 5. Clean RUV for the deleted master
if repltype == replication.IPA_REPLICA:
try:
thisrepl.cleanallruv(rid)
except KeyboardInterrupt:
print "Wait for task interrupted. It will continue to run in the background"
# 6. Finally clean up the removed replica common entries.
try: try:
thisrepl.replica_cleanup(hostname, realm, force=True) thisrepl.replica_cleanup(hostname, realm, force=True)
except Exception, e: except Exception, e:
print "Failed to cleanup %s entries: %s" % (hostname, str(e)) print "Failed to cleanup %s entries: %s" % (hostname, convert_error(e))
print "You may need to manually remove them from the tree" print "You may need to manually remove them from the tree"
# 6. And clean up the removed replica DNS entries if any. # 7. And clean up the removed replica DNS entries if any.
try: try:
if bindinstance.dns_container_exists(options.host, thisrepl.suffix, if bindinstance.dns_container_exists(options.host, thisrepl.suffix,
dm_password=options.dirman_passwd): dm_password=options.dirman_passwd):
@@ -352,7 +557,7 @@ def del_master(realm, hostname, options):
bind = bindinstance.BindInstance() bind = bindinstance.BindInstance()
bind.remove_master_dns_records(hostname, realm, realm.lower()) bind.remove_master_dns_records(hostname, realm, realm.lower())
except Exception, e: except Exception, e:
print "Failed to cleanup %s DNS entries: %s" % (hostname, str(e)) print "Failed to cleanup %s DNS entries: %s" % (hostname, convert_error(e))
print "You may need to manually remove them from the tree" print "You may need to manually remove them from the tree"
def add_link(realm, replica1, replica2, dirman_passwd, options): def add_link(realm, replica1, replica2, dirman_passwd, options):
@@ -391,12 +596,11 @@ def add_link(realm, replica1, replica2, dirman_passwd, options):
# the directory server and kill the connection # the directory server and kill the connection
try: try:
repl1 = replication.ReplicationManager(realm, replica1, dirman_passwd) repl1 = replication.ReplicationManager(realm, replica1, dirman_passwd)
except (ldap.NO_SUCH_OBJECT, errors.NotFound): except (ldap.NO_SUCH_OBJECT, errors.NotFound):
print "Cannot find replica '%s'" % replica1 print "Cannot find replica '%s'" % replica1
return return
except Exception, e: except Exception, e:
print "Failed to get data from '%s': %s" % (replica1, str(e)) print "Failed to connect to '%s': %s" % (replica1, convert_error(e))
return return
if options.winsync: if options.winsync:
@@ -513,6 +717,8 @@ def main():
if len(args) == 2: if len(args) == 2:
replica = args[1] replica = args[1]
list_replicas(realm, host, replica, dirman_passwd, options.verbose) list_replicas(realm, host, replica, dirman_passwd, options.verbose)
elif args[0] == "list-ruv":
list_ruv(realm, host, dirman_passwd, options.verbose)
elif args[0] == "del": elif args[0] == "del":
del_master(realm, args[1], options) del_master(realm, args[1], options)
elif args[0] == "re-initialize": elif args[0] == "re-initialize":
@@ -541,6 +747,12 @@ def main():
replica1 = host replica1 = host
replica2 = args[1] replica2 = args[1]
del_link(realm, replica1, replica2, dirman_passwd) del_link(realm, replica1, replica2, dirman_passwd)
elif args[0] == "clean-ruv":
clean_ruv(realm, args[1], options)
elif args[0] == "abort-clean-ruv":
abort_clean_ruv(realm, args[1], options)
elif args[0] == "list-clean-ruv":
list_clean_ruv(realm, host, dirman_passwd, options.verbose)
try: try:
main() main()

View File

@@ -42,11 +42,29 @@ Manages the replication agreements of an IPA server.
\fBforce\-sync\fR \fBforce\-sync\fR
\- Immediately flush any data to be replicated from a server specified with the \-\-from option \- Immediately flush any data to be replicated from a server specified with the \-\-from option
.TP .TP
\fBlist\-ruv\fR
\- List the replication IDs on this server.
.TP
\fBclean\-ruv\fR [REPLICATION_ID]
\- Run the CLEANALLRUV task to remove a replication ID.
.TP
\fBabort\-clean\-ruv\fR [REPLICATION_ID]
\- Abort a running CLEANALLRUV task.
.TP
\fBlist\-clean\-ruv\fR
\- List all running CLEANALLRUV and abort CLEANALLRUV tasks.
.TP
The connect and disconnect options are used to manage the replication topology. When a replica is created it is only connected with the master that created it. The connect option may be used to connect it to other existing replicas. The connect and disconnect options are used to manage the replication topology. When a replica is created it is only connected with the master that created it. The connect option may be used to connect it to other existing replicas.
.TP .TP
The disconnect option cannot be used to remove the last link of a replica. To remove a replica from the topology use the del option. The disconnect option cannot be used to remove the last link of a replica. To remove a replica from the topology use the del option.
.TP .TP
If a replica is deleted and then re\-added within a short time\-frame then the 389\-ds instance on the master that created it should be restarted before re\-installing the replica. The master will have the old service principals cached which will cause replication to fail. If a replica is deleted and then re\-added within a short time\-frame then the 389\-ds instance on the master that created it should be restarted before re\-installing the replica. The master will have the old service principals cached which will cause replication to fail.
.TP
Each IPA master server has a unique replication ID. This ID is used by 389\-ds\-base when storing information about replication status. The output consists of the masters and their respective replication ID. See \fBclean\-ruv\fR
.TP
When a master is removed, all other masters need to remove its replication ID from the list of masters. Normally this occurs automatically when a master is deleted with ipa\-replica\-manage. If one or more masters was down or unreachable when ipa\-replica\-manage was executed then this replica ID may still exist. The clean\-ruv command may be used to clean up an unused replication ID.
.TP
\fBNOTE\fR: clean\-ruv is \fBVERY DANGEROUS\fR. Execution against the wrong replication ID can result in inconsistent data on that master. The master should be re\-initialized from another if this happens.
.SH "OPTIONS" .SH "OPTIONS"
.TP .TP
\fB\-H\fR \fIHOST\fR, \fB\-\-host\fR=\fIHOST\fR \fB\-H\fR \fIHOST\fR, \fB\-\-host\fR=\fIHOST\fR
@@ -112,6 +130,11 @@ Completely remove a replica:
# ipa\-replica\-manage del srv4.example.com # ipa\-replica\-manage del srv4.example.com
.TP .TP
Using connect/disconnect you can manage the replication topology. Using connect/disconnect you can manage the replication topology.
.TP
List the replication IDs in use:
# ipa\-replica\-manage list\-ruv
srv1.example.com:389: 7
srv2.example.com:389: 4
.SH "WINSYNC" .SH "WINSYNC"
Creating a Windows AD Synchronization agreement is similar to creating an IPA replication agreement, there are just a couple of extra steps. Creating a Windows AD Synchronization agreement is similar to creating an IPA replication agreement, there are just a couple of extra steps.

View File

@@ -0,0 +1,4 @@
# Let a delegated user put the database into read-only mode when deleting
# an agreement.
dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
add:aci: '(targetattr=nsslapd-readonly)(version 3.0; acl "Allow marking the database readonly"; allow (write) groupdn = "ldap:///cn=Remove Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)'

View File

@@ -26,6 +26,7 @@ app_DATA = \
25-referint.update \ 25-referint.update \
30-s4u2proxy.update \ 30-s4u2proxy.update \
40-delegation.update \ 40-delegation.update \
40-replication.update \
40-dns.update \ 40-dns.update \
40-automember.update \ 40-automember.update \
45-roles.update \ 45-roles.update \

View File

@@ -1103,3 +1103,71 @@ class ReplicationManager(object):
if err: if err:
raise err #pylint: disable=E0702 raise err #pylint: disable=E0702
def set_readonly(self, readonly, critical=False):
"""
Set the database readonly status.
@readonly: boolean for read-only status
@critical: boolean to raise an exception on failure, default False.
"""
dn = DN(('cn', 'userRoot'), ('cn', 'ldbm database'),
('cn', 'plugins'), ('cn', 'config'))
mod = [(ldap.MOD_REPLACE, 'nsslapd-readonly', 'on' if readonly else 'off')]
try:
self.conn.modify_s(dn, mod)
except ldap.INSUFFICIENT_ACCESS, e:
# We can't modify the read-only status on the remote server.
# This usually isn't a show-stopper.
if critical:
raise e
root_logger.debug("No permission to modify replica read-only status, continuing anyway")
def cleanallruv(self, replicaId):
"""
Create a CLEANALLRUV task and monitor it until it has
completed.
"""
root_logger.debug("Creating CLEANALLRUV task for replica id %d" % replicaId)
dn = DN(('cn', 'clean %d' % replicaId), ('cn', 'cleanallruv'),('cn', 'tasks'), ('cn', 'config'))
e = ipaldap.Entry(dn)
e.setValues('objectclass', ['top', 'extensibleObject'])
e.setValue('replica-base-dn', api.env.basedn)
e.setValue('replica-id', replicaId)
e.setValue('cn', 'clean %d' % replicaId)
try:
self.conn.addEntry(e)
except errors.DuplicateEntry:
print "CLEANALLRUV task for replica id %d already exists." % replicaId
else:
print "Background task created to clean replication data. This may take a while."
print "This may be safely interrupted with Ctrl+C"
self.conn.checkTask(dn, dowait=True)
def abortcleanallruv(self, replicaId):
"""
Create a task to abort a CLEANALLRUV operation.
"""
root_logger.debug("Creating task to abort a CLEANALLRUV operation for replica id %d" % replicaId)
dn = DN(('cn', 'abort %d' % replicaId), ('cn', 'abort cleanallruv'),('cn', 'tasks'), ('cn', 'config'))
e = ipaldap.Entry(dn)
e.setValues('objectclass', ['top', 'extensibleObject'])
e.setValue('replica-base-dn', api.env.basedn)
e.setValue('replica-id', replicaId)
e.setValue('cn', 'abort %d' % replicaId)
try:
self.conn.addEntry(e)
except errors.DuplicateEntry:
print "An abort CLEANALLRUV task for replica id %d already exists." % replicaId
else:
print "Background task created. This may take a while."
print "This may be safely interrupted with Ctrl+C"
self.conn.checkTask(dn, dowait=True)