summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2013-03-01 15:02:14 -0500
committerRob Crittenden <rcritten@redhat.com>2013-03-13 10:32:36 -0400
commit9005b9bc8aac7c1381aadb7d17107ebbebae005d (patch)
tree15c0b3ce22d95e265130bf8fcb815bdf79e6b4dc
parent63407ed477035765dda38fbead1353d4f47ac26a (diff)
downloadfreeipa-9005b9bc8aac7c1381aadb7d17107ebbebae005d.tar.gz
freeipa-9005b9bc8aac7c1381aadb7d17107ebbebae005d.tar.xz
freeipa-9005b9bc8aac7c1381aadb7d17107ebbebae005d.zip
Extend ipa-replica-manage to be able to manage DNA ranges.
Attempt to automatically save DNA ranges when a master is removed. This is done by trying to find a master that does not yet define a DNA on-deck range. If one can be found then the range on the deleted master is added. If one cannot be found then it is reported as an error. Some validation of the ranges are done to ensure that they do overlap an IPA local range and do not overlap existing DNA ranges configured on other masters. http://freeipa.org/page/V3/Recover_DNA_Ranges https://fedorahosted.org/freeipa/ticket/3321
-rw-r--r--install/share/delegation.ldif9
-rw-r--r--install/share/replica-acis.ldif5
-rwxr-xr-xinstall/tools/ipa-replica-manage288
-rw-r--r--install/tools/man/ipa-replica-manage.145
-rw-r--r--install/updates/40-replication.update12
-rw-r--r--ipapython/ipaldap.py2
-rw-r--r--ipaserver/install/dsinstance.py3
-rw-r--r--ipaserver/install/replication.py98
8 files changed, 453 insertions, 9 deletions
diff --git a/install/share/delegation.ldif b/install/share/delegation.ldif
index f62062fe4..14069586c 100644
--- a/install/share/delegation.ldif
+++ b/install/share/delegation.ldif
@@ -545,6 +545,15 @@ cn: Remove Replication Agreements
ipapermissiontype: SYSTEM
member: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX
+dn: cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: groupofnames
+objectClass: ipapermission
+cn: Modify DNA Range
+ipapermissiontype: SYSTEM
+member: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX
+
# Entitlement management
dn: cn=Register Entitlements,cn=permissions,cn=pbac,$SUFFIX
diff --git a/install/share/replica-acis.ldif b/install/share/replica-acis.ldif
index 65dfb7a66..f4e96139f 100644
--- a/install/share/replica-acis.ldif
+++ b/install/share/replica-acis.ldif
@@ -20,6 +20,11 @@ changetype: modify
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";)
+dn: cn=Posix IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
+changetype: modify
+add: aci
+aci: (targetattr=dnaNextRange || dnaNextValue || dnaMaxValue)(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)
+
dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
changetype: modify
add: aci
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()
diff --git a/install/tools/man/ipa-replica-manage.1 b/install/tools/man/ipa-replica-manage.1
index 836743902..d00101990 100644
--- a/install/tools/man/ipa-replica-manage.1
+++ b/install/tools/man/ipa-replica-manage.1
@@ -16,13 +16,13 @@
.\"
.\" Author: Rob Crittenden <rcritten@redhat.com>
.\"
-.TH "ipa-replica-manage" "1" "Mar 14 2008" "FreeIPA" "FreeIPA Manual Pages"
+.TH "ipa-replica-manage" "1" "Mar 1 2013" "FreeIPA" "FreeIPA Manual Pages"
.SH "NAME"
ipa\-replica\-manage \- Manage an IPA replica
.SH "SYNOPSIS"
-ipa\-replica\-manage [\fIOPTION\fR]... [connect|disconnect|del|list|re\-initialize|force\-sync]
+ipa\-replica\-manage [\fIOPTION\fR]... [COMMAND]
.SH "DESCRIPTION"
-Manages the replication agreements of an IPA server.
+Manages the replication agreements of an IPA server. The available commands are:
.TP
\fBconnect\fR [SERVER_A] <SERVER_B>
\- Adds a new replication agreement between SERVER_A/localhost and SERVER_B
@@ -54,6 +54,18 @@ Manages the replication agreements of an IPA server.
\fBlist\-clean\-ruv\fR
\- List all running CLEANALLRUV and abort CLEANALLRUV tasks.
.TP
+\fBdnarange\-show [SERVER]\fR
+\- List the DNA ranges
+.TP
+\fBdnarange\-set SERVER START\-END\fR
+\- Set the DNA range on a master
+.TP
+\fBdnanextrange\-show [SERVER]\fR
+\- List the next DNA ranges
+.TP
+\fBdnanextrange\-set SERVER START\-END\fR
+\- Set the DNA next range on a master
+.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.
.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.
@@ -90,7 +102,7 @@ Provide additional information
Ignore some types of errors, don't prompt when deleting a master
.TP
\fB\-c\fR, \fB\-\-cleanup\fR
-When deleting a master with the --force flag, remove leftover references to an already deleted master.
+When deleting a master with the \-\-force flag, remove leftover references to an already deleted master.
.TP
\fB\-\-binddn\fR=\fIADMIN_DN\fR
Bind DN to use with remote server (default is cn=Directory Manager) \- Be careful to quote this value on the command line
@@ -112,6 +124,29 @@ Password for the IPA system user used by the Windows PassSync plugin to synchron
.TP
\fB\-\-from\fR=\fISERVER\fR
The server to pull the data from, used by the re\-initialize and force\-sync commands.
+.SH "RANGES"
+IPA uses the 389\-ds Distributed Numeric Assignment (DNA) Plugin to allocate POSIX ids for users and groups. A range is created when IPA is installed and half the range is assigned to the first IPA master for the purposes of allocation.
+.TP
+New IPA masters do not automatically get a DNA range assignment. A range assignment is done only when a user or POSIX group is added on that master.
+.TP
+The DNA plugin also supports an "on\-deck" or next range configuration. When the primary range is exhaused, rather than going to another master to ask for more, it will use its on\-deck range if one is defined. Each master can have only one range and one on\-deck range defined.
+.TP
+When a master is removed an attempt is made to save its DNA range(s) onto another master in its on\-deck range. IPA will not attempt to extend or merge ranges. If there are no available on\-deck range slots then this is reported to the user. The range is effectively lost unless it is manually merged into the range of another master.
+.TP
+The DNA range and on\-deck (next) values can be managed using the dnarange\-set and dnanextrange\-set commands. The rules for managing these ranges are:
+\- The range must be completely contained within a local range as defined by the ipa idrange command.
+
+\- The range cannot overlap the DNA range or on\-deck range on another IPA master.
+
+\- The range cannot overlap the ID range of an AD Trust.
+
+\- The primary DNA range cannot be removed.
+
+\- An on\-deck range range can be removed by setting it to 0\-0. The assumption is that the range will be manually moved or merged elsewhere.
+.TP
+The range and next range of a specific master can be displayed by passing the FQDN of that master to the dnarange\-show or dnanextrange\-show command.
+.TP
+Performing range changes as a delegated administrator (e.g. not using the Directory Manager password) requires additional 389\-ds ACIs. These are installed in upgraded masters but not existing ones. The changs are made in cn=config which is not replicated. The result is that DNA ranges cannot be managed on non\-upgraded masters as a delegated administrator.
.SH "EXAMPLES"
.TP
List all masters:
@@ -162,7 +197,7 @@ The following examples use the AD administrator account as the synchronization u
2. Remove any existing kerberos credentials
# kdestroy
.TP
-3) Add the winsync replication agreement
+3. Add the winsync replication agreement
# ipa\-replica\-manage connect \-\-winsync \-\-passsync=<bindpwd_for_syncuser_that will_be_used_for_agreement> \-\-cacert=/path/to/adscacert/WIN\-CA.cer \-\-binddn "cn=administrator,cn=users,dc=ad,dc=example,dc=com" \-\-bindpw <ads_administrator_password> \-v <adserver.fqdn>
.TP
You will be prompted to supply the Directory Manager's password.
diff --git a/install/updates/40-replication.update b/install/updates/40-replication.update
index f9e0496be..619d14663 100644
--- a/install/updates/40-replication.update
+++ b/install/updates/40-replication.update
@@ -2,3 +2,15 @@
# 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";)'
+
+# Add rules to manage DNA ranges
+dn: cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Modify DNA Range
+default:ipapermissiontype: SYSTEM
+default:member: cn=Replication Administrators,cn=privileges,cn=pbac,$SUFFIX
+
+dn: cn=Posix IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config
+add:aci: '(targetattr=dnaNextRange || dnaNextValue || dnaMaxValue)(version 3.0;acl "permission:Modify DNA Range";allow (write) groupdn = "ldap:///cn=Modify DNA Range,cn=permissions,cn=pbac,$SUFFIX";)'
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 10492d178..6873511c4 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1806,6 +1806,8 @@ class IPAdmin(LDAPClient):
if removes:
if not force_replace:
modlist.append((ldap.MOD_DELETE, key, removes))
+ elif new_values == []: # delete an empty value
+ modlist.append((ldap.MOD_DELETE, key, removes))
return modlist
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 23843d759..c744c9ca9 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -218,6 +218,7 @@ class DsInstance(service.Service):
self.step("adding master entry", self.__add_master_entry)
self.step("configuring Posix uid/gid generation",
self.__config_uidgid_gen)
+ self.step("adding replication acis", self.__add_replication_acis)
self.step("enabling compatibility plugin",
self.__enable_compat_plugin)
self.step("tuning directory server", self.__tuning)
@@ -253,7 +254,6 @@ class DsInstance(service.Service):
self.step("adding default layout", self.__add_default_layout)
self.step("adding delegation layout", self.__add_delegation_layout)
- self.step("adding replication acis", self.__add_replication_acis)
self.step("creating container for managed entries", self.__managed_entries)
self.step("configuring user private groups", self.__user_private_groups)
self.step("configuring netgroups from hostgroups", self.__host_nis_groups)
@@ -284,7 +284,6 @@ class DsInstance(service.Service):
self.__common_setup(True)
self.step("setting up initial replication", self.__setup_replica)
- self.step("adding replication acis", self.__add_replication_acis)
# See LDIFs for automember configuration during replica install
self.step("setting Auto Member configuration", self.__add_replica_automember_config)
self.step("enabling S4U2Proxy delegation", self.__setup_s4u2proxy)
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 076d4f87f..c1ca0aaa6 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -37,6 +37,7 @@ IPA_USER_CONTAINER = DN(('cn', 'users'), ('cn', 'accounts'))
PORT = 636
TIMEOUT = 120
REPL_MAN_DN = DN(('cn', 'replication manager'), ('cn', 'config'))
+DNA_DN = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config'))
IPA_REPLICA = 1
WINSYNC = 2
@@ -1307,3 +1308,100 @@ class ReplicationManager(object):
print "This may be safely interrupted with Ctrl+C"
wait_for_task(self.conn, dn)
+
+ def get_DNA_range(self, hostname):
+ """
+ Return the DNA range on this server as a tuple, (next, max), or
+ (None, None) if no range has been assigned yet.
+
+ Raises an exception on errors reading an entry.
+ """
+ entry = self.conn.get_entry(DNA_DN)
+
+ nextvalue = int(entry.single_value("dnaNextValue", 0))
+ maxvalue = int(entry.single_value("dnaMaxValue", 0))
+
+ sharedcfgdn = entry.single_value("dnaSharedCfgDN", None)
+ if sharedcfgdn is not None:
+ sharedcfgdn = DN(sharedcfgdn)
+
+ shared_entry = self.conn.get_entry(sharedcfgdn)
+ remaining = int(shared_entry.single_value("dnaRemainingValues", 0))
+ else:
+ remaining = 0
+
+ if nextvalue == 0 and maxvalue == 0:
+ return (None, None)
+
+ # Check the magic values for an unconfigured DNA entry
+ if maxvalue == 1100 and nextvalue == 1101 and remaining == 0:
+ return (None, None)
+ else:
+ return (nextvalue, maxvalue)
+
+ def get_DNA_next_range(self, hostname):
+ """
+ Return the DNA "on-deck" range on this server as a tuple, (next, max),
+ or
+ (None, None) if no range has been assigned yet.
+
+ Raises an exception on errors reading an entry.
+ """
+ entry = self.conn.get_entry(DNA_DN)
+
+ range = entry.single_value("dnaNextRange", None)
+
+ if range is None:
+ return (None, None)
+
+ try:
+ (next, max) = range.split('-')
+ except ValueError:
+ # Should not happen, malformed entry, return nothing.
+ return (None, None)
+
+ return (int(next), int(max))
+
+ def save_DNA_next_range(self, next_start, next_max):
+ """
+ Save a DNA range into the on-deck value.
+
+ This adds a dnaNextRange value to the DNA configuration. This
+ attribute takes the form of start-next.
+
+ Returns True on success.
+ Returns False if the range is already defined.
+ Raises an exception on failure.
+ """
+ entry = self.conn.get_entry(DNA_DN)
+
+ range = entry.single_value("dnaNextRange", None)
+
+ if range is not None and next_start != 0 and next_max != 0:
+ return False
+
+ if next_start == 0 and next_max == 0:
+ entry["dnaNextRange"] = None
+ else:
+ entry["dnaNextRange"] = "%s-%s" % (next_start, next_max)
+
+ self.conn.update_entry(entry)
+
+ return True
+
+ def save_DNA_range(self, next_start, next_max):
+ """
+ Save a DNA range.
+
+ This is potentially very dangerous.
+
+ Returns True on success. Raises an exception on failure.
+ """
+ entry = self.conn.get_entry(DNA_DN)
+
+ entry["dnaNextValue"] = next_start
+ entry["dnaMaxValue"] = next_max
+
+ self.conn.update_entry(entry)
+
+ return True