Implement the design at http://freeipa.org/page/V3/Recover_DNA_Ranges
Note that this required some new ACIs in cn=config which is not replicated so the range-set commands won't work against older instances. It should be gracefully handled though.
It also doesn't work so well if you try it using a delegated administrator, see ticket https://fedorahosted.org/freeipa/ticket/3480
rob
>From 9a654b0b3730f8d9058dfbf25a93a58e1f4939e7 Mon Sep 17 00:00:00 2001 From: Rob Crittenden <[email protected]> Date: Fri, 1 Mar 2013 15:02:14 -0500 Subject: [PATCH] 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. https://fedorahosted.org/freeipa/ticket/3321 --- install/share/delegation.ldif | 9 ++ install/share/replica-acis.ldif | 5 + install/tools/ipa-replica-manage | 258 ++++++++++++++++++++++++++++++++- install/tools/man/ipa-replica-manage.1 | 41 +++++- install/updates/40-replication.update | 12 ++ ipaserver/install/replication.py | 120 +++++++++++++++ ipaserver/ipaldap.py | 2 + 7 files changed, 437 insertions(+), 10 deletions(-) diff --git a/install/share/delegation.ldif b/install/share/delegation.ldif index f62062fe498634d56128ebf78874c3ba91d7d09b..14069586cf1f1021d281a3e86133de1535b62559 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 65dfb7a669965731dfd2c6ac1efd99209a2ea404..44c73f8ab28ae62d0603ec1c9abbe1de17b34ebc 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 859809bf1c301913c3eb7fc92d1ed58675609b8c..6c0b45620dd2deabfc11ef2249b18205fb23b1fd 100755 --- a/install/tools/ipa-replica-manage +++ b/install/tools/ipa-replica-manage @@ -34,6 +34,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 +53,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"), } @@ -272,6 +277,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.getDNARange(repl2.conn.host) + (next_start, next_max) = repl2.getDNANextRange(repl2.conn.host) + if range_start is not None: + if not storeDNARange(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 storeDNARange(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 +296,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) @@ -632,11 +648,14 @@ def del_master(realm, hostname, options): this_services = [] other_services = [] - for master_cn in [m.getValue('cn') for m in masters]: + for master_cn in [m.single_value('cn') for m in masters]: master_dn = DN(('cn', master_cn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) - services = delrepl.conn.get_entries(master_dn, - delrepl.conn.SCOPE_ONELEVEL) - services_cns = [s.getValue('cn') for s in services] + try: + services = delrepl.conn.get_entries(master_dn, + delrepl.conn.SCOPE_ONELEVEL) + except errors.NotFound: + continue + services_cns = [s.single_value('cn') for s in services] if master_cn == hostname: this_services = services_cns @@ -670,7 +689,7 @@ def del_master(realm, hostname, options): "from %s: %s" % (hostname, r, e)) # 5. Clean RUV for the deleted master - if repltype == replication.IPA_REPLICA: + if repltype == replication.IPA_REPLICA and rid is not None: try: thisrepl.cleanallruv(rid) except KeyboardInterrupt: @@ -833,6 +852,219 @@ def force_sync(realm, thishost, fromhost, dirman_passwd): repl = replication.ReplicationManager(realm, fromhost, dirman_passwd) repl.force_sync(repl.conn, thishost) +def showDNARanges(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" % ipautil.convert_ldap_error(e)) + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), repl.suffix) + try: + entries = repl.conn.get_entries(dn, repl.conn.SCOPE_ONELEVEL) + except: + 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, ipautil.convert_ldap_error(e)) + continue + if not nextrange: + (start, max) = repl2.getDNARange(remote) + if start is None: + print "%s: No range set" % remote + else: + print "%s: %s-%s" % (remote, start, max) + else: + (next_start, next_max) = repl2.getDNANextRange(remote) + 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 storeDNARange(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: + 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" % ipautil.convert_ldap_error(e) + continue + (next_start, next_max) = repl2.getDNANextRange(candidate) + if next_start is None: + return repl2.saveDNANextRange(range_start, range_max) + + return False + + +def setDNARange(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 + """ + 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): + overlap = xrange(max(s1, r1), min(s2, r2) + 1) + return len(overlap) > 0 + + 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" % ipautil.convert_ldap_error(e)) + + if dna_next > 0 or dna_max > 0: + # Verify that the new range doesn't overlap with an existing range + dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) + 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') + (entry_start, entry_max) = repl.getDNARange(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 range on %s" % master) + (entry_start, entry_max) = repl.getDNANextRange(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) + + # 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) + + failed = 0 + for ent in entries: + entry_start = int(ent.single_value('ipabaseid')) + entry_max = entry_start + int(ent.single_value('ipaidrangesize')) + if not range_intersection(dna_next, dna_max, entry_start, entry_max): + failed += 1 + + if failed == len(entries): + 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: + if not repl.saveDNANextRange(dna_next, dna_max): + sys.exit("Updating next range failed") + else: + if not repl.saveDNARange(dna_next, dna_max): + sys.exit("Updating range failed") + + def main(): if os.getegid() == 0: installutils.check_server_configuration() @@ -915,6 +1147,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 + showDNARanges(host, master, realm, dirman_passwd, False) + elif args[0] == "dnanextrange-show": + if len(args) == 2: + master = args[1] + else: + master = None + showDNARanges(host, master, realm, dirman_passwd, True) + elif args[0] == "dnarange-set": + setDNARange(args[1], args[2], realm, dirman_passwd, next_range=False) + elif args[0] == "dnanextrange-set": + setDNARange(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 836743902278ec2273f3ce7a7fbf3992370c4828..d23e2566eb9a22c70991cbdca0140eb1d268533c 100644 --- a/install/tools/man/ipa-replica-manage.1 +++ b/install/tools/man/ipa-replica-manage.1 @@ -16,13 +16,13 @@ .\" .\" Author: Rob Crittenden <[email protected]> .\" -.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 +\fBipadnarange\-show [SERVER]\fR +\- List the DNA ranges +.TP +\fBipadnarange\-set SERVER x\-y\fR +\- Set the DNA range on a master +.TP +\fBipadnanextrange\-show [SERVER]\fR +\- List the next DNA ranges +.TP +\fBipadnanextrange\-set SERVER x\-y\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,25 @@ 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 overlap 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 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. .SH "EXAMPLES" .TP List all masters: @@ -162,7 +193,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 f9e0496be336ec7653e6b1688ad28245014ce6a0..619d14663eeb6f692864c960dfd3542fc22cb581 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/ipaserver/install/replication.py b/ipaserver/install/replication.py index 804d046bf2553daa4aded5c23436a98636e20da0..9076c8396041a95c7ea01ef15aa77991516d30e6 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -1308,3 +1308,123 @@ class ReplicationManager(object): print "This may be safely interrupted with Ctrl+C" wait_for_task(self.conn, dn) + + def getDNARange(self, hostname): + """ + Return the DNA range on this server as a tuple, (next, max), or + (None, None) if no range has been assigned yet. + """ + dn = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config')) + try: + entry = self.conn.get_entry(dn) + except Exception, e: + print "Unable to read DNA configuration: %s" % e.message + return (None, None) + + 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) + + if (nextvalue > maxvalue and maxvalue == 1100 and + nextvalue == 1101 and remaining == 0): + return (None, None) + else: + return (nextvalue, maxvalue) + + def getDNANextRange(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. + """ + dn = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config')) + + try: + entry = self.conn.get_entry(dn) + except Exception, e: + print "Unable to read DNA configuration: %s" % e.message + return (None, None) + + 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 saveDNANextRange(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. + """ + dn = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config')) + try: + entry = self.conn.get_entry(dn) + except Exception, e: + print "Unable to read DNA configuration: %s" % e.message + return (None, None) + + range = entry.single_value("dnaNextRange", None) + + if range is not None and next_start != 0 and next_max != 0: + print "Range already defined" + return False + + if next_start == 0 and next_max == 0: + entry.delAttr("dnaNextRange") + else: + entry["dnaNextRange"] = "%s-%s" % (next_start, next_max) + try: + self.conn.update_entry(entry) + except Exception, e: + print e + return False + else: + return True + + def saveDNARange(self, next_start, next_max): + """ + Save a DNA range. + + This is potentially very dangerous. + + Returns True on success. + """ + dn = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config')) + try: + entry = self.conn.get_entry(dn) + except Exception, e: + print "Unable to read DNA configuration: %s" % e.message + return (None, None) + + entry["dnaNextValue"] = next_start + entry["dnaMaxValue"] = next_max + + try: + self.conn.update_entry(entry) + except Exception, e: + print e + return False + else: + return True diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py index 4a46532642013204720ba467966c59de31a92301..cb9a7e98fd0c486abe5b8b92aff711fa69f23fa9 100644 --- a/ipaserver/ipaldap.py +++ b/ipaserver/ipaldap.py @@ -1775,6 +1775,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 -- 1.8.1
_______________________________________________ Freeipa-devel mailing list [email protected] https://www.redhat.com/mailman/listinfo/freeipa-devel
