From 79a8669d6be5ac1fe2f1b62e3a48568200bd3148 Mon Sep 17 00:00:00 2001
From: gbellato <gbellato@localhost>
Date: Wed, 22 Aug 2007 07:36:24 +0000
Subject: [PATCH] issue 1804 : Ensure that conflicts are visible to administrators

---
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java  |   81 ++++++++++++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java  |    2 
 opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java                     |   67 ++++++++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java |    9 ++
 opends/src/messages/messages/replication.properties                                               |    3 
 opends/src/server/org/opends/server/util/ServerConstants.java                                     |   19 ++++
 6 files changed, 175 insertions(+), 6 deletions(-)

diff --git a/opends/src/messages/messages/replication.properties b/opends/src/messages/messages/replication.properties
index 0083d79..0b61dc0 100644
--- a/opends/src/messages/messages/replication.properties
+++ b/opends/src/messages/messages/replication.properties
@@ -186,4 +186,5 @@
  ChangeNumber %s error %s %s
 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
\ No newline at end of file
+ information will be ignored
+NOTICE_UNRESOLVED_CONFLICT_69=An unresolved conflict was detected for DN %s
\ No newline at end of file
diff --git a/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java b/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
index 4713ac4..e2840f8 100644
--- a/opends/src/server/org/opends/server/replication/plugin/ReplicationDomain.java
+++ b/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;
+  }
 }
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index 9ce1489..080dc21 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/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";
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
index 4c6c666..016d567 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
+++ b/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;
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
index 4d1d947..394dc9e 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/UpdateOperationTest.java
+++ b/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.
@@ -661,6 +664,11 @@
     if (found == false)
      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
@@ -691,14 +699,22 @@
     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");
+
 
 
     /*
@@ -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);
@@ -818,6 +852,11 @@
     assertNotNull(resultEntry,
         "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");
 
 
     /*
@@ -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(
@@ -844,6 +884,12 @@
     assertNotNull(resultEntry,
       "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(
@@ -862,6 +909,12 @@
     assertNotNull(resultEntry,
       "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
@@ -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
@@ -897,6 +951,12 @@
     assertNotNull(resultEntry, "The modifyDN was not or incorrectly replayed");
     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 =
@@ -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();
   }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
index d1c3050..e39f6bc 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
+++ b/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,7 +162,13 @@
    */
   public DN dn()
   {
-    return null;
+    try
+    {
+      return DN.decode("cn=domain, cn=domains,cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
+    } catch (DirectoryException e)
+    {
+      return null;
+    }
   }
 
   /**

--
Gitblit v1.10.0