mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

ludovicp
31.49.2010 23e98d540a23d7f7a21e3ffae94938ea066306ac
Fix issue #3891 - Handle replication conflict when adding child entry and parent is deleted
5 files modified
410 ■■■■■ changed files
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java 27 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java 63 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java 18 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java 281 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -77,7 +77,6 @@
import org.opends.server.backends.jeb.BackendImpl;
import org.opends.server.backends.task.Task;
import org.opends.server.config.ConfigException;
import org.opends.server.controls.SubtreeDeleteControl;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
@@ -2564,11 +2563,10 @@
              /*
               * Create a new operation as the ConflictResolution
               * different operation.
               *  Note: When msg is a DeleteMsg, the DeleteOperation is properly
               *  created with subtreeDelete request control when needed.
               */
              op = msg.createOperation(conn);
              if (op instanceof DeleteOperation) {
                op.addRequestControl(new SubtreeDeleteControl(false));
              }
            }
          }
          else
@@ -3189,21 +3187,14 @@
          for (SearchResultEntry entry : entries)
          {
            /*
             * Check the ADD and ModRDN date of the child entry. If it is after
             * the delete date then keep the entry as a conflicting entry,
             * otherwise delete the entry with the operation.
             * Check the ADD and ModRDN date of the child entry (All of them,
             * not only the one that are newer than the DEL op)
             * and keep the entry as a conflicting entry,
             */
            if (cn != null)
            {
              Historical hist = Historical.load(entry);
              if (hist.AddedOrRenamedAfter(cn))
              {
                conflict = true;
                markConflictEntry(conflictOp, entry.getDN(), entryDN);
                renameConflictEntry(conflictOp, entry.getDN(),
                                Historical.getEntryUuid(entry));
              }
            }
            conflict = true;
            markConflictEntry(conflictOp, entry.getDN(), entryDN);
            renameConflictEntry(conflictOp, entry.getDN(),
                Historical.getEntryUuid(entry));
          }
        }
      }
opends/src/server/org/opends/server/replication/protocol/DeleteMsg.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.replication.protocol;
@@ -31,6 +31,7 @@
import java.io.UnsupportedEncodingException;
import java.util.zip.DataFormatException;
import org.opends.server.controls.SubtreeDeleteControl;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.common.ChangeNumber;
@@ -43,7 +44,10 @@
 */
public class DeleteMsg extends LDAPUpdateMsg
{
  String initiatorsName;
  private String initiatorsName;
  // whether the DEL operation is a subtree DEL
  private boolean isSubtreeDelete = false;
  /**
   * Creates a new delete message.
@@ -54,15 +58,23 @@
  {
    super((OperationContext) operation.getAttachment(SYNCHROCONTEXT),
           operation.getRawEntryDN().toString());
    try
    {
      if (operation.getRequestControl(SubtreeDeleteControl.DECODER) != null)
        isSubtreeDelete = true;
    }
    catch(Exception e)
    {}
  }
  /**
   * Creates a new delete message.
   *
   * @param dn The dn with which the message must be created.
   * @param dn           The dn with which the message must be created.
   * @param changeNumber The change number with which the message must be
   *                     created.
   * @param uid The unique id with which the message must be created.
   * @param uid          The unique id with which the message must be created.
   */
  public DeleteMsg(String dn, ChangeNumber changeNumber, String uid)
  {
@@ -87,6 +99,12 @@
    // protocol version has been read as part of the header
    if (protocolVersion >= 4)
      decodeBody_V4(in, pos);
    else
    {
      // Keep the previous protocol version behavior - when we don't know the
      // truth, we assume 'subtree'
      isSubtreeDelete = true;
    }
  }
@@ -101,6 +119,10 @@
        InternalClientConnection.nextOperationID(),
        InternalClientConnection.nextMessageID(), null,
        ByteString.valueOf(newDn));
    if (isSubtreeDelete)
      del.addRequestControl(new SubtreeDeleteControl(false));
    DeleteContext ctx = new DeleteContext(getChangeNumber(), getUniqueId());
    del.setAttachment(SYNCHROCONTEXT, ctx);
    return del;
@@ -152,6 +174,8 @@
    {
      bodyLength++;
    }
    // subtree flag
    bodyLength++;
    /* encode the header in a byte[] large enough to also contain the mods */
    byte [] encodedMsg = encodeHeader(MSG_TYPE_DELETE, bodyLength,
@@ -163,6 +187,9 @@
      encodedMsg[pos++] = 0;
    pos = addByteArray(byteEntryAttrLen, encodedMsg, pos);
    pos = addByteArray(encodedEclIncludes, encodedMsg, pos);
    encodedMsg[pos++] = (isSubtreeDelete ? (byte) 1 : (byte) 0);
    return encodedMsg;
  }
@@ -173,7 +200,6 @@
  private void decodeBody_V4(byte[] in, int pos)
  throws DataFormatException, UnsupportedEncodingException
  {
    // Read ecl attr len
    int length = getNextLength(in, pos);
    if (length != 0)
    {
@@ -185,15 +211,21 @@
      initiatorsName = null;
      pos += 1;
    }
    // Read ecl attr len
    length = getNextLength(in, pos);
    int eclAttrLen = Integer.valueOf(new String(in, pos, length,"UTF-8"));
    // Skip the length
    pos += length + 1;
    // Read/Don't decode entry attributes
    encodedEclIncludes = new byte[eclAttrLen];
    try
    {
      // Copy ecl attr
      System.arraycopy(in, pos, encodedEclIncludes, 0, eclAttrLen);
      // Skip the attrs
      pos += eclAttrLen +1;
    } catch (IndexOutOfBoundsException e)
    {
      throw new DataFormatException(e.getMessage());
@@ -204,6 +236,10 @@
    {
      throw new DataFormatException(e.getMessage());
    }
    // subtree flag
    isSubtreeDelete = (in[pos] == 1);
  }
  /**
@@ -263,4 +299,21 @@
    return initiatorsName;
  }
  /**
   * Set the subtree flag.
   * @param subtreeDelete the subtree flag.
   */
  public void setSubtreeDelete(boolean subtreeDelete)
  {
    this.isSubtreeDelete = subtreeDelete;
  }
  /**
   * Get the subtree flag.
   * @return the subtree flag.
   */
  public boolean isSubtreeDelete()
  {
    return this.isSubtreeDelete;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 */
package org.opends.server.replication;
@@ -1194,12 +1194,18 @@
    assertNull(getEntry(DN.decode(domain1dn), 10000, false),
        "The DELETE replication message was not replayed");
    // check that domain2 and domain3 have not been renamed as conflicting
    assertNull(getEntry(conflictDomain2dn, 10000, true),
        "The conflicting entries were created");
    assertNull(getEntry(conflictDomain3dn, 10000, true),
        "The conflicting entries were created");
    // check that domain2 and domain3 have been renamed as conflicting
    String confDomain2dn = "entryuuid="+domain2uid+"+dc=domain2,ou=people,"+TEST_ROOT_DN_STRING;
    String confDomain3dn = "entryuuid="+domain3uid+"+dc=domain3,ou=people,"+TEST_ROOT_DN_STRING;
    assertTrue(DirectoryServer.entryExists(DN.decode(confDomain2dn)),
    "The conflicting entry exist for domain2" + confDomain2dn);
    assertTrue(DirectoryServer.entryExists(DN.decode(confDomain3dn)),
    "The conflicting entry exist for domain3" + confDomain3dn);
    // check that unresolved conflict count has been incremented
    assertEquals(getMonitorDelta(), 1);
    delEntry(DN.decode(confDomain2dn));
    delEntry(DN.decode(confDomain3dn));
    //
    // Check that when an entry is added on one master below an entry
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/NamingConflictTest.java
@@ -22,12 +22,13 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 */
package org.opends.server.replication.plugin;
import static org.opends.server.TestCaseUtils.TEST_ROOT_DN_STRING;
import java.util.ArrayList;
import java.util.TreeSet;
import org.opends.server.TestCaseUtils;
@@ -36,7 +37,10 @@
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.common.ChangeNumberGenerator;
import org.opends.server.replication.protocol.AddMsg;
import org.opends.server.replication.protocol.ModifyDNMsg;
import org.opends.server.replication.protocol.DeleteMsg;
import org.opends.server.types.Attribute;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.testng.annotations.Test;
@@ -113,7 +117,10 @@
          TEST_ROOT_DN_STRING,
      "uid=simultaneous2");
      // Put the message in the replay queue
      domain.processUpdate(modDnMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // This MODIFY DN uses an older DN and should therefore be cancelled
@@ -124,9 +131,14 @@
          TEST_ROOT_DN_STRING,
      "uid=simulatneouswrong");
      // Put the message in the replay queue
      domain.processUpdate(modDnMsg);
      // Make the domain replay the change from the replay queue
      // and resolve conflict
      domain.replay(queue.take().getUpdateMessage());
      // Expect the conflict resolution
      assertFalse(DirectoryServer.entryExists(entry.getDN()),
      "The modDN conflict was not resolved as expected.");
    }
@@ -135,4 +147,271 @@
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
  /**
   * Tests for issue 3891
   *       S1                                       S2
   *       ADD uid=xx,ou=parent,...          [SUBTREE] DEL ou=parent, ...
   *
   * 1/ removeParentConflict1 (on S1)
   *    - t1(cn1) ADD uid=xx,ou=parent,...
   *         - t2(cn2) replay SUBTREE DEL ou=parent, ....
   *    => No conflict : expect the parent entry & subtree to be deleted
   *
   * 2/ removeParentConflict2 (on S1)
   *    - t1(cn1) ADD uid=xx,ou=parent,...
   *             - replay t2(cn2) DEL ou=parent, ....
   *    => Conflict and no automatic resolution: expect
   *         - the child entry to be renamed under root entry
   *         - the parent entry to be deleted
   *
   * 3/ removeParentConflict3 (on S2)
   *                         - t2(cn2) DEL or SUBTREE DEL ou=parent, ....
   *                         - t1(cn1) replay ADD uid=xx,ou=parent,...
   *                        => Conflict and no automatic resolution: expect
   *                           - the child entry to be renamed under root entry
   *
   */
  @Test(enabled=true)
  public void removeParentConflict1() throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
    DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
    conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
    LDAPReplicationDomain domain =
      MultimasterReplication.createNewDomain(conf, queue);
    domain.start();
    try
    {
      /*
       * Create a Change number generator to generate new ChangeNumbers
       * when we need to send operations messages to the replicationServer.
       */
      ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
      Entry parentEntry = TestCaseUtils.entryFromLdifString(
          "dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: organizationalUnit\n");
      Entry childEntry = TestCaseUtils.entryFromLdifString(
          "dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: person\n"
          + "objectClass: organizationalPerson\n"
          + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
          + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
          + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
          + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
          + "cn: Aaccf Amar\n" + "l: Rockford\n"
          + "street: 17984 Thirteenth Street\n"
          + "employeeNumber: 1\n"
          + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
          + "userPassword: password\n" + "initials: AA\n");
      TestCaseUtils.addEntry(parentEntry);
      TestCaseUtils.addEntry(childEntry);
      String parentUUID = getEntryUUID(parentEntry.getDN());
      String childUUID = getEntryUUID(childEntry.getDN());
      ChangeNumber cn2 = gen.newChangeNumber();
      DeleteMsg  delMsg = new DeleteMsg(
          parentEntry.getDN().toNormalizedString(),
          cn2,
          parentUUID);
      delMsg.setSubtreeDelete(true);
      // Put the message in the replay queue
      domain.processUpdate(delMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // Expect the subtree to be deleted and no conflict entry created
      assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
      "DEL subtree on parent was not processed as expected.");
      assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
      "DEL subtree on parent was not processed as expected.");
    }
    finally
    {
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
  @Test(enabled=true)
  public void removeParentConflict2() throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
    DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
    conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
    LDAPReplicationDomain domain =
      MultimasterReplication.createNewDomain(conf, queue);
    domain.start();
    try
    {
      /*
       * Create a Change number generator to generate new ChangeNumbers
       * when we need to send operations messages to the replicationServer.
       */
      ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
      Entry parentEntry = TestCaseUtils.entryFromLdifString(
          "dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: organizationalUnit\n");
      Entry childEntry = TestCaseUtils.entryFromLdifString(
          "dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: person\n"
          + "objectClass: organizationalPerson\n"
          + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
          + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
          + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
          + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
          + "cn: Aaccf Amar\n" + "l: Rockford\n"
          + "street: 17984 Thirteenth Street\n"
          + "employeeNumber: 1\n"
          + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
          + "userPassword: password\n" + "initials: AA\n");
      TestCaseUtils.addEntry(parentEntry);
      TestCaseUtils.addEntry(childEntry);
      assertTrue(DirectoryServer.entryExists(parentEntry.getDN()),
      "Parent entry expected to exist.");
      assertTrue(DirectoryServer.entryExists(childEntry.getDN()),
      "Child  entry expected to be exist.");
      String parentUUID = getEntryUUID(parentEntry.getDN());
      String childUUID = getEntryUUID(childEntry.getDN());
      ChangeNumber cn2 = gen.newChangeNumber();
      DeleteMsg  delMsg = new DeleteMsg(
          parentEntry.getDN().toNormalizedString(),
          cn2,
          parentUUID);
      // NOT SUBTREE
      // Put the message in the replay queue
      domain.processUpdate(delMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // Expect the parent entry to be deleted
      assertTrue(!DirectoryServer.entryExists(parentEntry.getDN()),
          "Parent entry expected to be deleted : " + parentEntry.getDN());
      // Expect the child entry to be moved as conflict entry under the root
      // entry of the suffix
      DN childDN = DN.decode("entryuuid="+childUUID+
          "+cn=child,o=test");
      assertTrue(DirectoryServer.entryExists(childDN),
          "Child entry conflict exist with DN="+childDN);
    }
    finally
    {
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
  @Test(enabled=true)
  public void removeParentConflict3() throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    final DN baseDn = DN.decode(TEST_ROOT_DN_STRING);
    TestSynchronousReplayQueue queue = new TestSynchronousReplayQueue();
    DomainFakeCfg conf = new DomainFakeCfg(baseDn, 1, new TreeSet<String>());
    conf.setIsolationPolicy(IsolationPolicy.ACCEPT_ALL_UPDATES);
    LDAPReplicationDomain domain =
      MultimasterReplication.createNewDomain(conf, queue);
    domain.start();
    try
    {
      /*
       * Create a Change number generator to generate new ChangeNumbers
       * when we need to send operations messages to the replicationServer.
       */
      ChangeNumberGenerator gen = new ChangeNumberGenerator(201, 0);
      Entry parentEntry = TestCaseUtils.entryFromLdifString(
          "dn: ou=rpConflict, "+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: organizationalUnit\n");
      Entry childEntry = TestCaseUtils.entryFromLdifString(
          "dn: cn=child, ou=rpConflict,"+ TEST_ROOT_DN_STRING + "\n"
          + "objectClass: top\n"
          + "objectClass: person\n"
          + "objectClass: organizationalPerson\n"
          + "objectClass: inetOrgPerson\n" + "uid: user.1\n"
          + "description: This is the description for Aaccf Amar.\n" + "st: NC\n"
          + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
          + "$Rockford, NC  85762\n" + "mail: user.1@example.com\n"
          + "cn: Aaccf Amar\n" + "l: Rockford\n"
          + "street: 17984 Thirteenth Street\n"
          + "employeeNumber: 1\n"
          + "sn: Amar\n" + "givenName: Aaccf\n" + "postalCode: 85762\n"
          + "userPassword: password\n" + "initials: AA\n");
      TestCaseUtils.addEntry(parentEntry);
      String parentUUID = getEntryUUID(parentEntry.getDN());
      TestCaseUtils.deleteEntry(parentEntry);
      ChangeNumber cn1 = gen.newChangeNumber();
      // Create and publish an update message to add the child entry.
      String childUUID = "44444444-4444-4444-4444-444444444444";
      AddMsg addMsg = new AddMsg(
          cn1,
          childEntry.getDN().toString(),
          childUUID,
          parentUUID,
          childEntry.getObjectClassAttribute(),
          childEntry.getAttributes(),
          new ArrayList<Attribute>());
      // Put the message in the replay queue
      domain.processUpdate(addMsg);
      // Make the domain replay the change from the replay queue
      domain.replay(queue.take().getUpdateMessage());
      // Expect the parent entry to be deleted
      assertFalse(DirectoryServer.entryExists(parentEntry.getDN()),
          "Parent entry exists ");
      // Expect the child entry to be moved as conflict entry under the root
      // entry of the suffix
      DN childDN = DN.decode("entryuuid="+childUUID+
          "+cn=child,o=test");
      assertTrue(DirectoryServer.entryExists(childDN),
          "Child entry conflict exist with DN="+childDN);
    }
    finally
    {
      MultimasterReplication.deleteDomain(baseDn);
    }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -79,7 +79,7 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.controls.SubtreeDeleteControl;
/**
 * Test the constructors, encoders and decoders of the replication protocol
 * PDUs classes (message classes)
@@ -321,8 +321,8 @@
    entryAttrList.add(eattr2);
    return new Object[][] {
        {"dc=com", entryAttrList},
        {"dc=delete,dc=an,dc=entry,dc=with,dc=a,dc=long dn", null},
        {"dc=com", entryAttrList, false},
        {"dc=delete,dc=an,dc=entry,dc=with,dc=a,dc=long dn", null, true},
        };
  }
@@ -333,18 +333,23 @@
   * Finally test that both Msg matches.
   */
  @Test(enabled=true,dataProvider = "createDeleteData")
  public void deleteMsgTest(String rawDN, List<Attribute> entryAttrList)
  public void deleteMsgTest(String rawDN, List<Attribute> entryAttrList,
      boolean subtree)
  throws Exception
  {
    InternalClientConnection connection =
        InternalClientConnection.getRootConnection();
    DeleteOperationBasis opBasis =
      new DeleteOperationBasis(connection, 1, 1,null, DN.decode(rawDN));
    if (subtree)
    {
      opBasis.addRequestControl(new SubtreeDeleteControl(false));
    }
    LocalBackendDeleteOperation op = new LocalBackendDeleteOperation(opBasis);
    ChangeNumber cn = new ChangeNumber(TimeThread.getTime(),123,  45);
    op.setAttachment(SYNCHROCONTEXT, new DeleteContext(cn, "uniqueid"));
    DeleteMsg msg = new DeleteMsg(op);
    assertTrue((msg.isSubtreeDelete()==subtree));
    // Set ECL entry attributes
    if (entryAttrList != null)
    {
@@ -356,8 +361,8 @@
    assertEquals(msg.toString(), generatedMsg.toString());
    assertEquals(msg.getInitiatorsName(), generatedMsg.getInitiatorsName());
    assertEquals(msg.getChangeNumber(), generatedMsg.getChangeNumber());
    assertEquals(generatedMsg.isSubtreeDelete(), subtree);
    // Get ECL entry attributes
    ArrayList<RawAttribute> genAttrList = generatedMsg.getEclIncludes();
@@ -379,6 +384,9 @@
    Operation generatedOperation = generatedMsg.createOperation(connection);
    assertEquals(generatedOperation.getClass(), DeleteOperationBasis.class);
    assertTrue(
        (subtree?(generatedOperation.getRequestControl(SubtreeDeleteControl.DECODER)!=null):
          (generatedOperation.getRequestControl(SubtreeDeleteControl.DECODER)==null)));
    DeleteOperationBasis mod2 = (DeleteOperationBasis) generatedOperation;
@@ -387,6 +395,7 @@
    // Create an update message from this op
    DeleteMsg updateMsg = (DeleteMsg) LDAPUpdateMsg.generateMsg(op);
    assertEquals(msg.getChangeNumber(), updateMsg.getChangeNumber());
    assertEquals(msg.isSubtreeDelete(), updateMsg.isSubtreeDelete());
  }
  @DataProvider(name = "createModifyDnData")