Browse Source

Add support for pre/post db transaction plugins

There are two new plugin types:
betxnpreoperation - these plugins are called just after the database
calls txn_begin - they are passed in SLAPI_TXN the DB_TXN* just created
- they can use this as a parent transaction in a nested transaction
if the plugin wishes to cause the parent to abort, the plugin should return
a non-zero return code, and set the ldap error code to a meaningful error
value
betxnpostoperation - called just before the database calls txn_commit
The changelog code uses the betxnpostoperation to create a nested
transaction for the changelog write
Reviewed by: nhosoi (Thanks!)
Rich Megginson 14 years ago
parent
commit
056cc3551f

+ 64 - 31
ldap/servers/plugins/replication/cl5_api.c

@@ -93,7 +93,9 @@
 #define HASH_BACKETS_COUNT 16   /* number of buckets in a hash table */
 
 #if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR >= 4100
-#define DEFAULT_DB_OP_FLAGS DB_AUTO_COMMIT
+#define USE_DB_TXN 1 /* use transactions */
+#define DEFAULT_DB_ENV_OP_FLAGS DB_AUTO_COMMIT
+#define DEFAULT_DB_OP_FLAGS 0
 #define DB_OPEN(oflags, db, txnid, file, database, type, flags, mode, rval)    \
 {                                                                              \
 	if (((oflags) & DB_INIT_TXN) && ((oflags) & DB_INIT_LOG))                  \
@@ -303,6 +305,8 @@ static PRBool _cl5ReplicaInList (Object *replica, Object **replicas);
 static int _cl5Entry2DBData (const CL5Entry *entry, char **data, PRUint32 *len);
 static int _cl5WriteOperation(const char *replName, const char *replGen,
                               const slapi_operation_parameters *op, PRBool local);
+static int _cl5WriteOperationTxn(const char *replName, const char *replGen,
+                                 const slapi_operation_parameters *op, PRBool local, void *txn);
 static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid);
 static int _cl5GetNextEntry (CL5Entry *entry, void *iterator);
 static int _cl5CurrentDeleteEntry (void *iterator);
@@ -1376,7 +1380,7 @@ void cl5DestroyIterator (void *iterator)
 	slapi_ch_free ((void**)&it);
 }
 
-/* Name:		cl5WriteOperation
+/* Name:		cl5WriteOperationTxn
    Description:	writes operation to changelog
    Parameters:  replName - name of the replica to which operation applies
                 replGen - replica generation for the operation
@@ -1385,14 +1389,15 @@ void cl5DestroyIterator (void *iterator)
                    is in progress (if the data is reloaded). !!!
                 op - operation to write
 				local - this is a non-replicated operation
+                txn - the transaction containing this operation
    Return:		CL5_SUCCESS if function is successfull;
 				CL5_BAD_DATA if invalid op is passed;
 				CL5_BAD_STATE if db has not been initialized;
 				CL5_MEMORY_ERROR if memory allocation failed;
 				CL5_DB_ERROR if any other db error occured;
  */
-int cl5WriteOperation(const char *replName, const char *replGen,
-                      const slapi_operation_parameters *op, PRBool local)
+int cl5WriteOperationTxn(const char *replName, const char *replGen,
+                         const slapi_operation_parameters *op, PRBool local, void *txn)
 {
 	int rc;
 
@@ -1421,7 +1426,7 @@ int cl5WriteOperation(const char *replName, const char *replGen,
 	if (rc != CL5_SUCCESS)
 		return rc;
 
-	rc = _cl5WriteOperation(replName, replGen, op, local);
+	rc = _cl5WriteOperationTxn(replName, replGen, op, local, txn);
 
     /* update the upper bound ruv vector */
     if (rc == CL5_SUCCESS)
@@ -1440,6 +1445,27 @@ int cl5WriteOperation(const char *replName, const char *replGen,
 	return rc;	
 }
 
+/* Name:		cl5WriteOperation
+   Description:	writes operation to changelog
+   Parameters:  replName - name of the replica to which operation applies
+                replGen - replica generation for the operation
+                !!!Note that we pass name and generation rather than
+                   replica object since generation can change while operation
+                   is in progress (if the data is reloaded). !!!
+                op - operation to write
+				local - this is a non-replicated operation
+   Return:		CL5_SUCCESS if function is successfull;
+				CL5_BAD_DATA if invalid op is passed;
+				CL5_BAD_STATE if db has not been initialized;
+				CL5_MEMORY_ERROR if memory allocation failed;
+				CL5_DB_ERROR if any other db error occured;
+ */
+int cl5WriteOperation(const char *replName, const char *replGen,
+                      const slapi_operation_parameters *op, PRBool local)
+{
+    return cl5WriteOperationTxn(replName, replGen, op, local, NULL);
+}
+
 /* Name:		cl5CreateReplayIterator
    Description:	creates an iterator that allows to retireve changes that should
 				to be sent to the consumer identified by ruv. The iteration is peformed by 
@@ -2050,7 +2076,7 @@ static int _cl5DBOpen ()
                 PR_snprintf(fullpathname, MAXPATHLEN, "%s/%s", s_cl5Desc.dbDir, entry->name);
                 rc = s_cl5Desc.dbEnv->dbremove(s_cl5Desc.dbEnv,
                                                0, fullpathname, 0,
-                                               DEFAULT_DB_OP_FLAGS);
+                                               DEFAULT_DB_ENV_OP_FLAGS);
                 if (rc != 0)
                 {
                     slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
@@ -3248,7 +3274,7 @@ static int  _cl5Delete (const char *clDir, int rmDir)
 		} else {
 			/* DB files */
 			rc = s_cl5Desc.dbEnv->dbremove(s_cl5Desc.dbEnv, 0, filename, 0,
-                                           DEFAULT_DB_OP_FLAGS);
+                                           DEFAULT_DB_ENV_OP_FLAGS);
 			if (rc) {
 				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, 
 				                "_cl5Delete: failed to remove \"%s\"; "
@@ -4450,8 +4476,8 @@ _cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, char **repl
 	return rval;
 }
 
-static int _cl5WriteOperation(const char *replName, const char *replGen, 
-                              const slapi_operation_parameters *op, PRBool local)
+static int _cl5WriteOperationTxn(const char *replName, const char *replGen, 
+                                 const slapi_operation_parameters *op, PRBool local, void *txn)
 {
 	int rc;
 	int cnt;
@@ -4463,6 +4489,7 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 	CL5DBFile *file = NULL;
 	Object *file_obj = NULL;
 	DB_TXN *txnid = NULL;
+	DB_TXN *parent_txnid = (DB_TXN *)txn;
 
 	rc = _cl5GetDBFileByReplicaName (replName, replGen, &file_obj);
 	if (rc == CL5_NOTFOUND)
@@ -4472,14 +4499,14 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 		if (rc != CL5_SUCCESS)
 		{
 			slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, 
-							"_cl5WriteOperation: failed to find or open DB object for replica %s\n", replName);
+							"_cl5WriteOperationTxn: failed to find or open DB object for replica %s\n", replName);
 			return rc;
 		}
 	}
 	else if (rc != CL5_SUCCESS)
 	{
 		slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, 
-						"_cl5WriteOperation: failed to get db file for target dn (%s)", 
+						"_cl5WriteOperationTxn: failed to get db file for target dn (%s)", 
 						op->target_address.dn);
 		return CL5_OBJSET_ERROR;
 	}
@@ -4499,7 +4526,7 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 	{
 		char s[CSN_STRSIZE];		
 		slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, 
-						"_cl5WriteOperation: failed to convert entry with csn (%s) "
+						"_cl5WriteOperationTxn: failed to convert entry with csn (%s) "
                         "to db format\n", csn_as_string(op->csn,PR_FALSE,s));
 		goto done;
 	}
@@ -4514,7 +4541,7 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 		if (rc != 0)
 		{
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, 
-				"_cl5WriteOperation: failed to write entry; db error - %d %s\n", 
+				"_cl5WriteOperationTxn: failed to write entry; db error - %d %s\n", 
 				rc, db_strerror(rc));
 			if (CL5_OS_ERR_IS_DISKFULL(rc))
 			{
@@ -4533,13 +4560,13 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 	{ 
 		if (cnt != 0)
 		{
-#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+#if USE_DB_TXN
 			/* abort previous transaction */
-			rc = txn_abort (txnid);
+			rc = TXN_ABORT (txnid);
 			if (rc != 0)
 			{
 				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
-							"_cl5WriteOperation: failed to abort transaction; db error - %d %s\n",
+							"_cl5WriteOperationTxn: failed to abort transaction; db error - %d %s\n",
 							rc, db_strerror(rc));
 				rc = CL5_DB_ERROR;
 				goto done;
@@ -4549,13 +4576,13 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
     		interval = PR_MillisecondsToInterval(slapi_rand() % 100);
     		DS_Sleep(interval);		
 		}
-#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
+#if USE_DB_TXN
 		/* begin transaction */
-		rc = txn_begin(s_cl5Desc.dbEnv, NULL /*pid*/, &txnid, 0);
+		rc = TXN_BEGIN(s_cl5Desc.dbEnv, parent_txnid, &txnid, 0);
 		if (rc != 0)
 		{
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
-						"_cl5WriteOperation: failed to start transaction; db error - %d %s\n",
+						"_cl5WriteOperationTxn: failed to start transaction; db error - %d %s\n",
 						rc, db_strerror(rc));
 			rc = CL5_DB_ERROR;
 			goto done;
@@ -4574,7 +4601,7 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 		if (CL5_OS_ERR_IS_DISKFULL(rc))
 		{
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
-				"_cl5WriteOperation: changelog (%s) DISK FULL; db error - %d %s\n",
+				"_cl5WriteOperationTxn: changelog (%s) DISK FULL; db error - %d %s\n",
 				s_cl5Desc.dbDir, rc, db_strerror(rc));
 			cl5_set_diskfull();
 			rc = CL5_DB_ERROR;
@@ -4584,11 +4611,11 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
 		{
 			if (rc == 0)
 			{
-				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) succeeded\n", cnt, (char*)key.data);
+				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperationTxn: retry (%d) the transaction (csn=%s) succeeded\n", cnt, (char*)key.data);
 			}
 			else if ((cnt + 1) >= MAX_TRIALS)
 			{
-				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperation: retry (%d) the transaction (csn=%s) failed (rc=%d (%s))\n", cnt, (char*)key.data, rc, db_strerror(rc));
+				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, "_cl5WriteOperationTxn: retry (%d) the transaction (csn=%s) failed (rc=%d (%s))\n", cnt, (char*)key.data, rc, db_strerror(rc));
 			}
 		}
 		cnt ++;
@@ -4596,23 +4623,23 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
     
 	if (rc == 0) /* we successfully added entry */
 	{
-#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
-		rc = txn_commit (txnid, 0);
+#if USE_DB_TXN
+		rc = TXN_COMMIT (txnid, 0);
 #endif
 	}
 	else	
 	{
 		char s[CSN_STRSIZE];		
 		slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl, 
-						"_cl5WriteOperation: failed to write entry with csn (%s); "
+						"_cl5WriteOperationTxn: failed to write entry with csn (%s); "
 						"db error - %d %s\n", csn_as_string(op->csn,PR_FALSE,s), 
 						rc, db_strerror(rc));
-#if 1000*DB_VERSION_MAJOR + 100*DB_VERSION_MINOR < 4100
-		rc = txn_abort (txnid);
+#if USE_DB_TXN
+		rc = TXN_ABORT (txnid);
 		if (rc != 0)
 		{
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name_cl,
-							"_cl5WriteOperation: failed to abort transaction; db error - %d %s\n",
+							"_cl5WriteOperationTxn: failed to abort transaction; db error - %d %s\n",
 							rc, db_strerror(rc));
 		}
 #endif
@@ -4627,7 +4654,7 @@ static int _cl5WriteOperation(const char *replName, const char *replGen,
     _cl5UpdateRUV (file_obj, op->csn, PR_TRUE, PR_TRUE);
 
 	slapi_log_error(SLAPI_LOG_PLUGIN, repl_plugin_name_cl, 
-			"cl5WriteOperation: successfully written entry with csn (%s)\n", csnStr);
+			"cl5WriteOperationTxn: successfully written entry with csn (%s)\n", csnStr);
 	rc = CL5_SUCCESS;
 done:
 	if (data->data)
@@ -4640,6 +4667,12 @@ done:
 	return rc;
 }
 
+static int _cl5WriteOperation(const char *replName, const char *replGen, 
+                              const slapi_operation_parameters *op, PRBool local)
+{
+    return _cl5WriteOperationTxn(replName, replGen, op, local, NULL);
+}
+
 static int _cl5GetFirstEntry (Object *obj, CL5Entry *entry, void **iterator, DB_TXN *txnid)
 {
 	int rc;
@@ -5861,9 +5894,9 @@ static void _cl5DBCloseFile (void **data)
 		 * run into problems when we try to checkpoint transactions later. */
 	    slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBCloseFile: "
 						"removing the changelog %s (flag %d)\n",
-						file->name, DEFAULT_DB_OP_FLAGS);
+						file->name, DEFAULT_DB_ENV_OP_FLAGS);
 		rc = s_cl5Desc.dbEnv->dbremove(s_cl5Desc.dbEnv, 0, file->name, 0,
-                                       DEFAULT_DB_OP_FLAGS);
+                                       DEFAULT_DB_ENV_OP_FLAGS);
 		if (rc != 0)
 		{
 			slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl, "_cl5DBCloseFile: "

+ 19 - 0
ldap/servers/plugins/replication/cl5_api.h

@@ -321,6 +321,25 @@ int cl5GetNextOperation (slapi_operation_parameters *op, void *iterator);
  */
 void cl5DestroyIterator (void *iterator);
 
+/* Name:		cl5WriteOperationTxn
+   Description:	writes operation to changelog as part of a containing transaction
+   Parameters:  repl_name - name of the replica to which operation applies
+                repl_gen - replica generation for the operation
+                !!!Note that we pass name and generation rather than
+                   replica object since generation can change while operation
+                   is in progress (if the data is reloaded). !!!
+                op - operation to write
+				local - this is a non-replicated operation
+                txn - the containing transaction
+   Return:		CL5_SUCCESS if function is successfull;
+				CL5_BAD_DATA if invalid op is passed;
+				CL5_BAD_STATE if db has not been initialized;
+				CL5_MEMORY_ERROR if memory allocation failed;
+				CL5_DB_ERROR if any other db error occured;
+ */
+int cl5WriteOperationTxn(const char *repl_name, const char *repl_gen,
+                         const slapi_operation_parameters *op, PRBool local, void *txn);
+
 /* Name:		cl5WriteOperation
    Description:	writes operation to changelog
    Parameters:  repl_name - name of the replica to which operation applies

+ 4 - 0
ldap/servers/plugins/replication/repl5.h

@@ -199,6 +199,10 @@ int multimaster_postop_add (Slapi_PBlock *pb);
 int multimaster_postop_delete (Slapi_PBlock *pb);
 int multimaster_postop_modify (Slapi_PBlock *pb);
 int multimaster_postop_modrdn (Slapi_PBlock *pb);
+int multimaster_betxnpostop_modrdn (Slapi_PBlock *pb);
+int multimaster_betxnpostop_delete (Slapi_PBlock *pb);
+int multimaster_betxnpostop_add (Slapi_PBlock *pb);
+int multimaster_betxnpostop_modify (Slapi_PBlock *pb);
 
 /* In repl5_init.c */
 char* get_thread_private_agmtname ();

+ 21 - 0
ldap/servers/plugins/replication/repl5_init.c

@@ -133,6 +133,7 @@ static Slapi_PluginDesc multimasterinternalpreopdesc = {"replication-multimaster
 static Slapi_PluginDesc multimasterinternalpostopdesc = {"replication-multimaster-internalpostop", VENDOR, DS_PACKAGE_VERSION, "Multimaster replication internal post-operation plugin"};
 static Slapi_PluginDesc multimasterbepreopdesc = {"replication-multimaster-bepreop", VENDOR, DS_PACKAGE_VERSION, "Multimaster replication bepre-operation plugin"};
 static Slapi_PluginDesc multimasterbepostopdesc = {"replication-multimaster-bepostop", VENDOR, DS_PACKAGE_VERSION, "Multimaster replication bepost-operation plugin"};
+static Slapi_PluginDesc multimasterbetxnpostopdesc = {"replication-multimaster-betxnpostop", VENDOR, DS_PACKAGE_VERSION, "Multimaster replication be transaction post-operation plugin"};
 static Slapi_PluginDesc multimasterextopdesc = { "replication-multimaster-extop", VENDOR, DS_PACKAGE_VERSION, "Multimaster replication extended-operation plugin" };
 
 static int multimaster_stopped_flag; /* A flag which is set when all the plugin threads are to stop */
@@ -328,6 +329,25 @@ multimaster_bepostop_init( Slapi_PBlock *pb )
 	return rc;
 }
 
+int
+multimaster_betxnpostop_init( Slapi_PBlock *pb )
+{
+    int rc= 0; /* OK */
+  
+	if( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 || 
+		slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterbetxnpostopdesc ) != 0 ||
+		slapi_pblock_set( pb, SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN, (void *) multimaster_betxnpostop_modrdn ) != 0 ||
+		slapi_pblock_set( pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN, (void *) multimaster_betxnpostop_delete ) != 0 ||
+		slapi_pblock_set( pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *) multimaster_betxnpostop_modrdn ) != 0 ||
+		slapi_pblock_set( pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *) multimaster_betxnpostop_delete ) != 0 )
+	{
+		slapi_log_error( SLAPI_LOG_PLUGIN, repl_plugin_name, "multimaster_betxnpostop_init failed\n" );
+		rc= -1;
+	}
+
+	return rc;
+}
+
 int
 multimaster_start_extop_init( Slapi_PBlock *pb )
 {
@@ -591,6 +611,7 @@ int replication_multimaster_plugin_init(Slapi_PBlock *pb)
         rc= slapi_register_plugin("postoperation", 1 /* Enabled */, "multimaster_postop_init", multimaster_postop_init, "Multimaster replication postoperation plugin", NULL, identity);
         rc= slapi_register_plugin("bepreoperation", 1 /* Enabled */, "multimaster_bepreop_init", multimaster_bepreop_init, "Multimaster replication bepreoperation plugin", NULL, identity);
 		rc= slapi_register_plugin("bepostoperation", 1 /* Enabled */, "multimaster_bepostop_init", multimaster_bepostop_init, "Multimaster replication bepostoperation plugin", NULL, identity);
+		rc= slapi_register_plugin("betxnpostoperation", 1 /* Enabled */, "multimaster_betxnpostop_init", multimaster_betxnpostop_init, "Multimaster replication betxnpostoperation plugin", NULL, identity);
         rc= slapi_register_plugin("internalpreoperation", 1 /* Enabled */, "multimaster_internalpreop_init", multimaster_internalpreop_init, "Multimaster replication internal preoperation plugin", NULL, identity);
         rc= slapi_register_plugin("internalpostoperation", 1 /* Enabled */, "multimaster_internalpostop_init", multimaster_internalpostop_init, "Multimaster replication internal postoperation plugin", NULL, identity);
 		rc= slapi_register_plugin("extendedop", 1 /* Enabled */, "multimaster_start_extop_init", multimaster_start_extop_init, "Multimaster replication start extended operation plugin", NULL, identity);

+ 78 - 48
ldap/servers/plugins/replication/repl5_plugins.c

@@ -851,6 +851,29 @@ multimaster_postop_modrdn (Slapi_PBlock *pb)
 	return process_postop(pb);
 }
 
+int
+multimaster_betxnpostop_delete (Slapi_PBlock *pb)
+{
+    return write_changelog_and_ruv(pb);
+}
+
+int
+multimaster_betxnpostop_modrdn (Slapi_PBlock *pb)
+{
+    return write_changelog_and_ruv(pb);
+}
+
+int
+multimaster_betxnpostop_add (Slapi_PBlock *pb)
+{
+    return write_changelog_and_ruv(pb);
+}
+
+int
+multimaster_betxnpostop_modify (Slapi_PBlock *pb)
+{
+    return write_changelog_and_ruv(pb);
+}
 
 /* Helper functions */
 
@@ -943,42 +966,63 @@ update_ruv_component(Replica *replica, CSN *opcsn, Slapi_PBlock *pb)
 static int
 write_changelog_and_ruv (Slapi_PBlock *pb)
 {	
+	Slapi_Operation *op = NULL;
 	int rc;
 	slapi_operation_parameters *op_params = NULL;
-    Object *repl_obj;
+	Object *repl_obj;
 	int return_value = 0;
-    Replica *r;
+	Replica *r;
+	Slapi_Backend *be;
+	int is_replicated_operation = 0;
 
-    /* we only log changes for operations applied to a replica */
+	/* we just let fixup operations through */
+	slapi_pblock_get( pb, SLAPI_OPERATION, &op );
+	if ((operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) ||
+		(operation_is_flag_set(op, OP_FLAG_TOMBSTONE_ENTRY)))
+	{
+		return 0;
+	}
+
+	/* ignore operations intended for chaining backends - they will be
+	   replicated back to us or should be ignored anyway
+	   replicated operations should be processed normally, as they should
+	   be going to a local backend */
+	is_replicated_operation= operation_is_flag_set(op,OP_FLAG_REPLICATED);
+	slapi_pblock_get(pb, SLAPI_BACKEND, &be);
+	if (!is_replicated_operation &&
+		slapi_be_is_flag_set(be,SLAPI_BE_FLAG_REMOTE_DATA))
+	{
+		return 0;
+	}
+
+	/* we only log changes for operations applied to a replica */
 	repl_obj = replica_get_replica_for_op (pb);
-    if (repl_obj == NULL)
-        return 0;
+	if (repl_obj == NULL)
+		return 0;
  
-    r = (Replica*)object_get_data (repl_obj);
-    PR_ASSERT (r);
+	r = (Replica*)object_get_data (repl_obj);
+	PR_ASSERT (r);
 
 	if (replica_is_flag_set (r, REPLICA_LOG_CHANGES) &&
 		(cl5GetState () == CL5_STATE_OPEN))
 	{
-        supplier_operation_extension *opext = NULL;
-        const char *repl_name;
-        char *repl_gen;
-		Slapi_Operation *op;
+		supplier_operation_extension *opext = NULL;
+		const char *repl_name;
+		char *repl_gen;
 
-		slapi_pblock_get(pb, SLAPI_OPERATION, &op);  
 		opext = (supplier_operation_extension*) repl_sup_get_ext (REPL_SUP_EXT_OP, op);
 		PR_ASSERT (opext);
 
-        /* get replica generation and replica name to pass to the write function */
-        repl_name = replica_get_name (r);
-        repl_gen = opext->repl_gen;
-        PR_ASSERT (repl_name && repl_gen);
+		/* get replica generation and replica name to pass to the write function */
+		repl_name = replica_get_name (r);
+		repl_gen = opext->repl_gen;
+		PR_ASSERT (repl_name && repl_gen);
 
 		/* for replicated operations, we log the original, non-urp data which is
 		   saved in the operation extension */
 		if (operation_is_flag_set(op,OP_FLAG_REPLICATED))
 		{		
-            PR_ASSERT (opext->operation_parameters);
+			PR_ASSERT (opext->operation_parameters);
 			op_params = opext->operation_parameters;
 		}
 		else /* since client operations don't go through urp, we log the operation data in pblock */
@@ -1013,21 +1057,23 @@ write_changelog_and_ruv (Slapi_PBlock *pb)
 			op_params->target_address.uniqueid = slapi_ch_strdup (uniqueid);
 		} 
 
-        /* we might have stripped all the mods - in that case we do not
-           log the operation */
-        if (op_params->operation_type != SLAPI_OPERATION_MODIFY ||
-            op_params->p.p_modify.modify_mods != NULL)
-        {
+		/* we might have stripped all the mods - in that case we do not
+		   log the operation */
+		if (op_params->operation_type != SLAPI_OPERATION_MODIFY ||
+			op_params->p.p_modify.modify_mods != NULL)
+		{
+			void *txn = NULL;
 			if (cl5_is_diskfull() && !cl5_diskspace_is_available()) 
 			{
 				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
-					"write_changelog_and_ruv: Skipped due to DISKFULL\n");
+								"write_changelog_and_ruv: Skipped due to DISKFULL\n");
 				return 0;
 			}
-		    rc = cl5WriteOperation(repl_name, repl_gen, op_params, 
-							       !operation_is_flag_set(op, OP_FLAG_REPLICATED));
-            if (rc != CL5_SUCCESS)
-		    {
+			slapi_pblock_get(pb, SLAPI_TXN, &txn);
+			rc = cl5WriteOperationTxn(repl_name, repl_gen, op_params, 
+									  !operation_is_flag_set(op, OP_FLAG_REPLICATED), txn);
+			if (rc != CL5_SUCCESS)
+			{
     			char csn_str[CSN_STRSIZE];
 			    /* ONREPL - log error */
         		slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
@@ -1036,10 +1082,10 @@ write_changelog_and_ruv (Slapi_PBlock *pb)
 					op_params->target_address.dn,
 					op_params->target_address.uniqueid,
 					op_params->operation_type,
-            		csn_as_string(op_params->csn, PR_FALSE, csn_str));
-			    return_value = 1;
-		    }
-        }
+					csn_as_string(op_params->csn, PR_FALSE, csn_str));
+				return_value = 1;
+			}
+		}
 
 		if (!operation_is_flag_set(op,OP_FLAG_REPLICATED))
 		{
@@ -1056,7 +1102,6 @@ write_changelog_and_ruv (Slapi_PBlock *pb)
 	  just read from the changelog in either the supplier or consumer ruv
 	*/
 	if (0 == return_value) {
-		Slapi_Operation *op;
 		CSN *opcsn;
 
 		slapi_pblock_get( pb, SLAPI_OPERATION, &op );
@@ -1107,24 +1152,9 @@ process_postop (Slapi_PBlock *pb)
 	get_repl_session_id (pb, sessionid, &opcsn);
 
     slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
-	/*
-	 * Don't abandon writing changelog since we'd do everything
-	 * possible to keep the changelog in sync with the backend
-	 * db which was committed before this function was called.
-	 *
-	 * if (rc == LDAP_SUCCESS && !slapi_op_abandoned(pb))
-	 */
 	if (rc == LDAP_SUCCESS)
 	{
-        rc = write_changelog_and_ruv(pb);
-        if (rc == 0)
-        {
-			agmtlist_notify_all(pb);
-		}
-        else
-        {
-            slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s process postop: error writing changelog and ruv\n", sessionid);
-        }
+        agmtlist_notify_all(pb);
 	}
     else if (opcsn)
 	{

+ 20 - 0
ldap/servers/slapd/back-ldbm/ldbm_add.c

@@ -671,6 +671,18 @@ ldbm_back_add( Slapi_PBlock *pb )
 			ldap_result_code= LDAP_OPERATIONS_ERROR;
 			goto error_return; 
 		}
+
+		/* stash the transaction */
+		slapi_pblock_set(pb, SLAPI_TXN, (void *)txn.back_txn_txn);
+
+		/* call the transaction pre add plugins just after creating the transaction */
+		if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN))) {
+			LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN plugin "
+						   "returned error code %d\n", retval );
+			slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+			goto error_return;
+		}
+
 		retval = id2entry_add( be, addingentry, &txn );
 		if (DB_LOCK_DEADLOCK == retval)
 		{
@@ -883,6 +895,14 @@ ldbm_back_add( Slapi_PBlock *pb )
 		}
 	}
 
+	/* call the transaction post add plugins just before the commit */
+	if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN))) {
+		LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_POST_ADD_FN plugin "
+					   "returned error code %d\n", retval );
+		slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+		goto error_return;
+	}
+
 	retval = dblayer_txn_commit(li,&txn);
 	if (0 != retval)
 	{

+ 20 - 0
ldap/servers/slapd/back-ldbm/ldbm_delete.c

@@ -467,6 +467,18 @@ ldbm_back_delete( Slapi_PBlock *pb )
 			ldap_result_code= LDAP_OPERATIONS_ERROR;
 			goto error_return;
 		}
+
+		/* stash the transaction */
+		slapi_pblock_set(pb, SLAPI_TXN, (void *)txn.back_txn_txn);
+
+		/* call the transaction pre delete plugins just after creating the transaction */
+		if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN))) {
+			LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN plugin "
+						   "returned error code %d\n", retval );
+			slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+			goto error_return;
+		}
+
 		if(create_tombstone_entry)
 		{
 			/*
@@ -868,6 +880,14 @@ ldbm_back_delete( Slapi_PBlock *pb )
 		goto error_return;
 	}
 
+	/* call the transaction post delete plugins just before the commit */
+	if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN))) {
+		LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN plugin "
+					   "returned error code %d\n", retval );
+		slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+		goto error_return;
+	}
+
 	retval = dblayer_txn_commit(li,&txn);
 	if (0 != retval)
 	{

+ 19 - 0
ldap/servers/slapd/back-ldbm/ldbm_modify.c

@@ -422,6 +422,17 @@ ldbm_back_modify( Slapi_PBlock *pb )
 			goto error_return;
 		}
 
+		/* stash the transaction */
+		slapi_pblock_set(pb, SLAPI_TXN, (void *)txn.back_txn_txn);
+
+		/* call the transaction pre modify plugins just after creating the transaction */
+		if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN))) {
+			LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN plugin "
+						   "returned error code %d\n", retval );
+			slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+			goto error_return;
+		}
+
 		/*
 		 * Update the ID to Entry index. 
 		 * Note that id2entry_add replaces the entry, so the Entry ID stays the same.
@@ -537,6 +548,14 @@ ldbm_back_modify( Slapi_PBlock *pb )
 	 */
 	e = NULL;
 	
+	/* call the transaction post modify plugins just before the commit */
+	if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN))) {
+		LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN plugin "
+					   "returned error code %d\n", retval );
+		slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+		goto error_return;
+	}
+
 	retval = dblayer_txn_commit(li,&txn);
 	if (0 != retval) {
 		if (LDBM_OS_ERR_IS_DISKFULL(retval)) disk_full = 1;

+ 19 - 0
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c

@@ -695,6 +695,17 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
             goto error_return;
         }
 
+		/* stash the transaction */
+		slapi_pblock_set(pb, SLAPI_TXN, (void *)txn.back_txn_txn);
+
+		/* call the transaction pre modrdn plugins just after creating the transaction */
+		if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN))) {
+			LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN plugin "
+						   "returned error code %d\n", retval );
+			slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+			goto error_return;
+		}
+
         /*
          * Update the indexes for the entry.
          */
@@ -898,6 +909,14 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
         modify_switch_entries( &newparent_modify_context,be);
     }
 
+	/* call the transaction post modrdn plugins just before the commit */
+	if ((retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN))) {
+		LDAPDebug1Arg( LDAP_DEBUG_ANY, "SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN plugin "
+					   "returned error code %d\n", retval );
+		slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+		goto error_return;
+	}
+
     retval = dblayer_txn_commit(li,&txn);
     if (0 != retval)
     {

+ 104 - 0
ldap/servers/slapd/pblock.c

@@ -1062,6 +1062,58 @@ slapi_pblock_get( Slapi_PBlock *pblock, int arg, void *value )
 		(*(IFP *)value) = pblock->pb_plugin->plg_internal_post_delete;
 		break;
 
+	/* backend pre txn operation plugin */
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpremodify;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpremodrdn;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpreadd;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpredelete;
+		break;
+
+	/* backend post txn operation plugin */
+	case SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpostmodify;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpostmodrdn;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_ADD_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpostadd;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		(*(IFP *)value) = pblock->pb_plugin->plg_betxnpostdelete;
+		break;
+
 	/* target address & controls for all operations should be normalized  */
 	case SLAPI_TARGET_ADDRESS:
 		if(pblock->pb_op!=NULL)
@@ -2497,6 +2549,58 @@ slapi_pblock_set( Slapi_PBlock *pblock, int arg, void *value )
 		pblock->pb_plugin->plg_internal_post_delete = (IFP) value;
 		break;
 		
+	/* backend preoperation plugin - called just after creating transaction */
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpremodify = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpremodrdn = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpreadd = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPREOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpredelete = (IFP) value;
+		break;
+
+	/* backend postoperation plugin - called just before committing transaction */
+	case SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpostmodify = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpostmodrdn = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_ADD_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpostadd = (IFP) value;
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN:
+		if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_BETXNPOSTOPERATION) {
+			return( -1 );
+		}
+		pblock->pb_plugin->plg_betxnpostdelete = (IFP) value;
+		break;
+
 	/* syntax plugin functions */
 	case SLAPI_PLUGIN_SYNTAX_FILTER_AVA:
 		if ( pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX ) {

+ 21 - 0
ldap/servers/slapd/plugin.c

@@ -373,6 +373,20 @@ plugin_call_plugins( Slapi_PBlock *pb, int whichfunction )
 	case SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN:
         plugin_list_number= PLUGIN_LIST_INTERNAL_POSTOPERATION;
 		break;
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN:
+	case SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN:
+	case SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN:
+	case SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN:
+        plugin_list_number= PLUGIN_LIST_BETXNPREOPERATION;
+		do_op = 1; /* always allow backend callbacks (even during startup) */
+		break;
+	case SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN:
+	case SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN:
+	case SLAPI_PLUGIN_BE_TXN_POST_ADD_FN:
+	case SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN:
+        plugin_list_number= PLUGIN_LIST_BETXNPOSTOPERATION;
+		do_op = 1; /* always allow backend callbacks (even during startup) */
+		break;
 	}
 	if(plugin_list_number!=-1 && do_op)
 	{
@@ -1440,6 +1454,7 @@ plugin_call_func (struct slapdplugin *list, int operation, Slapi_PBlock *pb, int
 			{
 				if (SLAPI_PLUGIN_PREOPERATION == list->plg_type ||
 					SLAPI_PLUGIN_INTERNAL_PREOPERATION == list->plg_type ||
+					SLAPI_PLUGIN_BETXNPREOPERATION == list->plg_type ||
                     SLAPI_PLUGIN_START_FN == operation )
 				{
 					/*
@@ -1653,6 +1668,12 @@ plugin_get_type_and_list(
 	} else if ( strcasecmp( plugintype, "bepostoperation" ) == 0 ) {
 		*type = SLAPI_PLUGIN_BEPOSTOPERATION;
     	plugin_list_index= PLUGIN_LIST_BEPOSTOPERATION;
+	} else if ( strcasecmp( plugintype, "betxnpreoperation" ) == 0 ) {
+		*type = SLAPI_PLUGIN_BETXNPREOPERATION;
+    	plugin_list_index= PLUGIN_LIST_BETXNPREOPERATION;
+	} else if ( strcasecmp( plugintype, "betxnpostoperation" ) == 0 ) {
+		*type = SLAPI_PLUGIN_BETXNPOSTOPERATION;
+    	plugin_list_index= PLUGIN_LIST_BETXNPOSTOPERATION;
 	} else if ( strcasecmp( plugintype, "internalpreoperation" ) == 0 ) {
 		*type = SLAPI_PLUGIN_INTERNAL_PREOPERATION;
     	plugin_list_index= PLUGIN_LIST_INTERNAL_PREOPERATION;

+ 28 - 1
ldap/servers/slapd/slap.h

@@ -702,7 +702,9 @@ struct matchingRuleList {
 #define PLUGIN_LIST_REVER_PWD_STORAGE_SCHEME 16
 #define PLUGIN_LIST_LDBM_ENTRY_FETCH_STORE 17
 #define PLUGIN_LIST_INDEX 18
-#define PLUGIN_LIST_GLOBAL_MAX 19
+#define PLUGIN_LIST_BETXNPREOPERATION 19
+#define PLUGIN_LIST_BETXNPOSTOPERATION 20
+#define PLUGIN_LIST_GLOBAL_MAX 21
 
 /* plugin configuration attributes */
 #define ATTR_PLUGIN_PATH				"nsslapd-pluginPath"
@@ -1108,6 +1110,31 @@ struct slapdplugin {
 		} plg_un_entry_fetch_store;
 #define plg_entryfetchfunc plg_un.plg_un_entry_fetch_store.plg_un_entry_fetch_func
 #define plg_entrystorefunc plg_un.plg_un_entry_fetch_store.plg_un_entry_store_func
+
+        /* backend txn pre-operation plugin structure */
+		struct plg_un_betxnpre_operation {
+			IFP	plg_un_betxnpre_modify;	  /* modify */
+			IFP	plg_un_betxnpre_modrdn;	  /* modrdn */
+			IFP	plg_un_betxnpre_add;		  /* add */
+			IFP	plg_un_betxnpre_delete;	  /* delete */
+		} plg_un_betxnpre;
+#define plg_betxnpremodify	plg_un.plg_un_betxnpre.plg_un_betxnpre_modify
+#define plg_betxnpremodrdn	plg_un.plg_un_betxnpre.plg_un_betxnpre_modrdn
+#define plg_betxnpreadd	plg_un.plg_un_betxnpre.plg_un_betxnpre_add
+#define plg_betxnpredelete	plg_un.plg_un_betxnpre.plg_un_betxnpre_delete
+
+        /* backend txn post-operation plugin structure */
+		struct plg_un_betxnpost_operation {
+			IFP	plg_un_betxnpost_modify;	  /* modify */
+			IFP	plg_un_betxnpost_modrdn;	  /* modrdn */
+			IFP	plg_un_betxnpost_add;		  /* add */
+			IFP	plg_un_betxnpost_delete;	  /* delete */
+		} plg_un_betxnpost;
+#define plg_betxnpostmodify	plg_un.plg_un_betxnpost.plg_un_betxnpost_modify
+#define plg_betxnpostmodrdn	plg_un.plg_un_betxnpost.plg_un_betxnpost_modrdn
+#define plg_betxnpostadd	plg_un.plg_un_betxnpost.plg_un_betxnpost_add
+#define plg_betxnpostdelete	plg_un.plg_un_betxnpost.plg_un_betxnpost_delete
+
 	} plg_un;
 };
 

+ 14 - 0
ldap/servers/slapd/slapi-plugin.h

@@ -5778,6 +5778,8 @@ time_t slapi_current_time( void );
 #define SLAPI_PLUGIN_REVER_PWD_STORAGE_SCHEME	16
 #define SLAPI_PLUGIN_LDBM_ENTRY_FETCH_STORE	17
 #define SLAPI_PLUGIN_INDEX			18
+#define	SLAPI_PLUGIN_BETXNPREOPERATION		19
+#define SLAPI_PLUGIN_BETXNPOSTOPERATION		20
 
 /*
  * special return values for extended operation plugins (zero or positive
@@ -5926,6 +5928,12 @@ typedef struct slapi_plugindesc {
 #define SLAPI_PLUGIN_BE_PRE_CLOSE_FN		454
 #define SLAPI_PLUGIN_BE_PRE_BACKUP_FN		455
 
+/* preoperation plugin to the backend - just after transaction creation */
+#define SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN		460
+#define SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN		461
+#define SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN		462
+#define SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN		463
+
 /* postoperation plugin functions */
 #define SLAPI_PLUGIN_POST_BIND_FN		501
 #define SLAPI_PLUGIN_POST_UNBIND_FN		502
@@ -5955,6 +5963,12 @@ typedef struct slapi_plugindesc {
 #define SLAPI_PLUGIN_BE_POST_OPEN_FN		554
 #define SLAPI_PLUGIN_BE_POST_BACKUP_FN		555
 
+/* postoperation plugin to the backend - just before transaction commit */
+#define SLAPI_PLUGIN_BE_TXN_POST_ADD_FN		560
+#define SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN		561
+#define SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN		562
+#define SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN		563
+
 /* matching rule plugin functions */
 #define SLAPI_PLUGIN_MR_FILTER_CREATE_FN	600
 #define SLAPI_PLUGIN_MR_INDEXER_CREATE_FN	601