@Test
  public void testRemoveMemberExceptions() {
    createGroup(2);
    ReplicatedEnvironment master = repEnvInfo[0].getEnv();
    assertTrue(master.getState().isMaster());

    RepNode masterRep = repEnvInfo[0].getRepNode();
    try {
      masterRep.removeMember(master.getNodeName());
      fail("Exception expected.");
    } catch (MasterStateException e) {
      // Expected
    }

    try {
      masterRep.removeMember("unknown node foobar");
      fail("Exception expected.");
    } catch (MemberNotFoundException e) {
      // Expected
    }

    masterRep.removeMember(repEnvInfo[1].getRepNode().getNodeName());
    try {
      masterRep.removeMember(repEnvInfo[1].getRepNode().getNodeName());
      fail("Exception expected.");
    } catch (MemberNotFoundException e) {
      // Expected
    }
    repEnvInfo[1].closeEnv();
  }
    @Override
    protected void preLogCommitHook() {
      super.preLogCommitHook();
      RepNode rmMasterNode = repEnvInfo[0].getRepNode();
      int size = rmMasterNode.getGroup().getAllElectableMembers().size();
      int delNodes = ((size & 1) == 1) ? 2 : 1;
      int closeNodeIndex = (size - delNodes) - 1;

      /*
       * The loop below simulates the concurrent removal of a node while
       * a transaction is in progress. It deletes a sufficient number of
       * nodes so as to get a lower simple nodes to get to a new lower
       * simple majority.
       */
      for (int i = repEnvInfo.length - 1; delNodes-- > 0; i--) {
        repEnvInfo[i].closeEnv();
        rmMasterNode.removeMember(repEnvInfo[i].getRepConfig().getNodeName(), delete);
      }

      /*
       * Shut down an additional undeleted Replica to provoke a
       * lack of acks based on the old simple majority.
       */
      repEnvInfo[closeNodeIndex].closeEnv();
    }
  /**
   * Bounce the master, causing replica1 to switch roles with the master. If a higher appVersion is
   * specified, the bounced node will also be upgraded.
   */
  private void bounceMaster(final int appVersion) throws Exception {

    /* Disable updates to RepGroupDB due to LocalCBVLSN updates. */
    LocalCBVLSNUpdater.setSuppressGroupDBUpdates(true);

    for (RepEnvInfo info : repEnvInfo) {
      if (info.getEnv() == masterEnv) {

        /*
         * Sync up the replication group so that node2 doesn't do
         * hard recovery.
         */
        RepTestUtils.syncGroupToLastCommit(repEnvInfo, repEnvInfo.length);
        /* Disable replay on replicas. */
        shutdownFeeder(info.getRepNode(), replicaEnv1);
        shutdownFeeder(info.getRepNode(), replicaEnv2);

        /* Close the master. */
        masterApp.close();
        masterApp = null;
        info.closeEnv();
        masterEnv = null;

        /* Force repEnvInfo[2] to the master. */
        WaitForMasterListener masterWaiter = new WaitForMasterListener();
        replicaEnv2.setStateChangeListener(masterWaiter);
        RepNode repNode = repEnvInfo[2].getRepNode();
        repNode.forceMaster(true);
        /* Enable the LocalCBVLSN updates. */
        LocalCBVLSNUpdater.setSuppressGroupDBUpdates(false);
        masterWaiter.awaitMastership();
        assertTrue(repNode.isMaster());
        masterEnv = replicaEnv2;

        /* Replica2 was elected, swap names with replica1. */
        final ReplicatedEnvironment tmpEnv = replicaEnv1;
        replicaEnv1 = replicaEnv2;
        replicaEnv2 = tmpEnv;
        final AppInterface tmpApp = replicaApp1;
        replicaApp1 = replicaApp2;
        replicaApp2 = tmpApp;

        /* Replica1 (or 2, see above) has been elected master. */
        masterApp = newAppObject(appVersion);
        masterApp.adopt(replicaApp1);
        /* Former master (just upgraded) becomes replica1. */
        replicaEnv1 = info.openEnv();
        replicaApp1.open(replicaEnv1);
        break;
      }
    }
    assertNotNull(masterApp);
    assertSame(masterEnv.getState(), ReplicatedEnvironment.State.MASTER);
  }
  /* Start the master (the helper node) first */
  @Test
  public void testGroupCreateMasterFirst() throws DatabaseException {

    for (int i = 0; i < repEnvInfo.length; i++) {
      ReplicatedEnvironment rep = repEnvInfo[i].openEnv();
      State state = rep.getState();
      assertEquals((i == 0) ? State.MASTER : State.REPLICA, state);
      RepNode repNode = RepInternal.getRepImpl(rep).getRepNode();
      /* No elections, helper nodes or members queried for master. */
      assertEquals(0, repNode.getElections().getElectionCount());
    }
  }
  /*
   * Test the API: RepNode.shutdownNetworkBackup/restartNetworkBackup service
   * used to disable the service around a replica syncup operation.
   */
  @Test
  public void testLockout() throws IOException {

    setExceptionListener(repEnvInfo[0]);
    repEnvInfo[0].openEnv();
    RepNode repNode = repEnvInfo[0].getRepNode();
    leaveGroupAllButMaster();

    repNode.shutdownNetworkBackup();
    File backupDir = new File(repEnvInfo[1].getEnvHome().getCanonicalPath() + ".backup");
    backupDir.mkdir();
    assertTrue(backupDir.exists());

    DataChannelFactory channelFactory =
        DataChannelFactoryBuilder.construct(RepTestUtils.readRepNetConfig());
    EnvironmentImpl envImpl = createEnvImpl(backupDir);
    try {
      NetworkBackup backup =
          new NetworkBackup(
              repNode.getSocket(),
              backupDir,
              new NameIdPair("n1", (short) 1),
              true,
              envImpl.getFileManager(),
              channelFactory);
      backup.execute();
      fail("expected exception service should not have been available");
    } catch (ServiceConnectFailedException e) {
      /* Expected. */
    } catch (Exception e) {
      fail("unexpected exception" + e);
    }

    repNode.restartNetworkBackup();
    try {
      NetworkBackup backup =
          new NetworkBackup(
              repNode.getSocket(),
              backupDir,
              new NameIdPair("n1", (short) 1),
              true,
              envImpl.getFileManager(),
              channelFactory);
      backup.execute();
    } catch (Exception e) {
      fail("unexpected exception:" + e);
    }

    envImpl.abnormalClose();
  }
  /*
   * 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();
  }
  /**
   * Verify that a NetworkBackup that's in progress is aborted by repNode.shutdownNetworkRestore()
   * and therefore during a rollback operation.
   */
  @Test
  public void testNBAbortOnSyncup()
      throws IOException, DatabaseException, ServiceConnectFailedException,
          LoadThresholdExceededException, InsufficientVLSNRangeException {

    setExceptionListener(repEnvInfo[0]);
    repEnvInfo[0].openEnv();
    final RepNode repNode = repEnvInfo[0].getRepNode();
    leaveGroupAllButMaster();
    File backupDir = new File(repEnvInfo[1].getEnvHome().getCanonicalPath() + ".backup");
    backupDir.mkdir();
    DataChannelFactory channelFactory =
        DataChannelFactoryBuilder.construct(RepTestUtils.readRepNetConfig());
    EnvironmentImpl envImpl = createEnvImpl(backupDir);
    NetworkBackup backup =
        new NetworkBackup(
            repNode.getSocket(),
            backupDir,
            new NameIdPair("n1", (short) 1),
            true,
            envImpl.getFileManager(),
            channelFactory);
    CyclicBarrier testBarrier =
        new CyclicBarrier(
            1,
            new Runnable() {
              public void run() {
                /* The syncup should kill the NB */
                repNode.shutdownNetworkBackup();
              }
            });
    backup.setTestBarrier(testBarrier);
    try {
      backup.execute();
      fail("Expected exception");
    } catch (IOException e) {
      /* Expected exception as in progress service was terminated. */
    }

    envImpl.abnormalClose();
  }
  /*
   * Tests internal node deletion APIs.
   */
  @Test
  public void testDeleteMember() {
    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()));
      final String delName = repEnvInfo[i].getEnv().getNodeName();
      repEnvInfo[i].closeEnv();
      masterRep.removeMember(delName, true);
      assertEquals((groupSize - i), masterRep.getGroup().getElectableGroupSize());
    }

    /*
     * Attempting to re-open them with the same node names should succeed
     */
    for (int i = 1; i < groupSize; i++) {
      repEnvInfo[i].openEnv();
    }
  }
  /*
   * Remove a feeder from the master so that no entries can be replayed on
   * this node.
   */
  private void shutdownFeeder(RepNode masterNode, ReplicatedEnvironment repEnv) throws Exception {

    String nodeName = repEnv.getRepConfig().getNodeName();
    RepNodeImpl removeNode = masterNode.getGroup().getNode(nodeName);
    masterNode.feederManager().shutdownFeeder(removeNode);
  }