From 74ae8f26373f050f18bc0f0f4b3f0706a1f3f5be Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Thu, 04 Sep 2014 13:01:44 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1206 : Create a new ReplicationBackend/ChangelogBackend   to support cn=changelog CR-4439

---
 opends/src/server/org/opends/server/backends/ChangelogBackend.java                                 |    2 
 opends/src/server/org/opends/server/replication/server/ReplicationServer.java                      |  127 ++++++++++++++++++++++---------
 opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java             |   12 +++
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java |    6 +
 opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java                |   11 ++
 opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java         |   12 +++
 opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java      |   12 +++
 7 files changed, 143 insertions(+), 39 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/ChangelogBackend.java b/opends/src/server/org/opends/server/backends/ChangelogBackend.java
index 11cc90f..9d7613f 100644
--- a/opends/src/server/org/opends/server/backends/ChangelogBackend.java
+++ b/opends/src/server/org/opends/server/backends/ChangelogBackend.java
@@ -1021,7 +1021,7 @@
     final MultiDomainServerState state = searchParams.cookie;
     if (state != null && !state.isEmpty())
     {
-      replicationServer.validateServerState(state, searchParams.getExcludedBaseDNs());
+      replicationServer.validateCookie(state, searchParams.getExcludedBaseDNs());
     }
   }
 
diff --git a/opends/src/server/org/opends/server/replication/server/ReplicationServer.java b/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
index 88ae89c..5c80530 100644
--- a/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
+++ b/opends/src/server/org/opends/server/replication/server/ReplicationServer.java
@@ -581,7 +581,7 @@
    * parameter.
    *
    * @param baseDN
-   *          The base Dn for which the ReplicationServerDomain must be
+   *          The base DN for which the ReplicationServerDomain must be
    *          returned.
    * @return The ReplicationServerDomain associated to the base DN given in
    *         parameter.
@@ -604,70 +604,123 @@
   }
 
   /**
-   * Validate that provided state is coherent with this replication server,
+   * Validate that provided cookie is coherent with this replication server,
    * when ignoring the provided set of DNs.
    * <p>
-   * The state is coherent if and only if it exactly has the set of DNs corresponding to
-   * the replication domains.
+   * The cookie is coherent if and only if it exactly has the set of DNs corresponding to
+   * the replication domains, and the states in the cookie are not older than oldest states
+   * in the server.
    *
-   * @param state
+   * @param cookie
    *            The multi domain state (cookie) to validate.
    * @param ignoredBaseDNs
    *            The set of DNs to ignore when validating
    * @throws DirectoryException
-   *            If the state is not valid
+   *            If the cookie is not valid
    */
-  public void validateServerState(MultiDomainServerState state, Set<DN> ignoredBaseDNs) throws DirectoryException
+  public void validateCookie(MultiDomainServerState cookie, Set<DN> ignoredBaseDNs) throws DirectoryException
   {
-    // Build the two sets of DNs to compare
-    final Set<DN> activeServerDomains = new HashSet<DN>();
+    final Set<DN> activeDomains = getDNsOfActiveDomainsInServer(ignoredBaseDNs);
+    final Set<DN> cookieDomains = getDNsOfCookie(cookie);
+
+    checkNoActiveDomainIsMissingInCookie(cookie, activeDomains, cookieDomains);
+    checkNoUnknownDomainIsProvidedInCookie(cookie, activeDomains, cookieDomains);
+    checkCookieIsNotOutdated(cookie, activeDomains);
+  }
+
+  private Set<DN> getDNsOfCookie(MultiDomainServerState cookie)
+  {
+    final Set<DN> cookieDomains = new HashSet<DN>();
+    for (final DN dn : cookie)
+    {
+      cookieDomains.add(dn);
+    }
+    return cookieDomains;
+  }
+
+  private Set<DN> getDNsOfActiveDomainsInServer(final Set<DN> ignoredBaseDNs) throws DirectoryException
+  {
+    final Set<DN> activeDomains = new HashSet<DN>();
     for (final DN dn : getDomainDNs(ignoredBaseDNs))
     {
       final ServerState lastServerState = getReplicationServerDomain(dn).getLatestServerState();
       if (!lastServerState.isEmpty())
       {
-         activeServerDomains.add(dn);
+         activeDomains.add(dn);
       }
     }
-    final Set<DN> stateDomains = new HashSet<DN>();
-    for (final DN dn : state)
-    {
-      stateDomains.add(dn);
-    }
+    return activeDomains;
+  }
 
-    // The two sets of DNs are expected to be the same. Check this.
-    final Set<DN> domainsCopy = new HashSet<DN>(activeServerDomains);
-    final Set<DN> stateDomainsCopy = new HashSet<DN>(stateDomains);
-    domainsCopy.removeAll(stateDomains);
-    if (!domainsCopy.isEmpty())
+  private void checkNoUnknownDomainIsProvidedInCookie(final MultiDomainServerState cookie, final Set<DN> activeDomains,
+      final Set<DN> cookieDomains) throws DirectoryException
+  {
+    if (!activeDomains.containsAll(cookieDomains))
     {
-      final StringBuilder missingDomains = new StringBuilder();
-      for (DN dn : domainsCopy)
-      {
-        missingDomains.append(dn).append(":;");
-      }
-      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-          ERR_RESYNC_REQUIRED_MISSING_DOMAIN_IN_PROVIDED_COOKIE.get(
-              missingDomains, "<" + state.toString() + missingDomains + ">"));
-    }
-    stateDomainsCopy.removeAll(activeServerDomains);
-    if (!stateDomainsCopy.isEmpty())
-    {
-      final StringBuilder startState = new StringBuilder();
-      for (DN dn : activeServerDomains) {
-        startState.append(dn).append(":").append(state.getServerState(dn).toString()).append(";");
+      final Set<DN> unknownCookieDomains = new HashSet<DN>(cookieDomains);
+      unknownCookieDomains.removeAll(activeDomains);
+      final StringBuilder currentStartingCookie = new StringBuilder();
+      for (DN domainDN : activeDomains) {
+        currentStartingCookie.append(domainDN).append(":").append(cookie.getServerState(domainDN)).append(";");
       }
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
           ERR_RESYNC_REQUIRED_UNKNOWN_DOMAIN_IN_PROVIDED_COOKIE.get(
-              stateDomainsCopy.toString(), startState));
+              unknownCookieDomains.toString(), currentStartingCookie));
     }
   }
 
+  private void checkNoActiveDomainIsMissingInCookie(final MultiDomainServerState cookie, final Set<DN> activeDomains,
+      final Set<DN> cookieDomains) throws DirectoryException
+  {
+    if (!cookieDomains.containsAll(activeDomains))
+    {
+      final Set<DN> missingActiveDomains = new HashSet<DN>(activeDomains);
+      missingActiveDomains.removeAll(cookieDomains);
+      final StringBuilder missingDomains = new StringBuilder();
+      for (DN domainDN : missingActiveDomains)
+      {
+        missingDomains.append(domainDN).append(":;");
+      }
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+          ERR_RESYNC_REQUIRED_MISSING_DOMAIN_IN_PROVIDED_COOKIE.get(
+              missingDomains, "<" + cookie + missingDomains + ">"));
+    }
+  }
+
+  private void checkCookieIsNotOutdated(final MultiDomainServerState cookie, final Set<DN> activeDomains)
+      throws DirectoryException
+  {
+    for (DN dn : activeDomains)
+    {
+      if (isCookieOutdatedForDomain(cookie, dn))
+      {
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+            ERR_RESYNC_REQUIRED_TOO_OLD_DOMAIN_IN_PROVIDED_COOKIE.get(dn.toString()));
+      }
+    }
+  }
+
+  /** Check that provided cookie is not outdated compared to the oldest state of a domain. */
+  private boolean isCookieOutdatedForDomain(MultiDomainServerState cookie, DN domainDN)
+  {
+    final ServerState domainOldestState = getReplicationServerDomain(domainDN).getOldestState();
+    final ServerState providedState = cookie.getServerState(domainDN);
+    for (final CSN oldestCsn : domainOldestState)
+    {
+      final CSN providedCsn = providedState.getCSN(oldestCsn.getServerId());
+      if (providedCsn != null && providedCsn.isOlderThan(oldestCsn))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Get the ReplicationServerDomain associated to the base DN given in
    * parameter.
    *
-   * @param baseDN The base Dn for which the ReplicationServerDomain must be
+   * @param baseDN The base DN for which the ReplicationServerDomain must be
    * returned.
    * @param create Specifies whether to create the ReplicationServerDomain if
    *        it does not already exist.
diff --git a/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java b/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
index 67195ef..4a21ff3 100644
--- a/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
+++ b/opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java
@@ -2382,6 +2382,17 @@
     return attributes;
   }
 
+  /**
+   * Returns the oldest known state for the domain, made of the oldest CSN
+   * stored for each serverId.
+   *
+   * @return the start state of the domain.
+   */
+  public ServerState getOldestState()
+  {
+    return domainDB.getDomainOldestCSNs(baseDN);
+  }
+
   private void sendTopologyMsg(String type, ServerHandler handler,
       TopologyMsg msg)
   {
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java b/opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java
index 32f9fba..f16b149 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java
@@ -43,6 +43,18 @@
 {
 
   /**
+   * Returns the oldest {@link CSN}s from the replicaDBs for each serverId in
+   * the specified replication domain.
+   *
+   * @param baseDN
+   *          the replication domain baseDN
+   * @return a new ServerState object holding the {serverId => oldest CSN}
+   *         mapping. If a replica DB is empty or closed, the oldest CSN will be
+   *         null for that replica. The caller owns the generated ServerState.
+   */
+  ServerState getDomainOldestCSNs(DN baseDN);
+
+  /**
    * Returns the newest {@link CSN}s from the replicaDBs for each serverId in
    * the specified replication domain.
    *
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java b/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
index 6458bac..1801956 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
@@ -485,6 +485,18 @@
 
   /** {@inheritDoc} */
   @Override
+  public ServerState getDomainOldestCSNs(DN baseDN)
+  {
+    final ServerState result = new ServerState();
+    for (FileReplicaDB replicaDB : getDomainMap(baseDN).values())
+    {
+      result.update(replicaDB.getOldestCSN());
+    }
+    return result;
+  }
+
+  /** {@inheritDoc} */
+  @Override
   public ServerState getDomainNewestCSNs(DN baseDN)
   {
     final ServerState result = new ServerState();
diff --git a/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java b/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
index 5f5ae2a..7200e00 100644
--- a/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
+++ b/opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java
@@ -512,6 +512,18 @@
 
   /** {@inheritDoc} */
   @Override
+  public ServerState getDomainOldestCSNs(DN baseDN)
+  {
+    final ServerState result = new ServerState();
+    for (JEReplicaDB replicaDB : getDomainMap(baseDN).values())
+    {
+      result.update(replicaDB.getOldestCSN());
+    }
+    return result;
+  }
+
+  /** {@inheritDoc} */
+  @Override
   public ServerState getDomainNewestCSNs(DN baseDN)
   {
     final ServerState result = new ServerState();
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
index fdade65..c8c2b18 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java
@@ -450,12 +450,16 @@
       final String cookie2 = lastCookie + "o=test6:";
       debugInfo(test, "Search with bad domain in cookie=" + cookie);
       searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*,o=test)", cookie2, 0, UNWILLING_TO_PERFORM, test);
+      // the last cookie value may not match due to order of domain dn which is not guaranteed, so do not test it
+      String expectedError = ERR_RESYNC_REQUIRED_UNKNOWN_DOMAIN_IN_PROVIDED_COOKIE.get("[o=test6]", "")
+          .toString().replaceAll("<>", "");
+      assertThat(searchOp.getErrorMessage().toString()).startsWith(expectedError);
 
       // test missing domain in provided cookie
       final String cookie3 = lastCookie.substring(lastCookie.indexOf(';')+1);
       debugInfo(test, "Search with bad domain in cookie=" + cookie);
       searchOp = searchChangelogUsingCookie("(targetDN=*" + test + "*,o=test)", cookie3, 0, UNWILLING_TO_PERFORM, test);
-      String expectedError = ERR_RESYNC_REQUIRED_MISSING_DOMAIN_IN_PROVIDED_COOKIE
+      expectedError = ERR_RESYNC_REQUIRED_MISSING_DOMAIN_IN_PROVIDED_COOKIE
           .get("o=test:;","<"+ cookie3 + "o=test:;>").toString();
       assertThat(searchOp.getErrorMessage().toString()).isEqualToIgnoringCase(expectedError);
     }

--
Gitblit v1.10.0