summaryrefslogtreecommitdiffstats
path: root/install/tools/ipa-replica-manage
diff options
context:
space:
mode:
Diffstat (limited to 'install/tools/ipa-replica-manage')
-rwxr-xr-xinstall/tools/ipa-replica-manage288
1 files changed, 286 insertions, 2 deletions
diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index fb80ca752..27a7454ab 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -23,6 +23,7 @@ import os
import re, krbV
import traceback
from urllib2 import urlparse
+import ldap
from ipapython import ipautil
from ipaserver.install import replication, dsinstance, installutils
@@ -34,6 +35,7 @@ from ipapython.ipa_log_manager import *
from ipapython.dn import DN
from ipapython.config import IPAOptionParser
from ipaclient import ipadiscovery
+from xmlrpclib import MAXINT
CACERT = "/etc/ipa/ca.crt"
@@ -52,6 +54,10 @@ commands = {
"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, "", ""),
+ "dnarange-show":(0, 1, "[master fqdn]", ""),
+ "dnanextrange-show":(0, 1, "", ""),
+ "dnarange-set":(2, 2, "<master fqdn> <range>", "must provide a master and ID range"),
+ "dnanextrange-set":(2, 2, "<master fqdn> <range>", "must provide a master and ID range"),
}
@@ -124,6 +130,9 @@ def test_connection(realm, host):
# We do a search in cn=config. NotFound in this case means no
# permission
return False
+ except ldap.LOCAL_ERROR:
+ # more than likely a GSSAPI error
+ return False
def list_replicas(realm, host, replica, dirman_passwd, verbose):
@@ -147,7 +156,7 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose):
dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm))
try:
entries = conn.get_entries(dn, conn.SCOPE_ONELEVEL)
- except:
+ except Exception:
print "Failed to read master data from '%s': %s" % (host, str(e))
return
else:
@@ -157,7 +166,7 @@ def list_replicas(realm, host, replica, dirman_passwd, verbose):
dn = DN(('cn', 'replicas'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm))
try:
entries = conn.get_entries(dn, conn.SCOPE_ONELEVEL)
- except:
+ except Exception:
pass
else:
for ent in entries:
@@ -272,6 +281,15 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
repl2.force_sync(repl2.conn, replica1)
cn, dn = repl2.agreement_dn(repl1.conn.host)
repl2.wait_for_repl_update(repl2.conn, dn, 30)
+ (range_start, range_max) = repl2.get_DNA_range(repl2.conn.host)
+ (next_start, next_max) = repl2.get_DNA_next_range(repl2.conn.host)
+ if range_start is not None:
+ if not store_DNA_range(repl1, range_start, range_max, repl2.conn.host, realm, dirman_passwd):
+ print "Unable to save DNA range %d-%d" % (range_start, range_max)
+ if next_start is not None:
+ if not store_DNA_range(repl1, next_start, next_max, repl2.conn.host, realm, dirman_passwd):
+ print "Unable to save DNA range %d-%d" % (next_start, next_max)
+ repl2.set_readonly(readonly=False)
repl2.delete_agreement(replica1)
repl2.delete_referral(replica1)
repl2.set_readonly(readonly=False)
@@ -282,11 +300,13 @@ def del_link(realm, replica1, replica2, dirman_passwd, force=False):
if failed:
if force:
print "Forcing removal on '%s'" % replica1
+ print "Any DNA range on '%s' will be lost" % replica2
else:
return False
if not repl2 and force:
print "Forcing removal on '%s'" % replica1
+ print "Any DNA range on '%s' will be lost" % replica2
repl1.delete_agreement(replica2)
repl1.delete_referral(replica2)
@@ -833,6 +853,254 @@ def force_sync(realm, thishost, fromhost, dirman_passwd):
repl = replication.ReplicationManager(realm, fromhost, dirman_passwd)
repl.force_sync(repl.conn, thishost)
+def show_DNA_ranges(hostname, master, realm, dirman_passwd, nextrange=False):
+ """
+ Display the DNA ranges for all current masters.
+
+ hostname: hostname of the master we're listing from
+ master: specific master to show, or None for all
+ realm: our realm, needed to create a connection
+ dirman_passwd: the DM password, needed to create a connection
+ nextrange: if False then show main range, if True then show next
+
+ Returns nothing
+ """
+ for check_host in [hostname, master]:
+ enforce_host_existence(check_host)
+
+ try:
+ repl = replication.ReplicationManager(realm, hostname, dirman_passwd)
+ except Exception, e:
+ sys.exit("Connection failed: %s" % e)
+ dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), repl.suffix)
+ try:
+ entries = repl.conn.get_entries(dn, repl.conn.SCOPE_ONELEVEL)
+ except Exception:
+ return False
+
+ for ent in entries:
+ remote = ent.single_value('cn')
+ if master is not None and remote != master:
+ continue
+ try:
+ repl2 = replication.ReplicationManager(realm, remote, dirman_passwd)
+ except Exception, e:
+ print "%s: Connection failed: %s" % (remote, e)
+ continue
+ if not nextrange:
+ try:
+ (start, max) = repl2.get_DNA_range(remote)
+ except errors.NotFound:
+ print "%s: No permission to read DNA configuration" % remote
+ continue
+ if start is None:
+ print "%s: No range set" % remote
+ else:
+ print "%s: %s-%s" % (remote, start, max)
+ else:
+ try:
+ (next_start, next_max) = repl2.get_DNA_next_range(remote)
+ except errors.NotFound:
+ print "%s: No permission to read DNA configuration" % remote
+ continue
+ if next_start is None:
+ print "%s: No on-deck range set" % remote
+ else:
+ print "%s: %s-%s" % (remote, next_start, next_max)
+
+ return False
+
+
+def store_DNA_range(repl, range_start, range_max, deleted_master, realm,
+ dirman_passwd):
+ """
+ Given a DNA range try to save it in a remaining master in the
+ on-deck (dnaNextRange) value.
+
+ Return True if range was saved, False if not
+
+ This function focuses on finding an available master.
+
+ repl: ReplicaMaster object for the master we're deleting from
+ range_start: The DNA next value
+ range_max: The DNA max value
+ deleted_master: The hostname of the master to be deleted
+ realm: our realm, needed to create a connection
+ dirman_passwd: the DM password, needed to create a connection
+ """
+ dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), repl.suffix)
+ try:
+ entries = repl.conn.get_entries(dn, repl.conn.SCOPE_ONELEVEL)
+ except Exception:
+ return False
+
+ for ent in entries:
+ candidate = ent.single_value('cn')
+ if candidate == deleted_master:
+ continue
+ try:
+ repl2 = replication.ReplicationManager(realm, candidate, dirman_passwd)
+ except Exception, e:
+ print "Connection failed: %s" % e
+ continue
+ (next_start, next_max) = repl2.get_DNA_next_range(candidate)
+ if next_start is None:
+ try:
+ return repl2.save_DNA_next_range(range_start, range_max)
+ except Exception, e:
+ print '%s: %s' % (candidate, e)
+
+ return False
+
+
+def set_DNA_range(hostname, range, realm, dirman_passwd, next_range=False):
+ """
+ Given a DNA range try to change it on the designated master.
+
+ The range must not overlap with any other ranges and must be within
+ one of the IPA local ranges as defined in cn=ranges.
+
+ Setting an on-deck range of 0-0 removes the range.
+
+ Return True if range was saved, False if not
+
+ hostname: hostname of the master to set the range on
+ range: The DNA range to set
+ realm: our realm, needed to create a connection
+ dirman_passwd: the DM password, needed to create a connection
+ next_range: if True then setting a next-range, otherwise a DNA range.
+ """
+ def validate_range(range, allow_all_zero=False):
+ """
+ Do some basic sanity checking on the range.
+
+ Returns None if ok, a string if an error.
+ """
+ try:
+ (dna_next, dna_max) = range.split('-', 1)
+ except ValueError, e:
+ return "Invalid range, must be the form x-y"
+
+ try:
+ dna_next = int(dna_next)
+ dna_max = int(dna_max)
+ except ValueError:
+ return "The range must consist of integers"
+
+ if dna_next == 0 and dna_max == 0 and allow_all_zero:
+ return None
+
+ if dna_next <= 0 or dna_max <= 0 or dna_next >= MAXINT or dna_max >= MAXINT:
+ return "The range must consist of positive integers between 1 and %d" % MAXINT
+
+ if dna_next >= dna_max:
+ return "Invalid range"
+
+ return None
+
+ def range_intersection(s1, s2, r1, r2):
+ return max(s1, r1) <= min(s2, r2)
+
+ enforce_host_existence(hostname)
+
+ err = validate_range(range, allow_all_zero=next_range)
+ if err is not None:
+ sys.exit(err)
+
+ # Normalize the range
+ (dna_next, dna_max) = range.split('-', 1)
+ dna_next = int(dna_next)
+ dna_max = int(dna_max)
+
+ try:
+ repl = replication.ReplicationManager(realm, hostname, dirman_passwd)
+ except Exception, e:
+ sys.exit("Connection failed: %s" % e)
+ if dna_next > 0:
+ # Verify that the new range doesn't overlap with an existing range
+ dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), repl.suffix)
+ try:
+ entries = repl.conn.get_entries(dn, repl.conn.SCOPE_ONELEVEL)
+ except Exception, e:
+ sys.exit("Failed to read master data from '%s': %s" % (repl.conn.host, str(e)))
+ else:
+ for ent in entries:
+ master = ent.single_value('cn')
+ if master == hostname and not next_range:
+ continue
+ try:
+ repl2 = replication.ReplicationManager(realm, master, dirman_passwd)
+ except Exception, e:
+ print "Connection to %s failed: %s" % (master, e)
+ print "Overlap not checked."
+ continue
+ try:
+ (entry_start, entry_max) = repl2.get_DNA_range(master)
+ except errors.NotFound:
+ print "%s: No permission to read DNA configuration" % master
+ continue
+ if (entry_start is not None and
+ range_intersection(entry_start, entry_max,
+ dna_next, dna_max)):
+ sys.exit("New range overlaps the DNA range on %s" % master)
+ (entry_start, entry_max) = repl2.get_DNA_next_range(master)
+ if (entry_start is not None and
+ range_intersection(entry_start, entry_max,
+ dna_next, dna_max)):
+ sys.exit("New range overlaps the DNA next range on %s" % master)
+ del(repl2)
+
+ # Verify that this is within one of the IPA domain ranges.
+ dn = DN(('cn','ranges'), ('cn','etc'), repl.suffix)
+ try:
+ entries = repl.conn.get_entries(dn, repl.conn.SCOPE_ONELEVEL,
+ "(objectclass=ipaDomainIDRange)")
+ except errors.NotFound, e:
+ sys.exit('Unable to load IPA ranges: %s' % e.message)
+
+ for ent in entries:
+ entry_start = int(ent.single_value('ipabaseid'))
+ entry_max = entry_start + int(ent.single_value('ipaidrangesize'))
+ if dna_next >= entry_start and dna_max <= entry_max:
+ break
+ else:
+ sys.exit("New range does not fit within existing IPA ranges. See ipa help idrange command")
+
+ # If this falls within any of the AD ranges then it fails.
+ try:
+ entries = repl.conn.get_entries(dn, repl.conn.SCOPE_BASE,
+ "(objectclass=ipatrustedaddomainrange)")
+ except errors.NotFound:
+ entries = []
+
+ for ent in entries:
+ entry_start = int(ent.single_value('ipabaseid'))
+ entry_max = entry_start + int(ent.single_value('ipaidrangesize'))
+ if range_intersection(dna_next, dna_max, entry_start, entry_max):
+ sys.exit("New range overlaps with a Trust range. See ipa help idrange command")
+
+ if next_range:
+ try:
+ if not repl.save_DNA_next_range(dna_next, dna_max):
+ sys.exit("Updating next range failed")
+ except errors.EmptyModlist:
+ sys.exit("No changes to make")
+ except errors.NotFound:
+ sys.exit("No permission to update ranges")
+ except Exception, e:
+ sys.exit("Updating next range failed: %s" % e)
+ else:
+ try:
+ if not repl.save_DNA_range(dna_next, dna_max):
+ sys.exit("Updating range failed")
+ except errors.EmptyModlist:
+ sys.exit("No changes to make")
+ except errors.NotFound:
+ sys.exit("No permission to update ranges")
+ except Exception, e:
+ sys.exit("Updating range failed: %s" % e)
+
+
def main():
if os.getegid() == 0:
installutils.check_server_configuration()
@@ -915,6 +1183,22 @@ def main():
abort_clean_ruv(realm, args[1], options)
elif args[0] == "list-clean-ruv":
list_clean_ruv(realm, host, dirman_passwd, options.verbose)
+ elif args[0] == "dnarange-show":
+ if len(args) == 2:
+ master = args[1]
+ else:
+ master = None
+ show_DNA_ranges(host, master, realm, dirman_passwd, False)
+ elif args[0] == "dnanextrange-show":
+ if len(args) == 2:
+ master = args[1]
+ else:
+ master = None
+ show_DNA_ranges(host, master, realm, dirman_passwd, True)
+ elif args[0] == "dnarange-set":
+ set_DNA_range(args[1], args[2], realm, dirman_passwd, next_range=False)
+ elif args[0] == "dnanextrange-set":
+ set_DNA_range(args[1], args[2], realm, dirman_passwd, next_range=True)
try:
main()