/* * Provoke sufficient log cleaning to move the entire VLSN right * sufficiently that the new VLSN range no longer overlaps the VLSN * range upon entry thus guaranteeing a LogFileRefreshException. */ private void shiftVLSNRight(ReplicatedEnvironment menv) { /* Shift the vlsn range window. */ RepImpl menvImpl = repEnvInfo[0].getRepImpl(); final VLSNIndex vlsnIndex = menvImpl.getRepNode().getVLSNIndex(); VLSN masterHigh = vlsnIndex.getRange().getLast(); CheckpointConfig checkpointConfig = new CheckpointConfig(); checkpointConfig.setForce(true); do { /* * Populate just the master, leaving the replica behind Re-populate * with the same keys to create Cleaner fodder. */ populateDB(menv, TEST_DB_NAME, 100); /* * Sleep to permit the cbvlsn on the master to be updated. It's * done with the period: FeederManager.MASTER_CHANGE_CHECK_TIMEOUT */ try { Thread.sleep(1000); } catch (InterruptedException e) { fail("unexpected interrupt"); } menv.cleanLog(); menv.checkpoint(checkpointConfig); } while (masterHigh.compareTo(vlsnIndex.getRange().getFirst()) > 0); }
@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(); }
/** * 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()); } }
/** Crash the current master, and wait until the group elects a new one. */ private ReplicatedEnvironment crashMasterAndElectNewMaster( ReplicatedEnvironment master, RepEnvInfo[] repEnvInfo) { int masterIndex = RepInternal.getNodeId(master) - 1; logger.info("Crashing " + master.getNodeName()); repEnvInfo[masterIndex].abnormalCloseEnv(); logger.info("Rejoining"); ReplicatedEnvironment newMaster = RepTestUtils.openRepEnvsJoin(repEnvInfo); logger.info("New master = " + newMaster.getNodeName()); return newMaster; }
private void testMemberRemoveAckInteraction(final boolean delete) { createGroup(groupSize); Transaction txn; Database db; try { MasterTxn.setFactory(new TxnFactory(delete)); ReplicatedEnvironment master = repEnvInfo[0].getEnv(); txn = master.beginTransaction(null, null); /* Write to the environment. */ db = master.openDatabase(txn, "random", dbconfig); db.close(); txn.commit(); } catch (InsufficientAcksException e) { fail("No exception expected."); } finally { MasterTxn.setFactory(null); } }
/* * 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(); }
@Test public void testNoQuorum() throws DatabaseException, InterruptedException { for (int i = 0; i < 3; i++) { ReplicatedEnvironment rep = repEnvInfo[i].openEnv(); State state = rep.getState(); assertEquals((i == 0) ? State.MASTER : State.REPLICA, state); } RepTestUtils.syncGroupToLastCommit(repEnvInfo, 3); repEnvInfo[1].closeEnv(); repEnvInfo[2].closeEnv(); // A new node joining in the absence of a quorum must fail try { repEnvInfo[3].openEnv(); fail("Expected exception"); } catch (UnknownMasterException e) { /* Expected. */ } }
/* * 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(); } }
/** * Bounce replica2. No election will take place. If a higher appVersion is specified, the bounced * node will also be upgraded. */ private void bounceReplica2(final int appVersion) throws Exception { replicaApp2.close(); replicaApp2 = null; for (RepEnvInfo info : repEnvInfo) { if (info.getEnv() == replicaEnv2) { info.closeEnv(); replicaEnv2 = info.openEnv(); replicaApp2 = newAppObject(appVersion); replicaApp2.open(replicaEnv2); break; } } assertNotNull(replicaApp2); assertSame(masterEnv.getState(), ReplicatedEnvironment.State.MASTER); }
/* * 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); }
private void experienceLogFlushTask(String sleepTime, boolean flushBeforeCrash) throws Throwable { try { createRepEnvInfo(sleepTime); ReplicatedEnvironment master = RepTestUtils.joinGroup(repEnvInfo); long startTime = System.currentTimeMillis(); StatsConfig stConfig = new StatsConfig(); stConfig.setClear(true); /* Flush the existed dirty data before we do writes. */ for (int i = 0; i < repEnvInfo.length; i++) { repEnvInfo[i].getEnv().sync(); repEnvInfo[i].getEnv().getStats(stConfig); } DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setAllowCreate(true); dbConfig.setTransactional(true); Database db = master.openDatabase(null, dbName, dbConfig); DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); for (int i = 1; i <= 100; i++) { IntegerBinding.intToEntry(i, key); StringBinding.stringToEntry(value, data); db.put(null, key, data); } assertTrue(System.currentTimeMillis() - startTime < 15000); Thread.sleep(15000); long endTime = System.currentTimeMillis(); for (int i = 0; i < repEnvInfo.length; i++) { EnvironmentStats envStats = repEnvInfo[i].getEnv().getStats(stConfig); LogFlusher flusher = repEnvInfo[i].getRepNode().getLogFlusher(); if (flushBeforeCrash) { /* Make sure the LogFlushTask has been invoked. */ assertTrue(flusher.getFlushTask().scheduledExecutionTime() > startTime); assertTrue(flusher.getFlushTask().scheduledExecutionTime() < endTime); /* * Since the log file size is not so big, we can't assure * all the data will be written in the same log file, but * we can sure that a flush does happen. */ assertTrue(envStats.getNSequentialWrites() >= 1); assertTrue(envStats.getNLogFSyncs() == 1); } else { /* * Make sure the LogFlushTask is not invoked after making * the changes. */ assertTrue(flusher.getFlushTask().scheduledExecutionTime() < startTime); assertTrue(envStats.getNSequentialWrites() == 0); assertTrue(envStats.getNLogFSyncs() == 0); } assertTrue(envStats.getNFSyncs() == 0); } File[] envHomes = new File[3]; /* Close the replicas without doing a checkpoint. */ for (int i = 0; i < repEnvInfo.length; i++) { envHomes[i] = repEnvInfo[i].getEnvHome(); repEnvInfo[i].getRepImpl().abnormalClose(); } /* * Open a read only standalone Environment on the replicas to see * whether the data has been synced to the disk. */ EnvironmentConfig newConfig = new EnvironmentConfig(); newConfig.setAllowCreate(false); newConfig.setReadOnly(true); newConfig.setTransactional(true); for (int i = 0; i < repEnvInfo.length; i++) { Environment env = new Environment(envHomes[i], newConfig); dbConfig.setAllowCreate(false); dbConfig.setReadOnly(true); try { db = env.openDatabase(null, dbName, dbConfig); } catch (DatabaseNotFoundException e) { /* * If the system crashes before the flush, the database is * not synced to the disk, so this database can't be found * at all, it's expected. */ assertFalse(flushBeforeCrash); } if (flushBeforeCrash) { assertTrue(db.count() == 100); for (int index = 1; index <= 100; index++) { IntegerBinding.intToEntry(index, key); OperationStatus status = db.get(null, key, data, null); if (flushBeforeCrash) { assertTrue(status == OperationStatus.SUCCESS); assertEquals(value, StringBinding.entryToString(data)); } } } if (flushBeforeCrash) { db.close(); } env.close(); } } catch (Throwable t) { t.printStackTrace(); throw t; } }
/** * Test that a timeout in the feeder while attempting to read the group database because other * feeders have it write locked causes the feeder (and replica) to fail, but allows the master to * continue operating. [#23822] */ @Test public void testJoinGroupReadGroupTimeout() throws DatabaseException, InterruptedException { /* Start first node as master */ ReplicatedEnvironment repEnv = repEnvInfo[0].openEnv(); assertEquals("Master node state", State.MASTER, repEnv.getState()); RepImpl repImpl = RepInternal.getRepImpl(repEnv); for (int i = 1; i <= 2; i++) { /* Get a write lock on the RepGroupDB */ final MasterTxn txn = new MasterTxn( repImpl, new TransactionConfig() .setDurability( new Durability( SyncPolicy.SYNC, SyncPolicy.SYNC, ReplicaAckPolicy.SIMPLE_MAJORITY)), repImpl.getNameIdPair()); final DatabaseImpl groupDbImpl = repImpl.getGroupDb(); final DatabaseEntry value = new DatabaseEntry(); final Cursor cursor = DbInternal.makeCursor(groupDbImpl, txn, new CursorConfig()); final OperationStatus status = cursor.getNext(RepGroupDB.groupKeyEntry, value, LockMode.RMW); assertEquals(i + ": Lock group result", OperationStatus.SUCCESS, status); /* Wait longer than the default 500 ms read timeout */ Thread.sleep(600); /* Test both electable and secondary nodes */ if (i == 2) { repEnvInfo[i].getRepConfig().setNodeType(NodeType.SECONDARY); } /* Create a thread that attempts to join another environment */ RepNodeThread repNodeThread = new RepNodeThread(i, i != 1); repNodeThread.start(); /* Wait for attempt to complete */ repNodeThread.join(30000); assertEquals("RN thread alive", false, repNodeThread.isAlive()); if (i == 1) { /* Join attempt should fail for primary */ assertNotNull("Expected RN thread exception", repNodeThread.te); /* Release write lock on RepGroupDB */ cursor.close(); txn.abort(); /* Second join attempt should succeed */ repNodeThread = new RepNodeThread(1); repNodeThread.start(); repNodeThread.join(30000); assertEquals("RN thread alive", false, repNodeThread.isAlive()); assertEquals("RN thread exception", null, repNodeThread.te); } else { /* Join attempt should succeed for secondary */ assertEquals("RN thread exception", null, repNodeThread.te); /* Release write lock on RepGroupDB */ cursor.close(); txn.abort(); } } }