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

gbellato
22.36.2007 79a8669d6be5ac1fe2f1b62e3a48568200bd3148
issue 1804 : Ensure that conflicts are visible to administrators

The replication monitoring information already provides the
attribute unresolved-naming-conflicts that counts the number of conflicts
that was not automatically resolved since last startup.

The conflicting entries are also marked with the ds-sync-confict attribute
so that administrators can look for these entries
using filter ds-sync-confict=*

This code add the generation of an administrative alert when a conflict is
detected so that administrators can be made aware of the problem.
6 files modified
177 ■■■■■ changed files
opends/src/messages/messages/replication.properties 1 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java 67 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 19 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java 2 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java 81 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java 7 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/replication.properties
@@ -187,3 +187,4 @@
MILD_ERR_UNKNOWN_ATTRIBUTE_IN_HISTORICAL_68=The entry %s has historical \
 information for attribute %s which is not defined in the schema. This \
 information will be ignored
NOTICE_UNRESOLVED_CONFLICT_69=An unresolved conflict was detected for DN %s
opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
@@ -38,11 +38,13 @@
import static org.opends.server.replication.protocol.OperationContext.*;
import static org.opends.server.util.StaticUtils.createEntry;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.util.ServerConstants.*;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
@@ -56,6 +58,7 @@
import org.opends.server.admin.std.meta.MultimasterDomainCfgDefn.*;
import org.opends.server.admin.std.server.MultimasterDomainCfg;
import org.opends.server.admin.std.server.BackendCfg;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.Backend;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.SynchronizationProvider;
@@ -135,9 +138,16 @@
 *  handle protocol messages from the replicationServer.
 */
public class ReplicationDomain extends DirectoryThread
       implements ConfigurationChangeListener<MultimasterDomainCfg>
       implements ConfigurationChangeListener<MultimasterDomainCfg>,
                  AlertGenerator
{
  /**
   * The fully-qualified name of this class.
   */
  private static final String CLASS_NAME =
       "org.opends.server.replication.plugin.ReplicationDomain";
  /**
   * The attribute used to mark conflicting entries.
   * The value of this attribute should be the dn that this entry was
   * supposed to have when it was marked as conflicting.
@@ -238,6 +248,11 @@
  private IsolationPolicy isolationpolicy;
  /**
   * The DN of the configuration entry of this domain.
   */
  private DN configDn;
  /**
   * This class contain the context related to an import or export
   * launched on the domain.
   */
@@ -339,6 +354,7 @@
    window  = configuration.getWindowSize();
    heartbeatInterval = configuration.getHeartbeatInterval();
    isolationpolicy = configuration.getIsolationPolicy();
    configDn = configuration.dn();
    /*
     * Modify conflicts are solved for all suffixes but the schema suffix
@@ -405,6 +421,9 @@
    // listen for changes on the configuration
    configuration.addChangeListener(this);
    // register as an AltertGenerator
    DirectoryServer.registerAlertGenerator(this);
  }
@@ -1160,6 +1179,8 @@
    DirectoryServer.deregisterMonitorProvider(monitor.getMonitorInstanceName());
    DirectoryServer.deregisterAlertGenerator(this);
    // stop the ReplicationBroker
    broker.stop();
@@ -1899,10 +1920,16 @@
      mb.append(String.valueOf(newOp.getResultCode()));
      logError(mb.toMessage());
    }
    // Generate an alert to let the administratot know that some
    // conflict could not be solved.
    Message alertMessage = NOTE_UNRESOLVED_CONFLICT.get(conflictDN.toString());
    DirectoryServer.sendAlertNotification(this,
        ALERT_TYPE_REPLICATION_UNRESOLVED_CONFLICT, alertMessage);
  }
  /**
   * Add the conflict object class to an entry that could
   * Add the conflict attribute to an entry that could
   * not be added because it is conflicting with another entry.
   *
   * @param msg            The conflicting Add Operation.
@@ -1912,6 +1939,13 @@
   */
  private void addConflict(AddMsg msg) throws ASN1Exception
  {
    // Generate an alert to let the administratot know that some
    // conflict could not be solved.
    Message alertMessage = NOTE_UNRESOLVED_CONFLICT.get(msg.getDn());
    DirectoryServer.sendAlertNotification(this,
        ALERT_TYPE_REPLICATION_UNRESOLVED_CONFLICT, alertMessage);
    // Add the conflict attribute
    msg.addAttribute(DS_SYNC_CONFLICT, msg.getDn());
  }
@@ -2916,4 +2950,33 @@
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public LinkedHashMap<String, String> getAlerts()
  {
    LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
    alerts.put(ALERT_TYPE_REPLICATION_UNRESOLVED_CONFLICT,
               ALERT_DESCRIPTION_REPLICATION_UNRESOLVED_CONFLICT);
    return alerts;
  }
  /**
   * {@inheritDoc}
   */
  public String getClassName()
  {
    return CLASS_NAME;
  }
  /**
   * {@inheritDoc}
   */
  public DN getComponentEntryDN()
  {
    return configDn;
  }
}
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2634,5 +2634,24 @@
   * the SMTP server.
   */
  public static final String SMTP_PROPERTY_PORT = "mail.smtp.port";
  /**
   * The description for the alert type that will be used for the alert
   * notification generated if the multimaster replication detects
   * a conflict that cannot be solved automatically.
   */
  public static final String ALERT_DESCRIPTION_REPLICATION_UNRESOLVED_CONFLICT =
          "This alert type will be used to notify administrators if the  " +
          "multimaster replication cannot resolve automatically a conflict.";
  /**
   * The alert type string that will be used for the alert notification
   * generated if the multimaster replication detects
   * a conflict that cannot be solved automatically.
   */
  public static final String ALERT_TYPE_REPLICATION_UNRESOLVED_CONFLICT =
          "org.opends.server.replication.UnresolvedConflict";
}
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -490,7 +490,6 @@
    monitorAttr = attr;
    try
    {
      Thread.sleep(2000);
      lastCount = getMonitorAttrValue(baseDn, attr);
    }
    catch (Exception ex)
@@ -507,7 +506,6 @@
  protected long getMonitorDelta() {
    long delta = 0;
    try {
      Thread.sleep(2000);
      long currentCount = getMonitorAttrValue(monitorDn, monitorAttr);
      delta = (currentCount - lastCount);
      lastCount = currentCount;
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
@@ -51,6 +51,7 @@
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.extensions.DummyAlertHandler;
import org.opends.server.plugins.ShortCircuitPlugin;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
@@ -80,6 +81,7 @@
import org.opends.server.types.RDN;
import org.opends.server.types.RawModification;
import org.opends.server.types.ResultCode;
import org.opends.server.util.TimeThread;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -653,6 +655,7 @@
        DN.decode("cn=something,ou=People,dc=example,dc=com"), mods,
        user1entryUUID);
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    int AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modMsg);
    // check that the modify has been applied as if the entry had been renamed.
@@ -662,6 +665,11 @@
     fail("The modification has not been correctly replayed.");
    assertEquals(getMonitorDelta(), 1);
    // check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Test that the conflict resolution code is able to detect
     * that and entry have been renamed and that a new entry has
@@ -691,15 +699,23 @@
    modMsg = new ModifyMsg(gen.newChangeNumber(),
        DN.decode(user1dn), mods, "10000000-9abc-def0-1234-1234567890ab");
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modMsg);
    // check that the modify has not been applied
    TimeThread.sleep(2000);
    found = checkEntryHasAttribute(personWithUUIDEntry.getDN(),
                           "telephonenumber", "02 01 03 05", 10000, false);
    if (found == true)
     fail("The modification has been replayed while it should not.");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Test that the conflict resolution code is able to find entries
@@ -714,6 +730,7 @@
      new DeleteMsg("cn=anotherdn,ou=People,dc=example,dc=com",
          gen.newChangeNumber(), user1entryUUID);
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(delMsg);
    // check that the delete operation has been applied
@@ -722,6 +739,10 @@
    assertNull(resultEntry,
        "The DELETE replication message was not replayed");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Test that two adds with the same DN but a different unique ID result
@@ -749,6 +770,7 @@
        personWithSecondUniqueID.getObjectClassAttribute(),
        personWithSecondUniqueID.getAttributes(), new ArrayList<Attribute>());
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(addMsg);
    //  Check that the entry has been renamed and created in the local DS.
@@ -759,6 +781,11 @@
        "The ADD replication message was not applied");
    assertEquals(getMonitorDelta(), 1);
    assertConflictAttribute(resultEntry);
    // Check that there was an administrative alert generated
    // because the conflict has not been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+1,
        "An alert was not generated when resolving conflicts");
    //  delete the entries to clean the database.
    delMsg =
@@ -788,6 +815,7 @@
        personWithUUIDEntry.getObjectClassAttribute(),
        personWithUUIDEntry.getAttributes(), new ArrayList<Attribute>());
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(addMsg);
    //  Check that the entry has been renamed and created in the local DS.
@@ -796,6 +824,11 @@
    assertNotNull(resultEntry,
        "The ADD replication message was not applied");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Check that when replaying delete the naming conflict code
@@ -810,6 +843,7 @@
      new DeleteMsg("uid=new person,ou=People,dc=example,dc=com",
          gen.newChangeNumber(), "11111111-9abc-def0-1234-1234567890ab");
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(delMsg);
    resultEntry = getEntry(
          DN.decode("uid=new person,ou=People,dc=example,dc=com"), 10000, true);
@@ -819,6 +853,11 @@
        "The DELETE replication message was replayed when it should not");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Check that when replaying modify dn operations, the conflict
@@ -835,6 +874,7 @@
        "uid=wrong, ou=people,dc=example,dc=com",
        "uid=newrdn");
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modDnMsg);
    resultEntry = getEntry(
@@ -845,6 +885,12 @@
      "The modify dn was not or badly replayed");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * same test but by giving a bad entry DN
     */
@@ -853,6 +899,7 @@
        "uid=wrong,ou=People,dc=example,dc=com", gen.newChangeNumber(),
        user1entryUUID, baseUUID, false, null, "uid=reallynewrdn");
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modDnMsg);
    resultEntry = getEntry(
@@ -863,6 +910,12 @@
      "The modify dn was not or badly replayed");
    assertEquals(getMonitorDelta(), 1);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    /*
     * Check that conflicting entries are renamed when a
     * modifyDN is done with the same DN as an entry added on another server.
@@ -888,6 +941,7 @@
                               user1entrysecondUUID, baseUUID, false,
                               baseDn.toString(), "uid=reallynewrdn");
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modDnMsg);
   // check that the second entry has been renamed
@@ -898,6 +952,12 @@
    assertEquals(getMonitorDelta(), 1);
    assertConflictAttribute(resultEntry);
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+1,
        "An alert was not generated when resolving conflicts");
    // delete the entries to clean the database
    delMsg =
      new DeleteMsg("uid=reallynewrdn,ou=People,dc=example,dc=com",
@@ -1003,6 +1063,7 @@
    // - publish msg
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(addMsg);
    // - check that the Dn has been changed to baseDn2
@@ -1018,6 +1079,12 @@
    entryList.add(resultEntry.getDN());
    assertEquals(getMonitorDelta(), 1);
    
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    //
    // Check that when a delete is conflicting with Add of some entries
    // below the deleted entries, the child entry that have been added
@@ -1037,6 +1104,7 @@
        "entryUUID = " + domain3uid + "+dc=domain3,ou=people,dc=example,dc=com");
 
    updateMonitorCount(baseDn, unresolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    
    // delete domain1
    delMsg = new DeleteMsg(domain1dn, gen.newChangeNumber(), domain1uid);
@@ -1061,6 +1129,12 @@
    // check that unresolved conflict count has been incremented
    assertEquals(getMonitorDelta(), 1);
    
    // Check that an administrative alert was generated
    // because the conflict has not been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount+2,
        "An alert was incorrectly generated when resolving conflicts");
    // delete the resulting entries for the next test
    delEntry(conflictDomain2dn);
    delEntry(conflictDomain3dn);
@@ -1097,6 +1171,7 @@
        "uid=wrong, ou=people,dc=example,dc=com",
        "uid=newrdn");
    updateMonitorCount(baseDn, resolvedMonitorAttr);
    AlertCount = DummyAlertHandler.getAlertCount();
    broker.publish(modDnMsg);
    // unfortunately it is difficult to check that the operation
    // did not do anything.
@@ -1104,6 +1179,12 @@
    // has correctly been incremented.
    assertEquals(getMonitorDelta(), 1);
    
    // Check that there was no administrative alert generated
    // because the conflict has been automatically resolved.
    assertEquals(DummyAlertHandler.getAlertCount(), AlertCount,
        "An alert was incorrectly generated when resolving conflicts");
    broker.stop();
  }
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
@@ -35,6 +35,7 @@
import org.opends.server.admin.std.meta.MultimasterDomainCfgDefn.IsolationPolicy;
import org.opends.server.admin.std.server.MultimasterDomainCfg;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
/**
 * This class implement a configuration object for the MultimasterDomain
@@ -161,8 +162,14 @@
   */
  public DN dn()
  {
    try
    {
      return DN.decode("cn=domain, cn=domains,cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
    } catch (DirectoryException e)
    {
    return null;
  }
  }
  /**
   * {@inheritDoc}