blob: a1592ac660d1aeebd8dec59014a0f013f2c87f37 [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.bookkeeper.client;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallback;
import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tests the functionality of LedgerChecker. This Ledger checker should be able
* to detect the correct underReplicated fragment
*/
public class TestLedgerChecker extends BookKeeperClusterTestCase {
private static final byte[] TEST_LEDGER_ENTRY_DATA = "TestCheckerData"
.getBytes();
private static final byte[] TEST_LEDGER_PASSWORD = "testpasswd".getBytes();
static Logger LOG = LoggerFactory.getLogger(TestLedgerChecker.class);
public TestLedgerChecker() {
super(3);
}
class CheckerCallback implements GenericCallback<Set<LedgerFragment>> {
private Set<LedgerFragment> result = null;
private CountDownLatch latch = new CountDownLatch(1);
public void operationComplete(int rc, Set<LedgerFragment> result) {
this.result = result;
latch.countDown();
}
Set<LedgerFragment> waitAndGetResult() throws InterruptedException {
latch.await();
return result;
}
}
/**
* Tests that the LedgerChecker should detect the underReplicated fragments
* on multiple Bookie crashes
*/
@Test(timeout=60000)
public void testChecker() throws Exception {
LedgerHandle lh = bkc.createLedger(BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
startNewBookie();
for (int i = 0; i < 10; i++) {
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
}
InetSocketAddress replicaToKill = lh.getLedgerMetadata().getEnsembles()
.get(0L).get(0);
LOG.info("Killing {}", replicaToKill);
killBookie(replicaToKill);
for (int i = 0; i < 10; i++) {
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
}
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
for (LedgerFragment r : result) {
LOG.info("unreplicated fragment: {}", r);
}
assertEquals("Should have one missing fragment", 1, result.size());
assertEquals("Fragment should be missing from first replica", result
.iterator().next().getAddress(), replicaToKill);
InetSocketAddress replicaToKill2 = lh.getLedgerMetadata()
.getEnsembles().get(0L).get(1);
LOG.info("Killing {}", replicaToKill2);
killBookie(replicaToKill2);
result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
for (LedgerFragment r : result) {
LOG.info("unreplicated fragment: {}", r);
}
assertEquals("Should have three missing fragments", 3, result.size());
}
/**
* Tests that ledger checker should pick the fragment as bad only if any of
* the fragment entries not meeting the quorum.
*/
// /////////////////////////////////////////////////////
// /////////Ensemble = 3, Quorum = 2 ///////////////////
// /Sample Ledger meta data should look like////////////
// /0 a b c /////*entry present in a,b. Now kill c//////
// /1 a b d ////////////////////////////////////////////
// /Here even though one BK failed at this stage, //////
// /we don't have any missed entries. Quorum satisfied//
// /So, there should not be any missing replicas.///////
// /////////////////////////////////////////////////////
@Test(timeout = 3000)
public void testShouldNotGetTheFragmentIfThereIsNoMissedEntry()
throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
// Entry should have added in first 2 Bookies.
// Kill the 3rd BK from ensemble.
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
InetSocketAddress lastBookieFromEnsemble = firstEnsemble.get(2);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
startNewBookie();
LOG.info("Ensembles after first entry :"
+ lh.getLedgerMetadata().getEnsembles());
// Adding one more entry. Here enseble should be reformed.
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
LOG.info("Ensembles after second entry :"
+ lh.getLedgerMetadata().getEnsembles());
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
for (LedgerFragment r : result) {
LOG.info("unreplicated fragment: {}", r);
}
assertEquals("Should not have any missing fragment", 0, result.size());
}
/**
* Tests that LedgerChecker should give two fragments when 2 bookies failed
* in same ensemble when ensemble = 3, quorum = 2
*/
@Test(timeout = 3000)
public void testShouldGetTwoFrgamentsIfTwoBookiesFailedInSameEnsemble()
throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
startNewBookie();
startNewBookie();
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
InetSocketAddress firstBookieFromEnsemble = firstEnsemble.get(0);
killBookie(firstEnsemble, firstBookieFromEnsemble);
InetSocketAddress secondBookieFromEnsemble = firstEnsemble.get(1);
killBookie(firstEnsemble, secondBookieFromEnsemble);
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
for (LedgerFragment r : result) {
LOG.info("unreplicated fragment: {}", r);
}
assertEquals("There should be 2 fragments", 2, result.size());
}
/**
* Tests that LedgerChecker should not get any underReplicated fragments, if
* corresponding ledger does not exists.
*/
@Test(timeout = 3000)
public void testShouldNotGetAnyFragmentIfNoLedgerPresent()
throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
InetSocketAddress firstBookieFromEnsemble = firstEnsemble.get(0);
killBookie(firstBookieFromEnsemble);
startNewBookie();
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
bkc.deleteLedger(lh.getId());
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 0 fragments. But returned fragments are "
+ result, 0, result.size());
}
/**
* Tests that LedgerChecker should get failed ensemble number of fragments
* if ensemble bookie failures on next entry
*/
@Test(timeout = 3000)
public void testShouldGetFailedEnsembleNumberOfFgmntsIfEnsembleBookiesFailedOnNextWrite()
throws Exception {
startNewBookie();
startNewBookie();
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
for (int i = 0; i < 3; i++) {
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
}
// Kill all three bookies
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
for (InetSocketAddress bkAddr : firstEnsemble) {
killBookie(firstEnsemble, bkAddr);
}
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
for (LedgerFragment r : result) {
LOG.info("unreplicated fragment: {}", r);
}
assertEquals("There should be 3 fragments", 3, result.size());
}
/**
* Tests that LedgerChecker should not get any fragments as underReplicated
* if Ledger itself is empty
*/
@Test(timeout = 3000)
public void testShouldNotGetAnyFragmentWithEmptyLedger() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 0 fragments. But returned fragments are "
+ result, 0, result.size());
}
/**
* Tests that LedgerChecker should get all fragments if ledger is empty
* but all bookies in the ensemble are down.
* In this case, there's no way to tell whether data was written or not.
* In this case, there'll only be two fragments, as quorum is 2 and we only
* suspect that the first entry of the ledger could exist.
*/
@Test(timeout = 3000)
public void testShouldGet2FragmentsWithEmptyLedgerButBookiesDead() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
for (InetSocketAddress b : lh.getLedgerMetadata().getEnsembles().get(0L)) {
killBookie(b);
}
Set<LedgerFragment> result = getUnderReplicatedFragments(lh);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 2 fragments.", 2, result.size());
}
/**
* Tests that LedgerChecker should one fragment as underReplicated
* if there is an open ledger with single entry written.
*/
@Test(timeout = 3000)
public void testShouldGetOneFragmentWithSingleEntryOpenedLedger() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 3, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
InetSocketAddress lastBookieFromEnsemble = firstEnsemble.get(0);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
startNewBookie();
//Open ledger separately for Ledger checker.
LedgerHandle lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 1 fragment. But returned fragments are "
+ result, 1, result.size());
}
/**
* Tests that LedgerChecker correctly identifies missing fragments
* when a single entry is written after an ensemble change.
* This is important, as the last add confirmed may be less than the
* first entry id of the final segment.
*/
@Test(timeout = 3000)
public void testSingleEntryAfterEnsembleChange() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 3, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
for (int i = 0; i < 10; i++) {
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
}
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
InetSocketAddress lastBookieFromEnsemble = firstEnsemble.get(
lh.getDistributionSchedule().getWriteSet(lh.getLastAddPushed()).get(0));
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
startNewBookie();
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
lastBookieFromEnsemble = firstEnsemble.get(
lh.getDistributionSchedule().getWriteSet(lh.getLastAddPushed()).get(1));
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
//Open ledger separately for Ledger checker.
LedgerHandle lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 3 fragment. But returned fragments are "
+ result, 3, result.size());
}
/**
* Tests that LedgerChecker does not return any fragments
* from a closed ledger with 0 entries.
*/
@Test(timeout = 3000)
public void testClosedEmptyLedger() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 3, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
lh.close();
InetSocketAddress lastBookieFromEnsemble = firstEnsemble.get(0);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
//Open ledger separately for Ledger checker.
LedgerHandle lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 0 fragment. But returned fragments are "
+ result, 0, result.size());
}
/**
* Tests that LedgerChecker does not return any fragments
* from a closed ledger with 0 entries.
*/
@Test(timeout = 3000)
public void testClosedSingleEntryLedger() throws Exception {
LedgerHandle lh = bkc.createLedger(3, 2, BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
ArrayList<InetSocketAddress> firstEnsemble = lh.getLedgerMetadata()
.getEnsembles().get(0L);
lh.addEntry(TEST_LEDGER_ENTRY_DATA);
lh.close();
// kill bookie 2
InetSocketAddress lastBookieFromEnsemble = firstEnsemble.get(2);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
//Open ledger separately for Ledger checker.
LedgerHandle lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
Set<LedgerFragment> result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 0 fragment. But returned fragments are "
+ result, 0, result.size());
lh1.close();
// kill bookie 1
lastBookieFromEnsemble = firstEnsemble.get(1);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
startNewBookie();
//Open ledger separately for Ledger checker.
lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 1 fragment. But returned fragments are "
+ result, 1, result.size());
lh1.close();
// kill bookie 0
lastBookieFromEnsemble = firstEnsemble.get(0);
LOG.info("Killing " + lastBookieFromEnsemble + " from ensemble="
+ firstEnsemble);
killBookie(lastBookieFromEnsemble);
startNewBookie();
//Open ledger separately for Ledger checker.
lh1 =bkc.openLedgerNoRecovery(lh.getId(), BookKeeper.DigestType.CRC32,
TEST_LEDGER_PASSWORD);
result = getUnderReplicatedFragments(lh1);
assertNotNull("Result shouldn't be null", result);
assertEquals("There should be 2 fragment. But returned fragments are "
+ result, 2, result.size());
lh1.close();
}
private Set<LedgerFragment> getUnderReplicatedFragments(LedgerHandle lh)
throws InterruptedException {
LedgerChecker checker = new LedgerChecker(bkc);
CheckerCallback cb = new CheckerCallback();
checker.checkLedger(lh, cb);
Set<LedgerFragment> result = cb.waitAndGetResult();
return result;
}
private void killBookie(ArrayList<InetSocketAddress> firstEnsemble,
InetSocketAddress ensemble) throws Exception {
LOG.info("Killing " + ensemble + " from ensemble=" + firstEnsemble);
killBookie(ensemble);
}
}