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

gbellato
22.57.2007 3211d8caa762e2242a6c2669e1b91bd97025f33b
Fix for 1602 : removing root entries of domain cause replay of many changes

Before this fix, If one remove the root entry of a replication domain,
then stop the server The PersistentServerState is lost.
Therefore when later restarting the server all the changes from
the replication server are replayed on the database.

To avoid this the fix is simply to store the PersistentServerState to the configuration entry when the root entry does not exist.

I have not developed a unit test for this scenario because I believe that it is not possible to restart the server during the unit test.
I have done manual tests to check that the PersistentServerState
is correctly saved and reread.
1 files modified
157 ■■■■ changed files
opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java 157 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/PersistentServerState.java
@@ -51,10 +51,12 @@
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.LDAPException;
import org.opends.server.types.ModificationType;
import org.opends.server.types.RawModification;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
@@ -118,18 +120,54 @@
   */
  public void loadState()
  {
    SearchResultEntry stateEntry = null;
    // try to load the state from the base entry.
    stateEntry = searchBaseEntry();
    if (stateEntry == null)
    {
      // The base entry does not exist yet
      // in the database or was deleted. Try to read the ServerState
      // from the configuration instead.
      stateEntry = searchConfigEntry();
    }
    if (stateEntry != null)
    {
      updateStateFromEntry(stateEntry);
    }
    /*
     * Read the serverState from the database,
     * If not there create empty entry
     * TODO : The ServerState is saved to the database periodically,
     * therefore in case of crash it is possible that is does not contain
     * the latest changes that have been processed and saved to the
     * database.
     * In order to make sure that we don't loose them, search all the entries
     * that have been updated after this entry.
     * This is done by using the HistoricalCsnOrderingMatchingRule
     * and an ordering index for historical attribute
     */
  }
  /**
   * Run a search operation to find the base entry
   * of the replication domain for which this ServerState was created.
   *
   * @return Thebasen Entry or null if no entry was found;
   */
  private SearchResultEntry searchBaseEntry()
  {
    LDAPFilter filter;
    try
    {
      filter = LDAPFilter.decode("objectclass=*");
    } catch (LDAPException e)
    {
      // can not happen
      return;
      return null;
    }
    /*
@@ -149,17 +187,75 @@
          get(search.getResultCode().getResultCodeName(), search.toString(),
              search.getErrorMessage(), baseDn.toString());
      logError(message);
      return null;
    }
    SearchResultEntry resultEntry = null;
    SearchResultEntry stateEntry = null;
    if (search.getResultCode() == ResultCode.SUCCESS)
    {
      /*
       * Read the serverState from the REPLICATION_STATE attribute
       */
      LinkedList<SearchResultEntry> result = search.getSearchEntries();
      resultEntry = result.getFirst();
      if (resultEntry != null)
      if (!result.isEmpty())
      {
        stateEntry = result.getFirst();
      }
    }
    return stateEntry;
  }
  /**
   * Run a search operation to find the entry with the configuration
   * of the replication domain for which this ServerState was created.
   *
   * @return The configuration Entry or null if no entry was found;
   */
  private SearchResultEntry searchConfigEntry()
  {
    try
    {
      SearchFilter filter =
        SearchFilter.createFilterFromString(
            "(&(objectclass=ds-cfg-replication-domain-config)"
            +"(ds-cfg-replication-dn="+baseDn+"))");
      LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
      attributes.add(REPLICATION_STATE);
      InternalSearchOperation op =
          conn.processSearch(DN.decode("cn=config"),
          SearchScope.SUBORDINATE_SUBTREE,
          DereferencePolicy.NEVER_DEREF_ALIASES,
          1, 0, false, filter, attributes);
      if (op.getResultCode() == ResultCode.SUCCESS)
      {
        /*
         * Read the serverState from the REPLICATION_STATE attribute
         */
        LinkedList<SearchResultEntry> resultEntries =
          op.getSearchEntries();
        if (!resultEntries.isEmpty())
        {
          SearchResultEntry resultEntry = resultEntries.getFirst();
          return resultEntry;
        }
      }
      return null;
    } catch (DirectoryException e)
    {
      // can not happen
      return null;
    }
  }
  /**
   * Update this ServerState from the provided entry.
   *
   * @param resultEntry The entry that should be used to update this
   *                    ServerState.
   */
  private void updateStateFromEntry(SearchResultEntry resultEntry)
      {
        AttributeType synchronizationStateType =
          DirectoryServer.getAttributeType(REPLICATION_STATE);
@@ -178,19 +274,6 @@
        }
      }
      /*
       * TODO : The ServerState is saved to the database periodically,
       * therefore in case of crash it is possible that is does not contain
       * the latest changes that have been processed and saved to the
       * database.
       * In order to make sure that we don't loose them, search all the entries
       * that have been updated after this entry.
       * This is done by using the HistoricalCsnOrderingMatchingRule
       * and an ordering index for historical attribute
       */
    }
  }
  /**
   * Save the current values of this PersistentState object
   * in the appropiate entry of the database.
@@ -202,6 +285,32 @@
    /*
     * Generate a modify operation on the Server State baseD Entry.
     */
    ResultCode result = runUpdateStateEntry(baseDn);
    if (result == ResultCode.NO_SUCH_OBJECT)
    {
      // The base entry does not exist yet in the database or
      // has been deleted, save the state to the config entry instead.
      SearchResultEntry configEntry = searchConfigEntry();
      if (configEntry != null)
      {
        DN configDN = configEntry.getDN();
        result = runUpdateStateEntry(configDN);
      }
    }
    return result;
  }
  /**
   * Run a modify operation to update the entry whose DN is given as
   * a parameter with the serverState information.
   *
   * @param serverStateEntryDN The DN of the entry to be updated.
   *
   * @return A ResultCode indicating if the operation was successful.
   */
  private ResultCode runUpdateStateEntry(DN serverStateEntryDN)
  {
    ArrayList<ASN1OctetString> values = this.toASN1ArrayList();
    if (values.size() == 0)
@@ -216,16 +325,14 @@
    ModifyOperationBasis op =
      new ModifyOperationBasis(conn, InternalClientConnection.nextOperationID(),
          InternalClientConnection.nextMessageID(),
          new ArrayList<Control>(0), asn1BaseDn,
          new ArrayList<Control>(0),
          new ASN1OctetString(serverStateEntryDN.toString()),
          mods);
    op.setInternalOperation(true);
    op.setSynchronizationOperation(true);
    op.setDontSynchronize(true);
    op.run();
    ResultCode result = op.getResultCode();
    if ((result != ResultCode.SUCCESS) &&
        ((result != ResultCode.NO_SUCH_OBJECT)))
    if (op.getResultCode() != ResultCode.SUCCESS)
    {
      Message message = DEBUG_ERROR_UPDATING_RUV.get(
              op.getResultCode().getResultCodeName().toString(),
@@ -234,6 +341,6 @@
              baseDn.toString());
      logError(message);
    }
    return result;
    return op.getResultCode();
  }
}