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

Valery Kharseko
12 hours ago 92d88ca699cd8090a26b92cbe46789d2b848195f
opendj-server-legacy/src/test/java/org/opends/server/replication/ReplicationTestCase.java
@@ -102,6 +102,40 @@
  /** Generation id for a fully empty domain. */
  public static final long EMPTY_DN_GENID = GenerationIdChecksum.EMPTY_BACKEND_GENERATION_ID;
  /** How many times {@link #assertMonitorAttrValueStays} reads a value by default. */
  private static final int MONITOR_ATTR_SAMPLES = 5;
  /** How long {@link #assertMonitorAttrValueStays} waits between two reads. */
  private static final long MONITOR_ATTR_SAMPLE_INTERVAL_IN_MS = 200;
  /**
   * How long {@link #assertMonitorAttrValueStays} waits for the monitor entry of a domain
   * to be registered again before it gives up on reading it: longer than the
   * {@code MAX_REPLAY_RETRY_DELAY_IN_MS} a session restart holds it down for.
   */
  private static final long MONITOR_ATTR_SAMPLE_GRACE_IN_MS = 30000;
  /**
   * How much longer than the samples it asks for {@link #assertMonitorAttrValueStays}
   * runs before it gives up: the samples of a domain which keeps restarting its session
   * are taken a restart apart, and waiting for all of them would outlast the fork.
   */
  private static final long MONITOR_ATTR_SAMPLES_DEADLINE_IN_MS = 60000;
  /**
   * How many samples it takes for {@link #assertMonitorAttrValueStays} to outlast the
   * session restart which brings a change back, so that a counter only that delivery
   * could bump a second time is watched while it arrives.
   * <p>
   * It covers a domain which restarts its session for the first time, which waits
   * {@code LDAPReplicationDomain.REPLAY_RETRY_DELAY_IN_MS} before reconnecting. The wait
   * of a domain which has been restarting its session in a row is longer - it climbs to
   * {@code MAX_REPLAY_RETRY_DELAY_IN_MS} - so a redelivery is outside this window there;
   * sampling for ten seconds at every call site to cover it would cost more than the
   * assertions are worth.
   */
  protected static final int MONITOR_ATTR_SAMPLES_ACROSS_A_REDELIVERY = 12;
  /** The internal connection used for operation. */
  protected InternalClientConnection connection;
@@ -484,17 +518,170 @@
      @Override
      public Long call() throws Exception
      {
        String monitorFilter = "(&(cn=Directory server*)(domain-name=" + baseDN + "))";
        InternalSearchOperation op =
            connection.processSearch(newSearchRequest("cn=replication,cn=monitor", WHOLE_SUBTREE, monitorFilter));
        Assertions.assertThat(op.getSearchEntries()).as("Could not read monitoring information").isNotEmpty();
        SearchResultEntry entry = op.getSearchEntries().getFirst();
        return entry.parseAttribute(attr).asLong();
        Long value = readMonitorAttrValue(baseDN, attr);
        Assertions.assertThat(value)
            .as("the monitor entry of %s is not registered", baseDN).isNotNull();
        return value;
      }
    });
  }
  /**
   * Reads a monitor attribute of a replication domain, once.
   *
   * @param baseDN the base DN of the domain whose monitor entry to read
   * @param attr the monitor attribute to read
   * @return the value of the attribute, or {@code null} when the monitor entry of the
   *         domain is not registered - which it is not for as long as its session to the
   *         replication server is down
   * @throws Exception if the monitor could not be searched, or if the entry is there and
   *                   does not publish the attribute, which is a wrong name rather than
   *                   something to wait for
   */
  private Long readMonitorAttrValue(final DN baseDN, final String attr) throws Exception
  {
    String monitorFilter = "(&(cn=Directory server*)(domain-name=" + baseDN + "))";
    InternalSearchOperation op =
        connection.processSearch(newSearchRequest("cn=replication,cn=monitor", WHOLE_SUBTREE, monitorFilter));
    if (op.getSearchEntries().isEmpty())
    {
      return null;
    }
    SearchResultEntry entry = op.getSearchEntries().getFirst();
    Long value = entry.parseAttribute(attr).asLong();
    Assertions.assertThat(value)
        .as("the monitor entry of %s does not publish %s", baseDN, attr).isNotNull();
    return value;
  }
  /**
   * Waits for a monitor attribute of a replication domain to reach the expected value.
   * <p>
   * The monitor entry of a domain is deregistered for as long as its session to the
   * replication server is down, which is what a replay failure does to it, and a counter
   * is bumped a moment after the change or the delivery it counts was dealt with:
   * reading the value once would be a race on both counts.
   * <p>
   * The read is deliberately {@link #readMonitorAttrValue(DN, String)} rather than the
   * retrying {@link #getMonitorAttrValue(DN, String)}: a {@link TestTimer} budget is a
   * number of steps rather than a deadline, so one timer waiting on another multiplies
   * them - 150 steps around a read which sleeps ten seconds of its own is 25 minutes,
   * long past the {@code org.opends.test.timeout} the fork is killed on. One timer owns
   * the deadline here, and a monitor entry which is not registered is one failed poll -
   * so the deadline has to be wide enough for a domain which is restarting its session to
   * register it again, which takes the backoff of that restart.
   *
   * @param baseDN the base DN of the domain whose monitor entry to read
   * @param attributeName the monitor attribute to read
   * @param expected the value it must reach
   * @param message what is being asserted
   * @throws Exception if the value was not reached in time
   */
  protected void assertMonitorAttrValueEventually(
      final DN baseDN, final String attributeName, final long expected, final String message)
      throws Exception
  {
    TestTimer timer = new TestTimer.Builder()
      .maxSleep(60, SECONDS)
      .sleepTimes(200, MILLISECONDS)
      .toTimer();
    timer.repeatUntilSuccess(new CallableVoid()
    {
      @Override
      public void call() throws Exception
      {
        assertEquals(readMonitorAttrValue(baseDN, attributeName), (Long) expected, message);
      }
    });
  }
  /**
   * Checks that a monitor attribute of a replication domain holds the expected value and
   * keeps holding it, over {@link #MONITOR_ATTR_SAMPLES} samples.
   *
   * @param baseDN the base DN of the domain whose monitor entry to read
   * @param attributeName the monitor attribute to read
   * @param expected the value it must hold
   * @param message what is being asserted
   * @throws Exception if the value changes, or if the monitor entry can not be read
   */
  protected void assertMonitorAttrValueStays(
      final DN baseDN, final String attributeName, final long expected, final String message)
      throws Exception
  {
    assertMonitorAttrValueStays(baseDN, attributeName, expected, MONITOR_ATTR_SAMPLES, message);
  }
  /**
   * Checks that a monitor attribute of a replication domain holds the expected value and
   * keeps holding it, over the provided number of samples.
   * <p>
   * Waiting for a value to be reached is not enough to tell that something happened only
   * once: a counter which is bumped a second time goes through the expected value on its
   * way, and the first poll which sees it passes.
   * <p>
   * The samples have to outlast whatever could bump the counter a second time, or the
   * assertion only reads like it is watching for it. The default is enough for a second
   * attempt of the same delivery, which is fifty milliseconds away; a counter which a
   * change delivered again could bump has to be watched for longer than the session
   * restart which brings that delivery, so those call sites pass
   * {@link #MONITOR_ATTR_SAMPLES_ACROSS_A_REDELIVERY}.
   * <p>
   * The monitor entry of a domain is gone for as long as its session is down, which a
   * session restart in the middle of the window does: a read which comes back with
   * nothing is not a sample rather than a failure, and the samples asked for are taken
   * once it is back. So a restart stretches the window rather than shortening it, which
   * is the right way round for what is being asserted, and the entry staying away for
   * {@link #MONITOR_ATTR_SAMPLE_GRACE_IN_MS} is what fails the assertion. The samples are
   * bounded all the same: a domain which restarts its session over and over would
   * otherwise have this wait for one readable moment per restart until the fork is killed
   * for taking too long, which says nothing about the value being watched.
   *
   * @param baseDN the base DN of the domain whose monitor entry to read
   * @param attributeName the monitor attribute to read
   * @param expected the value it must hold
   * @param samples how many times to read the value, at least
   *                {@link #MONITOR_ATTR_SAMPLE_INTERVAL_IN_MS} apart
   * @param message what is being asserted
   * @throws Exception if the value changes, or if the monitor entry can not be read
   */
  protected void assertMonitorAttrValueStays(final DN baseDN, final String attributeName,
      final long expected, final int samples, final String message) throws Exception
  {
    final long now = System.currentTimeMillis();
    final long deadline = now + samples * MONITOR_ATTR_SAMPLE_INTERVAL_IN_MS
        + MONITOR_ATTR_SAMPLES_DEADLINE_IN_MS;
    long readableBy = now + MONITOR_ATTR_SAMPLE_GRACE_IN_MS;
    int taken = 0;
    while (taken < samples)
    {
      final Long value = readMonitorAttrValue(baseDN, attributeName);
      if (value != null)
      {
        assertEquals(value, (Long) expected, message);
        taken++;
        if (taken == samples)
        {
          // Every sample which was asked for held the value: how long they took to take
          // is not what this is asserting.
          return;
        }
        readableBy = System.currentTimeMillis() + MONITOR_ATTR_SAMPLE_GRACE_IN_MS;
      }
      else if (System.currentTimeMillis() > readableBy)
      {
        fail("the monitor entry of " + baseDN + " was not registered again in "
            + MONITOR_ATTR_SAMPLE_GRACE_IN_MS + "ms: " + message);
      }
      if (System.currentTimeMillis() > deadline)
      {
        fail("only " + taken + " of " + samples + " samples of " + attributeName
            + " could be read before the deadline: " + message);
      }
      Thread.sleep(MONITOR_ATTR_SAMPLE_INTERVAL_IN_MS);
    }
  }
  protected void checkEntryHasAttributeValue(final DN dn, final String attrTypeStr, final String valueString,
      int timeoutInSecs, String notFoundErrorMsg) throws Exception
  {