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

Nicolas Capponi
04.01.2014 74ae8f26373f050f18bc0f0f4b3f0706a1f3f5be
Checkpoint commit for OPENDJ-1206 : Create a new ReplicationBackend/ChangelogBackend 
to support cn=changelog
CR-4439

Implementation of an additional validation of provided cookie when searching changelog
in cookie mode.

Check for each state of the provided cookie that the csn is not older than the oldest CSN
available in each domain. If this is not the case, then an error
ERR_RESYNC_REQUIRED_TOO_OLD_DOMAIN_IN_PROVIDED_COOKIE is returned.

This check was done previously in the ECLServerHandler class but was not ported when
writing ChangelogBackend. It also means I'm re-adding some methods deleted by the
previous big clean done by Jean-Noel.

Changes:
* ReplicationServer.java :
- add the check to validateServerState() method
- refactor validateServerState() method for better readability
* ReplicationServerDomain.java :
- re-add getOldestState() method
* ReplicationDomainDB. java :
- re-add getDomainOldestCSNs(DN) method
* FileChangelogDB.java, JEChangelogDB.java :
- re-add implementation of getDomainOldestCSNs(DN) method
7 files modified
178 ■■■■ changed files
opends/src/server/org/opends/server/backends/ChangelogBackend.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationServer.java 123 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationServerDomain.java 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/changelog/api/ReplicationDomainDB.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/changelog/file/FileChangelogDB.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/changelog/je/JEChangelogDB.java 12 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/ChangelogBackendTestCase.java 6 ●●●● patch | view | raw | blame | history
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());
    }
  }
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
    {
      final StringBuilder missingDomains = new StringBuilder();
      for (DN dn : domainsCopy)
    if (!activeDomains.containsAll(cookieDomains))
      {
        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.
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)
  {
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.
   *
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();
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();
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);
    }