mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
committed by
Martin Kosek
parent
c0630950a1
commit
c9c55a2845
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
4
install/updates/40-replication.update
Normal file
4
install/updates/40-replication.update
Normal 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";)'
|
||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user