@Test
  public void testDefaultJoinGroupHelper() throws UnknownMasterException, DatabaseException {

    for (int i = 0; i < repEnvInfo.length; i++) {
      RepEnvInfo ri = repEnvInfo[i];
      if ((i + 1) == repEnvInfo.length) {
        /* Use a non-master helper for the last replicator. */
        ReplicationConfig config = RepTestUtils.createRepConfig((short) (i + 1));
        String hpPairs = "";
        // Skip the master, use all the other nodes
        for (int j = 1; j < i; j++) {
          hpPairs += "," + repEnvInfo[j].getRepConfig().getNodeHostPort();
        }
        hpPairs = hpPairs.substring(1);
        config.setHelperHosts(hpPairs);
        File envHome = ri.getEnvHome();
        ri =
            repEnvInfo[i] =
                new RepEnvInfo(
                    envHome, config, RepTestUtils.createEnvConfig(Durability.COMMIT_SYNC));
      }
      ri.openEnv();
      State state = ri.getEnv().getState();
      assertEquals((i == 0) ? State.MASTER : State.REPLICA, state);
    }
  }
  /*
   * Tests internal node removal APIs.
   */
  @Test
  public void testRemoveMember() {
    createGroup(groupSize);
    ReplicatedEnvironment master = repEnvInfo[0].getEnv();
    assertTrue(master.getState().isMaster());

    RepNode masterRep = repEnvInfo[0].getRepNode();

    /* Reduce the group size all the way down to one. */
    for (int i = 1; i < groupSize; i++) {
      assertTrue(!RepInternal.isClosed(repEnvInfo[i].getEnv()));
      masterRep.removeMember(repEnvInfo[i].getEnv().getNodeName());
      assertEquals((groupSize - i), masterRep.getGroup().getElectableGroupSize());
    }

    /* Close the replica handles*/
    for (int i = groupSize - 1; i > 0; i--) {
      repEnvInfo[i].closeEnv();
    }

    /* Attempting to re-open them with the same node names should fail. */
    for (int i = 1; i < groupSize; i++) {
      try {
        repEnvInfo[i].openEnv();
        fail("Exception expected");
      } catch (EnvironmentFailureException e) {
        /* Expected, the master should reject the attempt. */
        assertEquals(EnvironmentFailureReason.HANDSHAKE_ERROR, e.getReason());
      }
    }

    /* Doing the same but with different node names should be ok. */
    for (int i = 1; i < groupSize; i++) {
      final RepEnvInfo ri = repEnvInfo[i];
      final ReplicationConfig repConfig = ri.getRepConfig();
      TestUtils.removeLogFiles("RemoveRepEnvironments", ri.getEnvHome(), false);

      repConfig.setNodeName("ReplaceNode_" + i);
      ri.openEnv();
      assertEquals(i + 1, masterRep.getGroup().getElectableGroupSize());
    }
    master.close();
  }
  /**
   * This is really multiple tests in one. It tests network restore with a replica in each of the
   * following three states:
   *
   * <p>1) A brand new node joining the group and needing a network restore.
   *
   * <p>2) An existing node with its own unique log needing a network restore.
   *
   * <p>3) Repeated network restores, reflecting a mature node.
   */
  @Test
  public void testBasic() throws DatabaseException, Exception {

    /*
     * The cleaner thread can see InsufficientLogExceptions so just stifle
     * those exceptions from stderr.
     */
    DaemonThread.stifleExceptionChatter = true;

    configureForMaxCleaning(2);

    final RepEnvInfo info1 = repEnvInfo[0];
    RepEnvInfo info2 = repEnvInfo[1];

    ReplicatedEnvironment masterRep = info1.openEnv();
    Environment menv = masterRep;
    EnvironmentMutableConfig mconfig = menv.getMutableConfig();
    mconfig.setConfigParam(EnvironmentParams.ENV_RUN_CLEANER.getName(), "false");
    menv.setMutableConfig(mconfig);

    /*
     * Have just the master join first. We do this to test the special case
     * of a brand new node joining a group and needing VLSN 1. The same
     * node then rejoins with its VLSN > 1 to test subsequent rejoins
     * where the node has already participated in the replication.
     */
    populateDB(masterRep, TEST_DB_NAME, 100);

    mconfig = menv.getMutableConfig();
    mconfig.setConfigParam(EnvironmentParams.ENV_RUN_CLEANER.getName(), "true");
    menv.setMutableConfig(mconfig);

    File cenvDir = info2.getEnvHome();
    final int cid = 2;

    for (int i = 0; i < RESTORE_CYCLES; i++) {

      leaveGroupAllButMaster();
      shiftVLSNRight(masterRep);
      RepNodeImpl memberPrev =
          info1.getRepNode().getGroup().getMember(info2.getRepConfig().getNodeName());
      /* Node1 is not known on the first iteration. */
      final VLSN prevSync = (i == 0) ? null : memberPrev.getBarrierState().getLastCBVLSN();
      try {
        /* Should force a network restore. */
        setExceptionListener(info2);
        info2.openEnv();
        fail("exception expected");
      } catch (InsufficientLogException e) {
        RepNodeImpl member =
            info1.getRepNode().getGroup().getMember(info2.getRepConfig().getNodeName());

        /*
         * The sync state should have been advanced to help contribute
         * to the global CBVLSN and prevent it from advancing.
         */
        final VLSN currSync = member.getBarrierState().getLastCBVLSN();
        assertTrue((i == 0) || currSync.compareTo(prevSync) >= 0);

        NetworkRestore networkRestore = new NetworkRestore();
        networkRestore.execute(e, new NetworkRestoreConfig());
        final NetworkBackupStats stats = networkRestore.getNetworkBackupStats();
        assertThat(stats.getExpectedBytes(), greaterThan(0));
        assertThat(stats.getTransferredBytes(), greaterThan(0));
        /* Create a replacement replicator. */
        info2 = RepTestUtils.setupEnvInfo(cenvDir, RepTestUtils.DEFAULT_DURABILITY, cid, info1);
        setExceptionListener(info2);
        info2.openEnv();
      }
      /* Verify that we can continue with the "restored" log files. */
      populateDB(masterRep, TEST_DB_NAME, 100, 100);
      VLSN commitVLSN = RepTestUtils.syncGroupToLastCommit(repEnvInfo, 2);
      RepTestUtils.checkNodeEquality(commitVLSN, false, repEnvInfo);
      info2.closeEnv();
    }
  }