summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFraser Tweedale <ftweedal@redhat.com>2015-11-30 16:05:03 +1100
committerFraser Tweedale <ftweedal@redhat.com>2016-01-21 12:48:57 +1100
commit7957c8097d56f1ea905ffe7c689f3a3284ece0e1 (patch)
tree19a2cb8a511d7e3828f3f98b0196aa678614daca
parent2cb2e9c8df06a7fdb2fed11e2973c03483024bc0 (diff)
downloadpki-7957c8097d56f1ea905ffe7c689f3a3284ece0e1.tar.gz
pki-7957c8097d56f1ea905ffe7c689f3a3284ece0e1.tar.xz
pki-7957c8097d56f1ea905ffe7c689f3a3284ece0e1.zip
Handle LDAPProfileSubsystem delete-then-recreate races
Deleting and then immediately recreating a profile can result in the new profile temporarily going missing, if the DELETE EntryChangeControl is processed after profile readdition. Handle this case by tracking the nsUniqueId of entries that are deleted by an LDAPProfileSubsystem and NOT (re-)forgetting the profile when the subsequent EntryChangeControl gets processed. Fixes: https://fedorahosted.org/pki/ticket/1700
-rw-r--r--base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java112
1 files changed, 92 insertions, 20 deletions
diff --git a/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java b/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java
index b16a33fe2..f48aea391 100644
--- a/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java
+++ b/base/server/cmscore/src/com/netscape/cmscore/profile/LDAPProfileSubsystem.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.TreeMap;
+import java.util.TreeSet;
import netscape.ldap.LDAPAttribute;
import netscape.ldap.LDAPConnection;
@@ -62,6 +63,11 @@ public class LDAPProfileSubsystem
* of the profile entry that this instance has seen */
private TreeMap<String,Integer> entryUSNs;
+ private TreeMap<String,String> nsUniqueIds;
+
+ /* Set of nsUniqueIds of deleted entries */
+ private TreeSet<String> deletedNsUniqueIds;
+
/**
* Initializes this subsystem with the given configuration
* store.
@@ -79,6 +85,8 @@ public class LDAPProfileSubsystem
mProfiles = new LinkedHashMap<String, IProfile>();
mProfileClassIds = new Hashtable<String, String>();
entryUSNs = new TreeMap<>();
+ nsUniqueIds = new TreeMap<>();
+ deletedNsUniqueIds = new TreeSet<>();
IConfigStore cs = CMS.getConfigStore();
IConfigStore dbCfg = cs.getSubStore("internaldb");
@@ -110,6 +118,14 @@ public class LDAPProfileSubsystem
IPluginRegistry registry = (IPluginRegistry)
CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
+ String nsUniqueId =
+ ldapProfile.getAttribute("nsUniqueId").getStringValueArray()[0];
+ if (deletedNsUniqueIds.contains(nsUniqueId)) {
+ CMS.debug("readProfile: ignoring entry with nsUniqueId '"
+ + nsUniqueId + "' due to deletion");
+ return;
+ }
+
String profileId = null;
String dn = ldapProfile.getDN();
if (!dn.startsWith("cn=")) {
@@ -145,6 +161,7 @@ public class LDAPProfileSubsystem
CMS.debug("Start Profile Creation - " + profileId + " " + classId + " " + info.getClassName());
createProfile(profileId, classId, info.getClassName(), data);
entryUSNs.put(profileId, newEntryUSN);
+ nsUniqueIds.put(profileId, nsUniqueId);
CMS.debug("Done Profile Creation - " + profileId);
} catch (EProfileException e) {
CMS.debug("Error creating profile '" + profileId + "'; skipping.");
@@ -192,7 +209,7 @@ public class LDAPProfileSubsystem
}
}
- public void deleteProfile(String id) throws EProfileException {
+ public synchronized void deleteProfile(String id) throws EProfileException {
if (isProfileEnable(id)) {
throw new EProfileException("CMS_PROFILE_DELETE_ENABLEPROFILE");
}
@@ -215,9 +232,31 @@ public class LDAPProfileSubsystem
}
}
+ deletedNsUniqueIds.add(nsUniqueIds.get(id));
forgetProfile(id);
}
+ private synchronized void handleDELETE(LDAPEntry entry) {
+ LDAPAttribute attr = entry.getAttribute("nsUniqueId");
+ String nsUniqueId = null;
+ if (attr != null)
+ nsUniqueId = attr.getStringValueArray()[0];
+
+ if (deletedNsUniqueIds.remove(nsUniqueId)) {
+ CMS.debug("handleDELETE: delete was already effected");
+ return;
+ }
+
+ String profileId = null;
+ String dn = entry.getDN();
+ if (!dn.startsWith("cn=")) {
+ CMS.debug("handleDELETE: DN " + dn + " does not start with 'cn='");
+ return;
+ }
+ profileId = LDAPDN.explodeDN(dn, true)[0];
+ forgetProfile(profileId);
+ }
+
private synchronized void handleMODDN(DN oldDN, LDAPEntry entry) {
DN profilesDN = new DN(dn);
@@ -231,20 +270,61 @@ public class LDAPProfileSubsystem
@Override
public synchronized void commitProfile(String id) throws EProfileException {
LDAPConfigStore cs = (LDAPConfigStore) mProfiles.get(id).getConfigStore();
+
+ // first create a *new* profile object from the configStore
+ // and initialise it with the updated configStore
+ //
+ IPluginRegistry registry = (IPluginRegistry)
+ CMS.getSubsystem(CMS.SUBSYSTEM_REGISTRY);
+ String classId = mProfileClassIds.get(id);
+ IPluginInfo info = registry.getPluginInfo("profile", classId);
+ String className = info.getClassName();
+ IProfile newProfile = null;
try {
- String[] attrs = {"entryUSN"};
- LDAPEntry entry = cs.commitReturn(false, attrs);
+ newProfile = (IProfile) Class.forName(className).newInstance();
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+ throw new EProfileException("Could not instantiate class '"
+ + classId + "' for profile '" + id + "': " + e);
+ }
+ newProfile.setId(id);
+ try {
+ newProfile.init(this, cs);
+ } catch (EBaseException e) {
+ throw new EProfileException(
+ "Failed to initialise profile '" + id + "': " + e);
+ }
- LDAPAttribute attr = null;
- if (entry != null)
- attr = entry.getAttribute("entryUSN");
+ // next replace the existing profile with the new profile;
+ // this is to avoid any intermediate state where the profile
+ // is not fully initialised with its inputs, outputs and
+ // policy objects.
+ //
+ mProfiles.put(id, newProfile);
+
+ // finally commit the configStore and track the resulting
+ // entryUSN and (in case of add) the nsUniqueId
+ //
+ try {
+ String[] attrs = {"entryUSN", "nsUniqueId"};
+ LDAPEntry entry = cs.commitReturn(false, attrs);
+ if (entry == null) {
+ // shouldn't happen, but let's be sure not to crash anyway
+ return;
+ }
Integer entryUSN = null;
+ LDAPAttribute attr = entry.getAttribute("entryUSN");
if (attr != null)
entryUSN = new Integer(attr.getStringValueArray()[0]);
-
entryUSNs.put(id, entryUSN);
CMS.debug("commitProfile: new entryUSN = " + entryUSN);
+
+ String nsUniqueId = null;
+ attr = entry.getAttribute("nsUniqueId");
+ if (attr != null)
+ nsUniqueId = attr.getStringValueArray()[0];
+ CMS.debug("commitProfile: nsUniqueId = " + nsUniqueId);
+ nsUniqueIds.put(id, nsUniqueId);
} catch (ELdapException e) {
throw new EProfileException(
"Failed to commit config store of profile '" + id + ": " + e);
@@ -261,17 +341,7 @@ public class LDAPProfileSubsystem
mProfiles.remove(id);
mProfileClassIds.remove(id);
entryUSNs.remove(id);
- }
-
- private void forgetProfile(LDAPEntry entry) {
- String profileId = null;
- String dn = entry.getDN();
- if (!dn.startsWith("cn=")) {
- CMS.debug("forgetProfile: DN " + dn + " does not start with 'cn='");
- return;
- }
- profileId = LDAPDN.explodeDN(dn, true)[0];
- forgetProfile(profileId);
+ nsUniqueIds.remove(id);
}
/**
@@ -296,6 +366,8 @@ public class LDAPProfileSubsystem
mProfiles.clear();
mProfileClassIds.clear();
entryUSNs.clear();
+ nsUniqueIds.clear();
+ deletedNsUniqueIds.clear();
}
/**
@@ -328,7 +400,7 @@ public class LDAPProfileSubsystem
cons.setServerControls(persistCtrl);
cons.setBatchSize(1);
cons.setServerTimeLimit(0 /* seconds */);
- String[] attrs = {"*", "entryUSN"};
+ String[] attrs = {"*", "entryUSN", "nsUniqueId"};
LDAPSearchResults results = conn.search(
dn, LDAPConnection.SCOPE_ONE, "(objectclass=*)",
attrs, false, cons);
@@ -347,7 +419,7 @@ public class LDAPProfileSubsystem
break;
case LDAPPersistSearchControl.DELETE:
CMS.debug("Profile change monitor: DELETE");
- forgetProfile(entry);
+ handleDELETE(entry);
break;
case LDAPPersistSearchControl.MODIFY:
CMS.debug("Profile change monitor: MODIFY");